Merge branch 'master' into mathmerge
This commit is contained in:
@@ -71,16 +71,16 @@ namespace Content.Server.GameObjects.Components.Access
|
||||
|
||||
public static ICollection<string> FindAccessTags(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out IAccess accessComponent))
|
||||
if (entity.TryGetComponent(out IAccess? accessComponent))
|
||||
{
|
||||
return accessComponent.Tags;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out IHandsComponent handsComponent))
|
||||
if (entity.TryGetComponent(out IHandsComponent? handsComponent))
|
||||
{
|
||||
var activeHandEntity = handsComponent.GetActiveHand?.Owner;
|
||||
if (activeHandEntity != null &&
|
||||
activeHandEntity.TryGetComponent(out IAccess handAccessComponent))
|
||||
activeHandEntity.TryGetComponent(out IAccess? handAccessComponent))
|
||||
{
|
||||
return handAccessComponent.Tags;
|
||||
}
|
||||
@@ -90,11 +90,11 @@ namespace Content.Server.GameObjects.Components.Access
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out InventoryComponent inventoryComponent))
|
||||
if (entity.TryGetComponent(out InventoryComponent? inventoryComponent))
|
||||
{
|
||||
if (inventoryComponent.HasSlot(EquipmentSlotDefines.Slots.IDCARD) &&
|
||||
inventoryComponent.TryGetSlotItem(EquipmentSlotDefines.Slots.IDCARD, out ItemComponent item) &&
|
||||
item.Owner.TryGetComponent(out IAccess idAccessComponent)
|
||||
item.Owner.TryGetComponent(out IAccess? idAccessComponent)
|
||||
)
|
||||
{
|
||||
return idAccessComponent.Tags;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
@@ -14,17 +15,18 @@ namespace Content.Server.GameObjects.Components
|
||||
{
|
||||
public override string Name => "Anchorable";
|
||||
|
||||
int IInteractUsing.Priority => 1;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a tool can change the anchored status.
|
||||
/// </summary>
|
||||
/// <param name="user">The user doing the action</param>
|
||||
/// <param name="utilizing">The tool being used, can be null if forcing it</param>
|
||||
/// <param name="collidable">The physics component of the owning entity</param>
|
||||
/// <param name="force">Whether or not to check if the tool is valid</param>
|
||||
/// <returns>true if it is valid, false otherwise</returns>
|
||||
private bool Valid(IEntity user, IEntity? utilizing, [MaybeNullWhen(false)] out ICollidableComponent collidable, bool force = false)
|
||||
private async Task<bool> Valid(IEntity user, IEntity? utilizing, [MaybeNullWhen(false)] bool force = false)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out collidable))
|
||||
if (!Owner.HasComponent<ICollidableComponent>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -32,8 +34,8 @@ namespace Content.Server.GameObjects.Components
|
||||
if (!force)
|
||||
{
|
||||
if (utilizing == null ||
|
||||
!utilizing.TryGetComponent(out ToolComponent tool) ||
|
||||
!tool.UseTool(user, Owner, ToolQuality.Anchoring))
|
||||
!utilizing.TryGetComponent(out ToolComponent? tool) ||
|
||||
!(await tool.UseTool(user, Owner, 0.5f, ToolQuality.Anchoring)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -49,13 +51,14 @@ namespace Content.Server.GameObjects.Components
|
||||
/// <param name="utilizing">The tool being used, if any</param>
|
||||
/// <param name="force">Whether or not to ignore valid tool checks</param>
|
||||
/// <returns>true if anchored, false otherwise</returns>
|
||||
public bool TryAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
|
||||
public async Task<bool> TryAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
|
||||
{
|
||||
if (!Valid(user, utilizing, out var physics, force))
|
||||
if (!(await Valid(user, utilizing, force)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var physics = Owner.GetComponent<ICollidableComponent>();
|
||||
physics.Anchored = true;
|
||||
|
||||
return true;
|
||||
@@ -68,13 +71,14 @@ namespace Content.Server.GameObjects.Components
|
||||
/// <param name="utilizing">The tool being used, if any</param>
|
||||
/// <param name="force">Whether or not to ignore valid tool checks</param>
|
||||
/// <returns>true if unanchored, false otherwise</returns>
|
||||
public bool TryUnAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
|
||||
public async Task<bool> TryUnAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
|
||||
{
|
||||
if (!Valid(user, utilizing, out var physics, force))
|
||||
if (!(await Valid(user, utilizing, force)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var physics = Owner.GetComponent<ICollidableComponent>();
|
||||
physics.Anchored = false;
|
||||
|
||||
return true;
|
||||
@@ -87,16 +91,16 @@ namespace Content.Server.GameObjects.Components
|
||||
/// <param name="utilizing">The tool being used, if any</param>
|
||||
/// <param name="force">Whether or not to ignore valid tool checks</param>
|
||||
/// <returns>true if toggled, false otherwise</returns>
|
||||
private bool TryToggleAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
|
||||
private async Task<bool> TryToggleAnchor(IEntity user, IEntity? utilizing = null, bool force = false)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out ICollidableComponent collidable))
|
||||
if (!Owner.TryGetComponent(out ICollidableComponent? collidable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return collidable.Anchored ?
|
||||
TryUnAnchor(user, utilizing, force) :
|
||||
TryAnchor(user, utilizing, force);
|
||||
await TryUnAnchor(user, utilizing, force) :
|
||||
await TryAnchor(user, utilizing, force);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -105,9 +109,9 @@ namespace Content.Server.GameObjects.Components
|
||||
Owner.EnsureComponent<CollidableComponent>();
|
||||
}
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
return TryToggleAnchor(eventArgs.User, eventArgs.Using);
|
||||
return await TryToggleAnchor(eventArgs.User, eventArgs.Using);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
@@ -23,7 +23,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out DamageableComponent damageable)) return;
|
||||
if (!Owner.TryGetComponent(out IDamageableComponent damageable)) return;
|
||||
Owner.TryGetComponent(out ServerStatusEffectsComponent status);
|
||||
|
||||
var coordinates = Owner.Transform.GridPosition;
|
||||
@@ -52,7 +52,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
if(pressure > Atmospherics.WarningLowPressure)
|
||||
goto default;
|
||||
|
||||
damageable.TakeDamage(DamageType.Brute, Atmospherics.LowPressureDamage, Owner);
|
||||
damageable.ChangeDamage(DamageType.Blunt, Atmospherics.LowPressureDamage, false, Owner);
|
||||
|
||||
if (status == null) break;
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
var damage = (int) MathF.Min((pressure / Atmospherics.HazardHighPressure) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage);
|
||||
|
||||
damageable.TakeDamage(DamageType.Brute, damage, Owner);
|
||||
damageable.ChangeDamage(DamageType.Blunt, damage, false, Owner);
|
||||
|
||||
if (status == null) break;
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
_pressureDanger = GasAnalyzerDanger.Nominal;
|
||||
}
|
||||
|
||||
|
||||
Dirty();
|
||||
_timeSinceSync = 0f;
|
||||
}
|
||||
@@ -131,11 +131,11 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
if (session.AttachedEntity == null)
|
||||
return;
|
||||
|
||||
if (!session.AttachedEntity.TryGetComponent(out IHandsComponent handsComponent))
|
||||
if (!session.AttachedEntity.TryGetComponent(out IHandsComponent? handsComponent))
|
||||
return;
|
||||
|
||||
var activeHandEntity = handsComponent?.GetActiveHand?.Owner;
|
||||
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent gasAnalyzer))
|
||||
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -147,7 +147,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
// Check if position is out of range => don't update
|
||||
if (!_position.Value.InRange(_mapManager, pos, SharedInteractionSystem.InteractionRange))
|
||||
return;
|
||||
|
||||
|
||||
pos = _position.Value;
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
return;
|
||||
}
|
||||
|
||||
if (!player.TryGetComponent(out IHandsComponent handsComponent))
|
||||
if (!player.TryGetComponent(out IHandsComponent? handsComponent))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, player,
|
||||
Loc.GetString("You have no hands."));
|
||||
@@ -203,7 +203,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
}
|
||||
|
||||
var activeHandEntity = handsComponent.GetActiveHand?.Owner;
|
||||
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent gasAnalyzer))
|
||||
if (activeHandEntity == null || !activeHandEntity.TryGetComponent(out GasAnalyzerComponent? gasAnalyzer))
|
||||
{
|
||||
_notifyManager.PopupMessage(serverMsg.Session.AttachedEntity,
|
||||
serverMsg.Session.AttachedEntity,
|
||||
@@ -225,7 +225,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
return;
|
||||
}
|
||||
|
||||
if (eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||
if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
OpenInterface(actor.playerSession, eventArgs.ClickLocation);
|
||||
//TODO: show other sprite when ui open?
|
||||
@@ -236,7 +236,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
void IDropped.Dropped(DroppedEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||
if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
CloseInterface(actor.playerSession);
|
||||
//TODO: if other sprite is shown, change again
|
||||
@@ -245,7 +245,7 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||
if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
OpenInterface(actor.playerSession);
|
||||
//TODO: show other sprite when ui open?
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Atmos
|
||||
{
|
||||
@@ -8,7 +9,8 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
public class GasMixtureComponent : Component
|
||||
{
|
||||
public override string Name => "GasMixture";
|
||||
public GasMixture GasMixture { get; set; } = new GasMixture();
|
||||
|
||||
[ViewVariables] public GasMixture GasMixture { get; set; } = new GasMixture();
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
|
||||
1015
Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
Normal file
1015
Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,73 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Body;
|
||||
using Content.Shared.Body.Scanner;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
public class BodyScannerComponent : Component, IActivate
|
||||
{
|
||||
private BoundUserInterface _userInterface;
|
||||
public sealed override string Name => "BodyScanner";
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent actor) ||
|
||||
actor.playerSession.AttachedEntity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent attempt))
|
||||
{
|
||||
var state = InterfaceState(attempt.Template, attempt.Parts);
|
||||
_userInterface.SetState(state);
|
||||
}
|
||||
|
||||
_userInterface.Open(actor.playerSession);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||
.GetBoundUserInterface(BodyScannerUiKey.Key);
|
||||
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
|
||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { }
|
||||
|
||||
/// <summary>
|
||||
/// Copy BodyTemplate and BodyPart data into a common data class that the client can read.
|
||||
/// </summary>
|
||||
private BodyScannerInterfaceState InterfaceState(BodyTemplate template, IReadOnlyDictionary<string, BodyPart> bodyParts)
|
||||
{
|
||||
var partsData = new Dictionary<string, BodyScannerBodyPartData>();
|
||||
|
||||
foreach (var (slotName, part) in bodyParts)
|
||||
{
|
||||
var mechanismData = new List<BodyScannerMechanismData>();
|
||||
|
||||
foreach (var mechanism in part.Mechanisms)
|
||||
{
|
||||
mechanismData.Add(new BodyScannerMechanismData(mechanism.Name, mechanism.Description,
|
||||
mechanism.RSIPath,
|
||||
mechanism.RSIState, mechanism.MaxDurability, mechanism.CurrentDurability));
|
||||
}
|
||||
|
||||
partsData.Add(slotName,
|
||||
new BodyScannerBodyPartData(part.Name, part.RSIPath, part.RSIState, part.MaxDurability,
|
||||
part.CurrentDurability, mechanismData));
|
||||
}
|
||||
|
||||
var templateData = new BodyScannerTemplateData(template.Name, template.Slots);
|
||||
|
||||
return new BodyScannerInterfaceState(partsData, templateData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Body.Circulatory
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class BloodstreamComponent : Component, IGasMixtureHolder
|
||||
{
|
||||
public override string Name => "Bloodstream";
|
||||
|
||||
/// <summary>
|
||||
/// Max volume of internal solution storage
|
||||
/// </summary>
|
||||
[ViewVariables] private ReagentUnit _initialMaxVolume;
|
||||
|
||||
/// <summary>
|
||||
/// Internal solution for reagent storage
|
||||
/// </summary>
|
||||
[ViewVariables] private SolutionComponent _internalSolution;
|
||||
|
||||
/// <summary>
|
||||
/// Empty volume of internal solution
|
||||
/// </summary>
|
||||
[ViewVariables] public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume;
|
||||
|
||||
[ViewVariables] public GasMixture Air { get; set; } = new GasMixture(6);
|
||||
|
||||
[ViewVariables] public SolutionComponent Solution => _internalSolution;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_internalSolution = Owner.EnsureComponent<SolutionComponent>();
|
||||
_internalSolution.MaxVolume = _initialMaxVolume;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to transfer provided solution to internal solution.
|
||||
/// Only supports complete transfers
|
||||
/// </summary>
|
||||
/// <param name="solution">Solution to be transferred</param>
|
||||
/// <returns>Whether or not transfer was a success</returns>
|
||||
public bool TryTransferSolution(Solution solution)
|
||||
{
|
||||
// For now doesn't support partial transfers
|
||||
if (solution.TotalVolume + _internalSolution.CurrentVolume > _internalSolution.MaxVolume)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_internalSolution.TryAddSolution(solution, false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PumpToxins(GasMixture into, float pressure)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out MetabolismComponent metabolism))
|
||||
{
|
||||
Air.PumpGasTo(into, pressure);
|
||||
return;
|
||||
}
|
||||
|
||||
var toxins = metabolism.Clean(this);
|
||||
|
||||
toxins.PumpGasTo(into, pressure);
|
||||
Air.Merge(toxins);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Nutrition;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -11,7 +11,7 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Nutrition
|
||||
namespace Content.Server.GameObjects.Components.Body.Digestive
|
||||
{
|
||||
/// <summary>
|
||||
/// Where reagents go when ingested. Tracks ingested reagents over time, and
|
||||
@@ -25,7 +25,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
#pragma warning restore 649
|
||||
|
||||
/// <summary>
|
||||
/// Max volume of internal solution storage
|
||||
/// Max volume of internal solution storage
|
||||
/// </summary>
|
||||
public ReagentUnit MaxVolume
|
||||
{
|
||||
@@ -34,33 +34,29 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal solution storage
|
||||
/// Internal solution storage
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private SolutionComponent _stomachContents;
|
||||
|
||||
/// <summary>
|
||||
/// Initial internal solution storage volume
|
||||
/// Initial internal solution storage volume
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private ReagentUnit _initialMaxVolume;
|
||||
|
||||
/// <summary>
|
||||
/// Time in seconds between reagents being ingested and them being transferred to <see cref="BloodstreamComponent"/>
|
||||
/// Time in seconds between reagents being ingested and them being transferred
|
||||
/// to <see cref="BloodstreamComponent"/>
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private float _digestionDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Used to track how long each reagent has been in the stomach
|
||||
/// Used to track how long each reagent has been in the stomach
|
||||
/// </summary>
|
||||
private readonly List<ReagentDelta> _reagentDeltas = new List<ReagentDelta>();
|
||||
|
||||
/// <summary>
|
||||
/// Reference to bloodstream where digested reagents are transferred to
|
||||
/// </summary>
|
||||
private BloodstreamComponent _bloodstream;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
@@ -70,14 +66,10 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
_stomachContents = Owner.GetComponent<SolutionComponent>();
|
||||
_stomachContents.MaxVolume = _initialMaxVolume;
|
||||
if (!Owner.TryGetComponent<BloodstreamComponent>(out _bloodstream))
|
||||
{
|
||||
Logger.Warning(_localizationManager.GetString(
|
||||
"StomachComponent entity does not have a BloodstreamComponent, which is required for it to function. Owner entity name: {0}",
|
||||
Owner.Name));
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTransferSolution(Solution solution)
|
||||
@@ -88,9 +80,9 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
return false;
|
||||
}
|
||||
|
||||
//Add solution to _stomachContents
|
||||
// Add solution to _stomachContents
|
||||
_stomachContents.TryAddSolution(solution, false, true);
|
||||
//Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach
|
||||
// Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach
|
||||
foreach (var reagent in solution.Contents)
|
||||
{
|
||||
_reagentDeltas.Add(new ReagentDelta(reagent.ReagentId, reagent.Quantity));
|
||||
@@ -100,23 +92,26 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates digestion status of ingested reagents. Once reagents surpass _digestionDelay
|
||||
/// they are moved to the bloodstream, where they are then metabolized.
|
||||
/// Updates digestion status of ingested reagents.
|
||||
/// Once reagents surpass _digestionDelay they are moved to the bloodstream,
|
||||
/// where they are then metabolized.
|
||||
/// </summary>
|
||||
/// <param name="tickTime">The time since the last update in seconds.</param>
|
||||
public void OnUpdate(float tickTime)
|
||||
/// <param name="frameTime">The time since the last update in seconds.</param>
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (_bloodstream == null)
|
||||
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Add reagents ready for transfer to bloodstream to transferSolution
|
||||
// Add reagents ready for transfer to bloodstream to transferSolution
|
||||
var transferSolution = new Solution();
|
||||
foreach (var delta in _reagentDeltas.ToList()) //Use ToList here to remove entries while iterating
|
||||
|
||||
// Use ToList here to remove entries while iterating
|
||||
foreach (var delta in _reagentDeltas.ToList())
|
||||
{
|
||||
//Increment lifetime of reagents
|
||||
delta.Increment(tickTime);
|
||||
delta.Increment(frameTime);
|
||||
if (delta.Lifetime > _digestionDelay)
|
||||
{
|
||||
_stomachContents.TryRemoveReagent(delta.ReagentId, delta.Quantity);
|
||||
@@ -124,12 +119,13 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
_reagentDeltas.Remove(delta);
|
||||
}
|
||||
}
|
||||
//Transfer digested reagents to bloodstream
|
||||
_bloodstream.TryTransferSolution(transferSolution);
|
||||
|
||||
// Transfer digested reagents to bloodstream
|
||||
bloodstream.TryTransferSolution(transferSolution);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to track quantity changes when ingesting & digesting reagents
|
||||
/// Used to track quantity changes when ingesting & digesting reagents
|
||||
/// </summary>
|
||||
private class ReagentDelta
|
||||
{
|
||||
@@ -0,0 +1,181 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Server.Body;
|
||||
using Content.Shared.Body.Surgery;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
/// <summary>
|
||||
/// Component representing a dropped, tangible <see cref="BodyPart"/> entity.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
||||
private BodyManagerComponent _bodyManagerComponentCache;
|
||||
private int _idHash;
|
||||
private IEntity _performerCache;
|
||||
|
||||
private BoundUserInterface _userInterface;
|
||||
|
||||
public sealed override string Name => "DroppedBodyPart";
|
||||
|
||||
[ViewVariables] public BodyPart ContainedBodyPart { get; private set; }
|
||||
|
||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CloseAllSurgeryUIs();
|
||||
_optionsCache.Clear();
|
||||
_performerCache = null;
|
||||
_bodyManagerComponentCache = null;
|
||||
|
||||
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent bodyManager))
|
||||
{
|
||||
SendBodySlotListToUser(eventArgs, bodyManager);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
|
||||
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
|
||||
public void TransferBodyPartData(BodyPart data)
|
||||
{
|
||||
ContainedBodyPart = data;
|
||||
Owner.Name = Loc.GetString(ContainedBodyPart.Name);
|
||||
|
||||
if (Owner.TryGetComponent(out SpriteComponent component))
|
||||
{
|
||||
component.LayerSetRSI(0, data.RSIPath);
|
||||
component.LayerSetState(0, data.RSIState);
|
||||
|
||||
if (data.RSIColor.HasValue)
|
||||
{
|
||||
component.LayerSetColor(0, data.RSIColor.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SendBodySlotListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager)
|
||||
{
|
||||
// Create dictionary to send to client (text to be shown : data sent back if selected)
|
||||
var toSend = new Dictionary<string, int>();
|
||||
|
||||
// Here we are trying to grab a list of all empty BodySlots adjacent to an existing BodyPart that can be
|
||||
// attached to. i.e. an empty left hand slot, connected to an occupied left arm slot would be valid.
|
||||
var unoccupiedSlots = bodyManager.AllSlots.ToList().Except(bodyManager.OccupiedSlots.ToList()).ToList();
|
||||
foreach (var slot in unoccupiedSlots)
|
||||
{
|
||||
if (!bodyManager.TryGetSlotType(slot, out var typeResult) ||
|
||||
typeResult != ContainedBodyPart.PartType ||
|
||||
!bodyManager.TryGetBodyPartConnections(slot, out var parts))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var connectedPart in parts)
|
||||
{
|
||||
if (!connectedPart.CanAttachBodyPart(ContainedBodyPart))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_optionsCache.Add(_idHash, slot);
|
||||
toSend.Add(slot, _idHash++);
|
||||
}
|
||||
}
|
||||
|
||||
if (_optionsCache.Count > 0)
|
||||
{
|
||||
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
|
||||
UpdateSurgeryUIBodyPartSlotRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession,
|
||||
toSend);
|
||||
_performerCache = eventArgs.User;
|
||||
_bodyManagerComponentCache = bodyManager;
|
||||
}
|
||||
else // If surgery cannot be performed, show message saying so.
|
||||
{
|
||||
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User,
|
||||
Loc.GetString("You see no way to install {0:theName}.", Owner));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the client chooses from a list of possible BodyPartSlots to install the limb on.
|
||||
/// </summary>
|
||||
private void HandleReceiveBodyPartSlot(int key)
|
||||
{
|
||||
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
||||
|
||||
// TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
|
||||
if (!_optionsCache.TryGetValue(key, out var targetObject))
|
||||
{
|
||||
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache,
|
||||
Loc.GetString("You see no useful way to attach {0:theName} anymore.", Owner));
|
||||
}
|
||||
|
||||
var target = targetObject as string;
|
||||
|
||||
_sharedNotifyManager.PopupMessage(
|
||||
_bodyManagerComponentCache.Owner,
|
||||
_performerCache,
|
||||
!_bodyManagerComponentCache.InstallDroppedBodyPart(this, target)
|
||||
? Loc.GetString("You can't attach it!")
|
||||
: Loc.GetString("You attach {0:theName}.", ContainedBodyPart));
|
||||
}
|
||||
|
||||
private void OpenSurgeryUI(IPlayerSession session)
|
||||
{
|
||||
_userInterface.Open(session);
|
||||
}
|
||||
|
||||
private void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||
{
|
||||
_userInterface.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
|
||||
}
|
||||
|
||||
private void CloseSurgeryUI(IPlayerSession session)
|
||||
{
|
||||
_userInterface.Close(session);
|
||||
}
|
||||
|
||||
private void CloseAllSurgeryUIs()
|
||||
{
|
||||
_userInterface.CloseAll();
|
||||
}
|
||||
|
||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message.Message)
|
||||
{
|
||||
case ReceiveBodyPartSlotSurgeryUIMessage msg:
|
||||
HandleReceiveBodyPartSlot(msg.SelectedOptionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Body;
|
||||
using Content.Server.Body.Mechanisms;
|
||||
using Content.Shared.Body.Mechanism;
|
||||
using Content.Shared.Body.Surgery;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
/// <summary>
|
||||
/// Component representing a dropped, tangible <see cref="Mechanism"/> entity.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class DroppedMechanismComponent : Component, IAfterInteract
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
|
||||
[Dependency] private IPrototypeManager _prototypeManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
public sealed override string Name => "DroppedMechanism";
|
||||
|
||||
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
||||
|
||||
private BodyManagerComponent _bodyManagerComponentCache;
|
||||
|
||||
private int _idHash;
|
||||
|
||||
private IEntity _performerCache;
|
||||
|
||||
private BoundUserInterface _userInterface;
|
||||
|
||||
[ViewVariables] public Mechanism ContainedMechanism { get; private set; }
|
||||
|
||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CloseAllSurgeryUIs();
|
||||
_optionsCache.Clear();
|
||||
_performerCache = null;
|
||||
_bodyManagerComponentCache = null;
|
||||
|
||||
if (eventArgs.Target.TryGetComponent<BodyManagerComponent>(out var bodyManager))
|
||||
{
|
||||
SendBodyPartListToUser(eventArgs, bodyManager);
|
||||
}
|
||||
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(out var droppedBodyPart))
|
||||
{
|
||||
if (droppedBodyPart.ContainedBodyPart == null)
|
||||
{
|
||||
Logger.Debug(
|
||||
"Installing a mechanism was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!");
|
||||
throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!");
|
||||
}
|
||||
|
||||
if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this))
|
||||
{
|
||||
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User,
|
||||
Loc.GetString("You can't fit it in!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
|
||||
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
|
||||
public void InitializeDroppedMechanism(Mechanism data)
|
||||
{
|
||||
ContainedMechanism = data;
|
||||
Owner.Name = Loc.GetString(ContainedMechanism.Name);
|
||||
|
||||
if (Owner.TryGetComponent(out SpriteComponent component))
|
||||
{
|
||||
component.LayerSetRSI(0, data.RSIPath);
|
||||
component.LayerSetState(0, data.RSIState);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
// This is a temporary way to have spawnable hard-coded DroppedMechanismComponent prototypes
|
||||
// In the future (when it becomes possible) DroppedMechanismComponent should be auto-generated from
|
||||
// the Mechanism prototypes
|
||||
var debugLoadMechanismData = "";
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref debugLoadMechanismData, "debugLoadMechanismData", "");
|
||||
|
||||
if (serializer.Reading && debugLoadMechanismData != "")
|
||||
{
|
||||
_prototypeManager.TryIndex(debugLoadMechanismData, out MechanismPrototype data);
|
||||
|
||||
var mechanism = new Mechanism(data);
|
||||
mechanism.EnsureInitialize();
|
||||
|
||||
InitializeDroppedMechanism(mechanism);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendBodyPartListToUser(AfterInteractEventArgs eventArgs, BodyManagerComponent bodyManager)
|
||||
{
|
||||
// Create dictionary to send to client (text to be shown : data sent back if selected)
|
||||
var toSend = new Dictionary<string, int>();
|
||||
|
||||
foreach (var (key, value) in bodyManager.Parts)
|
||||
{
|
||||
// For each limb in the target, add it to our cache if it is a valid option.
|
||||
if (value.CanInstallMechanism(ContainedMechanism))
|
||||
{
|
||||
_optionsCache.Add(_idHash, value);
|
||||
toSend.Add(key + ": " + value.Name, _idHash++);
|
||||
}
|
||||
}
|
||||
|
||||
if (_optionsCache.Count > 0)
|
||||
{
|
||||
OpenSurgeryUI(eventArgs.User.GetComponent<BasicActorComponent>().playerSession);
|
||||
UpdateSurgeryUIBodyPartRequest(eventArgs.User.GetComponent<BasicActorComponent>().playerSession,
|
||||
toSend);
|
||||
_performerCache = eventArgs.User;
|
||||
_bodyManagerComponentCache = bodyManager;
|
||||
}
|
||||
else // If surgery cannot be performed, show message saying so.
|
||||
{
|
||||
_sharedNotifyManager.PopupMessage(eventArgs.Target, eventArgs.User,
|
||||
Loc.GetString("You see no way to install the {0}.", Owner.Name));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the client chooses from a list of possible BodyParts that can be operated on.
|
||||
/// </summary>
|
||||
private void HandleReceiveBodyPart(int key)
|
||||
{
|
||||
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
||||
|
||||
// TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
|
||||
if (!_optionsCache.TryGetValue(key, out var targetObject))
|
||||
{
|
||||
_sharedNotifyManager.PopupMessage(_bodyManagerComponentCache.Owner, _performerCache,
|
||||
Loc.GetString("You see no useful way to use the {0} anymore.", Owner.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
var target = targetObject as BodyPart;
|
||||
|
||||
_sharedNotifyManager.PopupMessage(
|
||||
_bodyManagerComponentCache.Owner,
|
||||
_performerCache,
|
||||
!target.TryInstallDroppedMechanism(this)
|
||||
? Loc.GetString("You can't fit it in!")
|
||||
: Loc.GetString("You jam the {1} inside {0:them}.", _performerCache, ContainedMechanism.Name));
|
||||
|
||||
// TODO: {1:theName}
|
||||
}
|
||||
|
||||
private void OpenSurgeryUI(IPlayerSession session)
|
||||
{
|
||||
_userInterface.Open(session);
|
||||
}
|
||||
|
||||
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||
{
|
||||
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
|
||||
}
|
||||
|
||||
private void CloseSurgeryUI(IPlayerSession session)
|
||||
{
|
||||
_userInterface.Close(session);
|
||||
}
|
||||
|
||||
private void CloseAllSurgeryUIs()
|
||||
{
|
||||
_userInterface.CloseAll();
|
||||
}
|
||||
|
||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message.Message)
|
||||
{
|
||||
case ReceiveBodyPartSurgeryUIMessage msg:
|
||||
HandleReceiveBodyPart(msg.SelectedOptionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Body.Respiratory
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class LungComponent : Component, IGasMixtureHolder
|
||||
{
|
||||
public override string Name => "Lung";
|
||||
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
/// <summary>
|
||||
/// The pressure that this lung exerts on the air around it
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)] private float Pressure { get; set; }
|
||||
|
||||
[ViewVariables] public GasMixture Air { get; set; } = new GasMixture();
|
||||
|
||||
[ViewVariables] public LungStatus Status { get; set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataReadWriteFunction(
|
||||
"volume",
|
||||
6,
|
||||
vol => Air.Volume = vol,
|
||||
() => Air.Volume);
|
||||
serializer.DataField(this, l => l.Pressure, "pressure", 100);
|
||||
}
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (Status == LungStatus.None)
|
||||
{
|
||||
Status = LungStatus.Inhaling;
|
||||
}
|
||||
|
||||
_accumulatedFrameTime += Status switch
|
||||
{
|
||||
LungStatus.Inhaling => frameTime,
|
||||
LungStatus.Exhaling => -frameTime,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var absoluteTime = Math.Abs(_accumulatedFrameTime);
|
||||
if (absoluteTime < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (Status)
|
||||
{
|
||||
case LungStatus.Inhaling:
|
||||
Inhale(absoluteTime);
|
||||
Status = LungStatus.Exhaling;
|
||||
break;
|
||||
case LungStatus.Exhaling:
|
||||
Exhale(absoluteTime);
|
||||
Status = LungStatus.Inhaling;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_accumulatedFrameTime = absoluteTime - 2;
|
||||
}
|
||||
|
||||
public void Inhale(float frameTime)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Owner.Transform.GridPosition.TryGetTileAir(out var tileAir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var amount = Atmospherics.BreathPercentage * frameTime;
|
||||
var volumeRatio = amount / tileAir.Volume;
|
||||
var temp = tileAir.RemoveRatio(volumeRatio);
|
||||
|
||||
temp.PumpGasTo(Air, Pressure);
|
||||
Air.PumpGasTo(bloodstream.Air, Pressure);
|
||||
tileAir.Merge(temp);
|
||||
}
|
||||
|
||||
public void Exhale(float frameTime)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Owner.Transform.GridPosition.TryGetTileAir(out var tileAir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bloodstream.PumpToxins(Air, Pressure);
|
||||
|
||||
var amount = Atmospherics.BreathPercentage * frameTime;
|
||||
var volumeRatio = amount / tileAir.Volume;
|
||||
var temp = tileAir.RemoveRatio(volumeRatio);
|
||||
|
||||
temp.PumpGasTo(tileAir, Pressure);
|
||||
Air.Merge(temp);
|
||||
}
|
||||
}
|
||||
|
||||
public enum LungStatus
|
||||
{
|
||||
None = 0,
|
||||
Inhaling,
|
||||
Exhaling
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.Body;
|
||||
using Content.Server.Body.Mechanisms;
|
||||
using Content.Server.Body.Surgery;
|
||||
using Content.Shared.Body.Surgery;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Body
|
||||
{
|
||||
// TODO: add checks to close UI if user walks too far away from tool or target.
|
||||
|
||||
/// <summary>
|
||||
/// Server-side component representing a generic tool capable of performing surgery.
|
||||
/// For instance, the scalpel.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
public override string Name => "SurgeryTool";
|
||||
public override uint? NetID => ContentNetIDs.SURGERY;
|
||||
|
||||
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
|
||||
|
||||
private float _baseOperateTime;
|
||||
|
||||
private BodyManagerComponent _bodyManagerComponentCache;
|
||||
|
||||
private ISurgeon.MechanismRequestCallback _callbackCache;
|
||||
|
||||
private int _idHash;
|
||||
|
||||
private IEntity _performerCache;
|
||||
|
||||
private SurgeryType _surgeryType;
|
||||
|
||||
private BoundUserInterface _userInterface;
|
||||
|
||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CloseAllSurgeryUIs();
|
||||
_optionsCache.Clear();
|
||||
|
||||
_performerCache = null;
|
||||
_bodyManagerComponentCache = null;
|
||||
_callbackCache = null;
|
||||
|
||||
// Attempt surgery on a BodyManagerComponent by sending a list of operable BodyParts to the client to choose from
|
||||
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent body))
|
||||
{
|
||||
// Create dictionary to send to client (text to be shown : data sent back if selected)
|
||||
var toSend = new Dictionary<string, int>();
|
||||
|
||||
foreach (var (key, value) in body.Parts)
|
||||
{
|
||||
// For each limb in the target, add it to our cache if it is a valid option.
|
||||
if (value.SurgeryCheck(_surgeryType))
|
||||
{
|
||||
_optionsCache.Add(_idHash, value);
|
||||
toSend.Add(key + ": " + value.Name, _idHash++);
|
||||
}
|
||||
}
|
||||
|
||||
if (_optionsCache.Count > 0)
|
||||
{
|
||||
OpenSurgeryUI(actor.playerSession);
|
||||
UpdateSurgeryUIBodyPartRequest(actor.playerSession, toSend);
|
||||
_performerCache = eventArgs.User; // Also, cache the data.
|
||||
_bodyManagerComponentCache = body;
|
||||
}
|
||||
else // If surgery cannot be performed, show message saying so.
|
||||
{
|
||||
SendNoUsefulWayToUsePopup();
|
||||
}
|
||||
}
|
||||
else if (eventArgs.Target.TryGetComponent<DroppedBodyPartComponent>(out var droppedBodyPart))
|
||||
{
|
||||
// Attempt surgery on a DroppedBodyPart - there's only one possible target so no need for selection UI
|
||||
_performerCache = eventArgs.User;
|
||||
|
||||
if (droppedBodyPart.ContainedBodyPart == null)
|
||||
{
|
||||
// Throw error if the DroppedBodyPart has no data in it.
|
||||
Logger.Debug(
|
||||
"Surgery was attempted on an IEntity with a DroppedBodyPartComponent that doesn't have a BodyPart in it!");
|
||||
throw new InvalidOperationException("A DroppedBodyPartComponent exists without a BodyPart in it!");
|
||||
}
|
||||
|
||||
// If surgery can be performed...
|
||||
if (!droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType))
|
||||
{
|
||||
SendNoUsefulWayToUsePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
//...do the surgery.
|
||||
if (droppedBodyPart.ContainedBodyPart.AttemptSurgery(_surgeryType, droppedBodyPart, this,
|
||||
eventArgs.User))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Log error if the surgery fails somehow.
|
||||
Logger.Debug($"Error when trying to perform surgery on ${nameof(BodyPart)} {eventArgs.User.Name}");
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public float BaseOperationTime { get => _baseOperateTime; set => _baseOperateTime = value; }
|
||||
|
||||
public void RequestMechanism(IEnumerable<Mechanism> options, ISurgeon.MechanismRequestCallback callback)
|
||||
{
|
||||
var toSend = new Dictionary<string, int>();
|
||||
foreach (var mechanism in options)
|
||||
{
|
||||
_optionsCache.Add(_idHash, mechanism);
|
||||
toSend.Add(mechanism.Name, _idHash++);
|
||||
}
|
||||
|
||||
if (_optionsCache.Count > 0)
|
||||
{
|
||||
OpenSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
||||
UpdateSurgeryUIMechanismRequest(_performerCache.GetComponent<BasicActorComponent>().playerSession,
|
||||
toSend);
|
||||
_callbackCache = callback;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Error on callback from mechanisms: there were no viable options to choose from!");
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
|
||||
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
|
||||
}
|
||||
|
||||
private void OpenSurgeryUI(IPlayerSession session)
|
||||
{
|
||||
_userInterface.Open(session);
|
||||
}
|
||||
|
||||
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||
{
|
||||
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
|
||||
}
|
||||
|
||||
private void UpdateSurgeryUIMechanismRequest(IPlayerSession session, Dictionary<string, int> options)
|
||||
{
|
||||
_userInterface.SendMessage(new RequestMechanismSurgeryUIMessage(options), session);
|
||||
}
|
||||
|
||||
private void CloseSurgeryUI(IPlayerSession session)
|
||||
{
|
||||
_userInterface.Close(session);
|
||||
}
|
||||
|
||||
private void CloseAllSurgeryUIs()
|
||||
{
|
||||
_userInterface.CloseAll();
|
||||
}
|
||||
|
||||
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message.Message)
|
||||
{
|
||||
case ReceiveBodyPartSurgeryUIMessage msg:
|
||||
HandleReceiveBodyPart(msg.SelectedOptionId);
|
||||
break;
|
||||
case ReceiveMechanismSurgeryUIMessage msg:
|
||||
HandleReceiveMechanism(msg.SelectedOptionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the client chooses from a list of possible
|
||||
/// <see cref="BodyPart"/> that can be operated on.
|
||||
/// </summary>
|
||||
private void HandleReceiveBodyPart(int key)
|
||||
{
|
||||
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
||||
// TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
|
||||
if (!_optionsCache.TryGetValue(key, out var targetObject))
|
||||
{
|
||||
SendNoUsefulWayToUseAnymorePopup();
|
||||
}
|
||||
|
||||
var target = targetObject as BodyPart;
|
||||
|
||||
if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache))
|
||||
{
|
||||
SendNoUsefulWayToUseAnymorePopup();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after the client chooses from a list of possible
|
||||
/// <see cref="Mechanism"/> to choose from.
|
||||
/// </summary>
|
||||
private void HandleReceiveMechanism(int key)
|
||||
{
|
||||
// TODO: sanity checks to see whether user is in range, user is still able-bodied, target is still the same, etc etc
|
||||
if (!_optionsCache.TryGetValue(key, out var targetObject))
|
||||
{
|
||||
SendNoUsefulWayToUseAnymorePopup();
|
||||
}
|
||||
|
||||
var target = targetObject as Mechanism;
|
||||
|
||||
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
|
||||
_callbackCache(target, _bodyManagerComponentCache, this, _performerCache);
|
||||
}
|
||||
|
||||
private void SendNoUsefulWayToUsePopup()
|
||||
{
|
||||
_sharedNotifyManager.PopupMessage(
|
||||
_bodyManagerComponentCache.Owner,
|
||||
_performerCache,
|
||||
Loc.GetString("You see no useful way to use {0:theName}.", Owner));
|
||||
}
|
||||
|
||||
private void SendNoUsefulWayToUseAnymorePopup()
|
||||
{
|
||||
_sharedNotifyManager.PopupMessage(
|
||||
_bodyManagerComponentCache.Owner,
|
||||
_performerCache,
|
||||
Loc.GetString("You see no useful way to use {0:theName} anymore.", Owner));
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _surgeryType, "surgeryType", SurgeryType.Incision);
|
||||
serializer.DataField(ref _baseOperateTime, "baseOperateTime", 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
/// </summary>
|
||||
private void BuckleStatus()
|
||||
{
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Buckled,
|
||||
Buckled
|
||||
@@ -291,7 +291,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
appearance.SetData(BuckleVisuals.Buckled, true);
|
||||
}
|
||||
@@ -359,12 +359,12 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
Owner.Transform.WorldRotation = oldBuckledTo.Owner.Transform.WorldRotation;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
appearance.SetData(BuckleVisuals.Buckled, false);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out StunnableComponent stunnable) && stunnable.KnockedDown)
|
||||
if (Owner.TryGetComponent(out StunnableComponent? stunnable) && stunnable.KnockedDown)
|
||||
{
|
||||
StandingStateHelper.Down(Owner);
|
||||
}
|
||||
@@ -373,14 +373,14 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
StandingStateHelper.Standing(Owner);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out SpeciesComponent species))
|
||||
if (Owner.TryGetComponent(out MobStateManagerComponent? stateManager))
|
||||
{
|
||||
species.CurrentDamageState.EnterState(Owner);
|
||||
stateManager.CurrentMobState.EnterState(Owner);
|
||||
}
|
||||
|
||||
BuckleStatus();
|
||||
|
||||
if (oldBuckledTo.Owner.TryGetComponent(out StrapComponent strap))
|
||||
if (oldBuckledTo.Owner.TryGetComponent(out StrapComponent? strap))
|
||||
{
|
||||
strap.Remove(this);
|
||||
_entitySystem.GetEntitySystem<AudioSystem>()
|
||||
@@ -535,7 +535,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
_entityManager.EventBus.UnsubscribeEvents(this);
|
||||
|
||||
if (BuckledTo != null &&
|
||||
BuckledTo.Owner.TryGetComponent(out StrapComponent strap))
|
||||
BuckledTo.Owner.TryGetComponent(out StrapComponent? strap))
|
||||
{
|
||||
strap.Remove(this);
|
||||
}
|
||||
@@ -552,7 +552,7 @@ namespace Content.Server.GameObjects.Components.Buckle
|
||||
|
||||
if (BuckledTo != null &&
|
||||
Owner.Transform.WorldRotation.GetCardinalDir() == Direction.North &&
|
||||
BuckledTo.Owner.TryGetComponent(out SpriteComponent strapSprite))
|
||||
BuckledTo.Owner.TryGetComponent(out SpriteComponent? strapSprite))
|
||||
{
|
||||
drawDepth = strapSprite.DrawDepth - 1;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace Content.Server.GameObjects.Components.Cargo
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
@@ -376,7 +377,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// </summary>
|
||||
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
|
||||
/// <returns></returns>
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs args)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs args)
|
||||
{
|
||||
if (!args.User.TryGetComponent(out IHandsComponent hands))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Metabolism;
|
||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Chemistry;
|
||||
@@ -134,7 +134,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
else //Handle injecting into bloodstream
|
||||
{
|
||||
if (targetEntity.TryGetComponent<BloodstreamComponent>(out var bloodstream) && _toggleState == InjectorToggleMode.Inject)
|
||||
if (targetEntity.TryGetComponent(out BloodstreamComponent bloodstream) &&
|
||||
_toggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||
using Content.Server.GameObjects.Components.Nutrition;
|
||||
using Content.Server.GameObjects.Components.Utensil;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Chemistry;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.Interfaces;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -50,7 +51,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// </summary>
|
||||
/// <param name="eventArgs">Attack event args</param>
|
||||
/// <returns></returns>
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
//Get target solution component
|
||||
if (!Owner.TryGetComponent<SolutionComponent>(out var targetSolution))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
@@ -290,7 +291,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// </summary>
|
||||
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
|
||||
/// <returns></returns>
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs args)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs args)
|
||||
{
|
||||
if (!args.User.TryGetComponent(out IHandsComponent hands))
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// ECS component that manages a liquid solution of reagents.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
internal class SolutionComponent : SharedSolutionComponent, IExamine
|
||||
public class SolutionComponent : SharedSolutionComponent, IExamine
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
@@ -59,7 +60,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
{
|
||||
_state = value;
|
||||
|
||||
if (!Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -92,7 +93,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent receiver) &&
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver) &&
|
||||
!receiver.Powered)
|
||||
{
|
||||
return false;
|
||||
@@ -113,7 +114,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out ICollidableComponent collidable) ||
|
||||
if (!entity.TryGetComponent(out ICollidableComponent? collidable) ||
|
||||
collidable.Anchored)
|
||||
{
|
||||
return false;
|
||||
@@ -154,7 +155,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ICollidableComponent collidable))
|
||||
if (entity.TryGetComponent(out ICollidableComponent? collidable))
|
||||
{
|
||||
var controller = collidable.EnsureController<ConveyedController>();
|
||||
controller.Move(direction, _speed * frameTime);
|
||||
@@ -162,10 +163,10 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
}
|
||||
}
|
||||
|
||||
private bool ToolUsed(IEntity user, ToolComponent tool)
|
||||
private async Task<bool> ToolUsed(IEntity user, ToolComponent tool)
|
||||
{
|
||||
if (!Owner.HasComponent<ItemComponent>() &&
|
||||
tool.UseTool(user, Owner, ToolQuality.Prying))
|
||||
await tool.UseTool(user, Owner, 0.5f, ToolQuality.Prying))
|
||||
{
|
||||
State = ConveyorState.Loose;
|
||||
|
||||
@@ -224,7 +225,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!@switch.TryGetComponent(out ConveyorSwitchComponent component))
|
||||
if (!@switch.TryGetComponent(out ConveyorSwitchComponent? component))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -244,17 +245,17 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent conveyorSwitch))
|
||||
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent? conveyorSwitch))
|
||||
{
|
||||
conveyorSwitch.Connect(this, eventArgs.User);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventArgs.Using.TryGetComponent(out ToolComponent tool))
|
||||
if (eventArgs.Using.TryGetComponent(out ToolComponent? tool))
|
||||
{
|
||||
return ToolUsed(eventArgs.User, tool);
|
||||
return await ToolUsed(eventArgs.User, tool);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Conveyor;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -33,7 +34,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
{
|
||||
_state = value;
|
||||
|
||||
if (Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
appearance.SetData(ConveyorVisuals.State, value);
|
||||
}
|
||||
@@ -144,7 +145,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!conveyor.TryGetComponent(out ConveyorComponent component))
|
||||
if (!conveyor.TryGetComponent(out ConveyorComponent? component))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -171,7 +172,7 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!@switch.TryGetComponent(out ConveyorSwitchComponent component))
|
||||
if (!@switch.TryGetComponent(out ConveyorSwitchComponent? component))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -193,15 +194,15 @@ namespace Content.Server.GameObjects.Components.Conveyor
|
||||
return NextState();
|
||||
}
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent(out ConveyorComponent conveyor))
|
||||
if (eventArgs.Using.TryGetComponent(out ConveyorComponent? conveyor))
|
||||
{
|
||||
Connect(conveyor, eventArgs.User);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent otherSwitch))
|
||||
if (eventArgs.Using.TryGetComponent(out ConveyorSwitchComponent? otherSwitch))
|
||||
{
|
||||
SyncWith(otherSwitch, eventArgs.User);
|
||||
return true;
|
||||
|
||||
@@ -1,39 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
// TODO: Repair needs to set CurrentDamageState to DamageState.Alive, but it doesn't exist... should be easy enough if it's just an interface you can slap on BreakableComponent
|
||||
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and sets it to a "broken state" after taking
|
||||
/// enough damage.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BreakableComponent : Component, IOnDamageBehavior, IExAct
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class BreakableComponent : RuinableComponent, IExAct
|
||||
{
|
||||
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
||||
#pragma warning restore 649
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Breakable";
|
||||
public DamageThreshold Threshold { get; private set; }
|
||||
[Dependency] private readonly IRobustRandom _random;
|
||||
#pragma warning restore 649
|
||||
|
||||
public DamageType damageType = DamageType.Total;
|
||||
public int damageValue = 0;
|
||||
public bool broken = false;
|
||||
public override string Name => "Breakable";
|
||||
|
||||
private ActSystem _actSystem;
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
public override List<DamageState> SupportedDamageStates =>
|
||||
new List<DamageState> {DamageState.Alive, DamageState.Dead};
|
||||
|
||||
public override DamageState CurrentDamageState => _currentDamageState;
|
||||
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
switch (eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
PerformDestruction();
|
||||
break;
|
||||
case ExplosionSeverity.Heavy:
|
||||
PerformDestruction();
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
if (_random.Prob(0.5f))
|
||||
{
|
||||
PerformDestruction();
|
||||
}
|
||||
|
||||
serializer.DataField(ref damageValue, "thresholdvalue", 100);
|
||||
serializer.DataField(ref damageType, "thresholdtype", DamageType.Total);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
@@ -42,38 +62,21 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||
}
|
||||
|
||||
public List<DamageThreshold> GetAllDamageThresholds()
|
||||
// Might want to move this down and have a more standardized method of revival
|
||||
public void FixAllDamage()
|
||||
{
|
||||
Threshold = new DamageThreshold(damageType, damageValue, ThresholdType.Breakage);
|
||||
return new List<DamageThreshold>() {Threshold};
|
||||
Heal();
|
||||
_currentDamageState = DamageState.Alive;
|
||||
}
|
||||
|
||||
public void OnDamageThresholdPassed(object obj, DamageThresholdPassedEventArgs e)
|
||||
protected override void DestructionBehavior()
|
||||
{
|
||||
if (e.Passed && e.DamageThreshold == Threshold && broken == false)
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
if (!Owner.Deleted && DestroySound != string.Empty)
|
||||
{
|
||||
broken = true;
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
var pos = Owner.Transform.GridPosition;
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
var prob = IoCManager.Resolve<IRobustRandom>();
|
||||
switch (eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
break;
|
||||
case ExplosionSeverity.Heavy:
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
if(prob.Prob(0.4f))
|
||||
_actSystem.HandleBreakage(Owner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -23,7 +24,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
|
||||
public override string Name => "DamageOnHighSpeedImpact";
|
||||
|
||||
public DamageType Damage { get; set; } = DamageType.Brute;
|
||||
public DamageType Damage { get; set; } = DamageType.Blunt;
|
||||
public float MinimumSpeed { get; set; } = 20f;
|
||||
public int BaseDamage { get; set; } = 5;
|
||||
public float Factor { get; set; } = 0.75f;
|
||||
@@ -38,7 +39,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, x => Damage, "damage", DamageType.Brute);
|
||||
serializer.DataField(this, x => Damage, "damage", DamageType.Blunt);
|
||||
serializer.DataField(this, x => MinimumSpeed, "minimumSpeed", 20f);
|
||||
serializer.DataField(this, x => BaseDamage, "baseDamage", 5);
|
||||
serializer.DataField(this, x => Factor, "factor", 1f);
|
||||
@@ -51,7 +52,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
|
||||
public void CollideWith(IEntity collidedWith)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out ICollidableComponent collidable) || !Owner.TryGetComponent(out DamageableComponent damageable)) return;
|
||||
if (!Owner.TryGetComponent(out ICollidableComponent collidable) || !Owner.TryGetComponent(out IDamageableComponent damageable)) return;
|
||||
|
||||
var speed = collidable.LinearVelocity.Length;
|
||||
|
||||
@@ -70,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
if (Owner.TryGetComponent(out StunnableComponent stun) && _robustRandom.Prob(StunChance))
|
||||
stun.Stun(StunSeconds);
|
||||
|
||||
damageable.TakeDamage(Damage, damage, collidedWith, Owner);
|
||||
damageable.ChangeDamage(Damage, damage, false, collidedWith);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -9,7 +10,7 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
[RegisterComponent]
|
||||
class DamageOnToolInteractComponent : Component, IInteractUsing
|
||||
public class DamageOnToolInteractComponent : Component, IInteractUsing
|
||||
{
|
||||
public override string Name => "DamageOnToolInteract";
|
||||
|
||||
@@ -29,10 +30,10 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Owner.EnsureComponent<DamageableComponent>();
|
||||
Owner.EnsureComponent<DestructibleComponent>();
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
|
||||
{
|
||||
@@ -40,12 +41,12 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
if (tool.HasQuality(ToolQuality.Welding) && toolQuality == ToolQuality.Welding)
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent<WelderComponent>(out WelderComponent welder))
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent(out WelderComponent welder))
|
||||
{
|
||||
if (welder.WelderLit) return CallDamage(eventArgs, tool);
|
||||
}
|
||||
}
|
||||
break; //If the tool quality is welding and its not lit or its not actually a welder that can be lit then its pointless to continue.
|
||||
}
|
||||
}
|
||||
|
||||
if (tool.HasQuality(toolQuality)) return CallDamage(eventArgs, tool);
|
||||
}
|
||||
@@ -55,14 +56,17 @@ namespace Content.Server.GameObjects.Components.Damage
|
||||
|
||||
protected bool CallDamage(InteractUsingEventArgs eventArgs, ToolComponent tool)
|
||||
{
|
||||
if (eventArgs.Target.TryGetComponent<DamageableComponent>(out var damageable))
|
||||
if (eventArgs.Target.TryGetComponent<DestructibleComponent>(out var damageable))
|
||||
{
|
||||
if(tool.HasQuality(ToolQuality.Welding)) damageable.TakeDamage(DamageType.Heat, Damage, eventArgs.Using, eventArgs.User);
|
||||
else
|
||||
damageable.TakeDamage(DamageType.Brute, Damage, eventArgs.Using, eventArgs.User);
|
||||
damageable.ChangeDamage(tool.HasQuality(ToolQuality.Welding)
|
||||
? DamageType.Heat
|
||||
: DamageType.Blunt,
|
||||
Damage, false, eventArgs.User);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// Triggers an event when values rise above or drop below this threshold
|
||||
/// </summary>
|
||||
public struct DamageThreshold
|
||||
{
|
||||
public DamageType DamageType { get; }
|
||||
public int Value { get; }
|
||||
public ThresholdType ThresholdType { get; }
|
||||
|
||||
public DamageThreshold(DamageType damageType, int value, ThresholdType thresholdType)
|
||||
{
|
||||
DamageType = damageType;
|
||||
Value = value;
|
||||
ThresholdType = thresholdType;
|
||||
}
|
||||
|
||||
public override bool Equals(Object obj)
|
||||
{
|
||||
return obj is DamageThreshold threshold && this == threshold;
|
||||
}
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return DamageType.GetHashCode() ^ Value.GetHashCode();
|
||||
}
|
||||
public static bool operator ==(DamageThreshold x, DamageThreshold y)
|
||||
{
|
||||
return x.DamageType == y.DamageType && x.Value == y.Value;
|
||||
}
|
||||
public static bool operator !=(DamageThreshold x, DamageThreshold y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ThresholdType
|
||||
{
|
||||
None,
|
||||
Destruction,
|
||||
Death,
|
||||
Critical,
|
||||
HUDUpdate,
|
||||
Breakage,
|
||||
}
|
||||
|
||||
public class DamageThresholdPassedEventArgs : EventArgs
|
||||
{
|
||||
public DamageThreshold DamageThreshold { get; }
|
||||
public bool Passed { get; }
|
||||
public int ExcessDamage { get; }
|
||||
|
||||
public DamageThresholdPassedEventArgs(DamageThreshold threshold, bool passed, int excess)
|
||||
{
|
||||
DamageThreshold = threshold;
|
||||
Passed = passed;
|
||||
ExcessDamage = excess;
|
||||
}
|
||||
}
|
||||
|
||||
public class DamageEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of damage.
|
||||
/// </summary>
|
||||
public DamageType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Change in damage.
|
||||
/// </summary>
|
||||
public int Damage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The entity that damaged this one.
|
||||
/// Could be null.
|
||||
/// </summary>
|
||||
public IEntity Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The mob entity that damaged this one.
|
||||
/// Could be null.
|
||||
/// </summary>
|
||||
public IEntity SourceMob { get; }
|
||||
|
||||
public DamageEventArgs(DamageType type, int damage, IEntity source, IEntity sourceMob)
|
||||
{
|
||||
Type = type;
|
||||
Damage = damage;
|
||||
Source = source;
|
||||
SourceMob = sourceMob;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
//TODO: add support for component add/remove
|
||||
|
||||
/// <summary>
|
||||
/// A component that handles receiving damage and healing,
|
||||
/// as well as informing other components of it.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class DamageableComponent : SharedDamageableComponent, IDamageableComponent
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Damageable";
|
||||
|
||||
/// <summary>
|
||||
/// The resistance set of this object.
|
||||
/// Affects receiving damage of various types.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public ResistanceSet Resistances { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<DamageType, int> CurrentDamage => _currentDamage;
|
||||
private Dictionary<DamageType, int> _currentDamage = new Dictionary<DamageType, int>();
|
||||
|
||||
[ViewVariables]
|
||||
public Dictionary<DamageType, List<DamageThreshold>> Thresholds = new Dictionary<DamageType, List<DamageThreshold>>();
|
||||
|
||||
public event EventHandler<DamageThresholdPassedEventArgs> DamageThresholdPassed;
|
||||
public event EventHandler<DamageEventArgs> Damaged;
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new DamageComponentState(_currentDamage);
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(this, x => Resistances, "resistances", ResistanceSet.DefaultResistanceSet);
|
||||
}
|
||||
|
||||
public bool IsDead()
|
||||
{
|
||||
var currentDamage = _currentDamage[DamageType.Total];
|
||||
foreach (var threshold in Thresholds[DamageType.Total])
|
||||
{
|
||||
if (threshold.Value <= currentDamage)
|
||||
{
|
||||
if (threshold.ThresholdType != ThresholdType.Death) continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
InitializeDamageType(DamageType.Total);
|
||||
|
||||
foreach (var damagebehavior in Owner.GetAllComponents<IOnDamageBehavior>())
|
||||
{
|
||||
AddThresholdsFrom(damagebehavior);
|
||||
Damaged += damagebehavior.OnDamaged;
|
||||
}
|
||||
|
||||
RecalculateComponentThresholds();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void TakeDamage(DamageType damageType, int amount, IEntity source = null, IEntity sourceMob = null)
|
||||
{
|
||||
if (damageType == DamageType.Total)
|
||||
{
|
||||
foreach (DamageType e in Enum.GetValues(typeof(DamageType)))
|
||||
{
|
||||
if (e == damageType) continue;
|
||||
TakeDamage(e, amount, source, sourceMob);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
InitializeDamageType(damageType);
|
||||
|
||||
int oldValue = _currentDamage[damageType];
|
||||
int oldTotalValue = -1;
|
||||
|
||||
if (amount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
amount = Resistances.CalculateDamage(damageType, amount);
|
||||
_currentDamage[damageType] = Math.Max(0, _currentDamage[damageType] + amount);
|
||||
UpdateForDamageType(damageType, oldValue);
|
||||
|
||||
Damaged?.Invoke(this, new DamageEventArgs(damageType, amount, source, sourceMob));
|
||||
|
||||
if (Resistances.AppliesToTotal(damageType))
|
||||
{
|
||||
oldTotalValue = _currentDamage[DamageType.Total];
|
||||
_currentDamage[DamageType.Total] = Math.Max(0, _currentDamage[DamageType.Total] + amount);
|
||||
UpdateForDamageType(DamageType.Total, oldTotalValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void TakeHealing(DamageType damageType, int amount, IEntity source = null, IEntity sourceMob = null)
|
||||
{
|
||||
if (damageType == DamageType.Total)
|
||||
{
|
||||
throw new ArgumentException("Cannot heal for DamageType.Total");
|
||||
}
|
||||
TakeDamage(damageType, -amount, source, sourceMob);
|
||||
}
|
||||
|
||||
public void HealAllDamage()
|
||||
{
|
||||
var values = Enum.GetValues(typeof(DamageType)).Cast<DamageType>();
|
||||
foreach (var damageType in values)
|
||||
{
|
||||
if (CurrentDamage.ContainsKey(damageType) && damageType != DamageType.Total)
|
||||
{
|
||||
TakeHealing(damageType, CurrentDamage[damageType]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateForDamageType(DamageType damageType, int oldValue)
|
||||
{
|
||||
int change = _currentDamage[damageType] - oldValue;
|
||||
|
||||
if (change == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int changeSign = Math.Sign(change);
|
||||
|
||||
foreach (var threshold in Thresholds[damageType])
|
||||
{
|
||||
var value = threshold.Value;
|
||||
if (((value * changeSign) > (oldValue * changeSign)) && ((value * changeSign) <= (_currentDamage[damageType] * changeSign)))
|
||||
{
|
||||
var excessDamage = change - value;
|
||||
var typeOfDamage = damageType;
|
||||
if (change - value < 0)
|
||||
{
|
||||
excessDamage = 0;
|
||||
}
|
||||
var args = new DamageThresholdPassedEventArgs(threshold, (changeSign > 0), excessDamage);
|
||||
DamageThresholdPassed?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RecalculateComponentThresholds()
|
||||
{
|
||||
foreach (IOnDamageBehavior onDamageBehaviorComponent in Owner.GetAllComponents<IOnDamageBehavior>())
|
||||
{
|
||||
AddThresholdsFrom(onDamageBehaviorComponent);
|
||||
}
|
||||
}
|
||||
|
||||
void AddThresholdsFrom(IOnDamageBehavior onDamageBehavior)
|
||||
{
|
||||
if (onDamageBehavior == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(onDamageBehavior));
|
||||
}
|
||||
|
||||
List<DamageThreshold> thresholds = onDamageBehavior.GetAllDamageThresholds();
|
||||
|
||||
if (thresholds == null)
|
||||
return;
|
||||
|
||||
foreach (DamageThreshold threshold in thresholds)
|
||||
{
|
||||
if (!Thresholds[threshold.DamageType].Contains(threshold))
|
||||
{
|
||||
Thresholds[threshold.DamageType].Add(threshold);
|
||||
}
|
||||
}
|
||||
|
||||
DamageThresholdPassed += onDamageBehavior.OnDamageThresholdPassed;
|
||||
}
|
||||
|
||||
void InitializeDamageType(DamageType damageType)
|
||||
{
|
||||
if (!_currentDamage.ContainsKey(damageType))
|
||||
{
|
||||
_currentDamage.Add(damageType, 0);
|
||||
Thresholds.Add(damageType, new List<DamageThreshold>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,114 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// Deletes the entity once a certain damage threshold has been reached.
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and deletes it after taking enough damage.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class DestructibleComponent : Component, IOnDamageBehavior, IDestroyAct, IExAct
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public class DestructibleComponent : RuinableComponent, IDestroyAct
|
||||
{
|
||||
#pragma warning disable 649
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
|
||||
#pragma warning restore 649
|
||||
#pragma warning restore 649
|
||||
|
||||
protected ActSystem ActSystem;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Destructible";
|
||||
|
||||
/// <summary>
|
||||
/// Damage threshold calculated from the values
|
||||
/// given in the prototype declaration.
|
||||
/// Entity spawned upon destruction.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public DamageThreshold Threshold { get; private set; }
|
||||
public string SpawnOnDestroy { get; set; }
|
||||
|
||||
public DamageType damageType = DamageType.Total;
|
||||
public int damageValue = 0;
|
||||
public string spawnOnDestroy = "";
|
||||
public string destroySound = "";
|
||||
public bool destroyed = false;
|
||||
|
||||
ActSystem _actSystem;
|
||||
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SpawnOnDestroy) && eventArgs.IsSpawnWreck)
|
||||
{
|
||||
Owner.EntityManager.SpawnEntity(SpawnOnDestroy, Owner.Transform.GridPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref damageValue, "thresholdvalue", 100);
|
||||
serializer.DataField(ref damageType, "thresholdtype", DamageType.Total);
|
||||
serializer.DataField(ref spawnOnDestroy, "spawnondestroy", "");
|
||||
serializer.DataField(ref destroySound, "destroysound", "");
|
||||
serializer.DataField(this, d => d.SpawnOnDestroy, "spawnondestroy", string.Empty);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_actSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||
ActSystem = _entitySystemManager.GetEntitySystem<ActSystem>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
List<DamageThreshold> IOnDamageBehavior.GetAllDamageThresholds()
|
||||
{
|
||||
Threshold = new DamageThreshold(damageType, damageValue, ThresholdType.Destruction);
|
||||
return new List<DamageThreshold>() { Threshold };
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IOnDamageBehavior.OnDamageThresholdPassed(object obj, DamageThresholdPassedEventArgs e)
|
||||
protected override void DestructionBehavior()
|
||||
{
|
||||
if (e.Passed && e.DamageThreshold == Threshold && destroyed == false)
|
||||
if (!Owner.Deleted)
|
||||
{
|
||||
destroyed = true;
|
||||
var pos = Owner.Transform.GridPosition;
|
||||
_actSystem.HandleDestruction(Owner, true);
|
||||
if(destroySound != string.Empty)
|
||||
ActSystem.HandleDestruction(Owner,
|
||||
true); //This will call IDestroyAct.OnDestroy on this component (and all other components on this entity)
|
||||
if (DestroySound != string.Empty)
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(destroySound, pos);
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
var prob = IoCManager.Resolve<IRobustRandom>();
|
||||
switch (eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
_actSystem.HandleDestruction(Owner, false);
|
||||
break;
|
||||
case ExplosionSeverity.Heavy:
|
||||
var spawnWreckOnHeavy = prob.Prob(0.5f);
|
||||
_actSystem.HandleDestruction(Owner, spawnWreckOnHeavy);
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
if (prob.Prob(0.4f))
|
||||
_actSystem.HandleDestruction(Owner, true);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void IDestroyAct.OnDestroy(DestructionEventArgs eventArgs)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(spawnOnDestroy) && eventArgs.IsSpawnWreck)
|
||||
{
|
||||
Owner.EntityManager.SpawnEntity(spawnOnDestroy, Owner.Transform.GridPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// Resistance set used by damageable objects.
|
||||
/// For each damage type, has a coefficient, damage reduction and "included in total" value.
|
||||
/// </summary>
|
||||
public class ResistanceSet : IExposeData
|
||||
{
|
||||
public static ResistanceSet DefaultResistanceSet = new ResistanceSet();
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<DamageType, ResistanceSetSettings> _resistances = new Dictionary<DamageType, ResistanceSetSettings>();
|
||||
|
||||
public ResistanceSet()
|
||||
{
|
||||
foreach (DamageType damageType in Enum.GetValues(typeof(DamageType)))
|
||||
{
|
||||
_resistances[damageType] = new ResistanceSetSettings();
|
||||
}
|
||||
}
|
||||
|
||||
public void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
foreach (DamageType damageType in Enum.GetValues(typeof(DamageType)))
|
||||
{
|
||||
var resistanceName = damageType.ToString().ToLower();
|
||||
serializer.DataReadFunction(resistanceName, new ResistanceSetSettings(), resistanceSetting =>
|
||||
{
|
||||
_resistances[damageType] = resistanceSetting;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts input damage with the resistance set values.
|
||||
/// </summary>
|
||||
/// <param name="damageType">Type of the damage.</param>
|
||||
/// <param name="amount">Incoming amount of the damage.</param>
|
||||
/// <returns>Damage adjusted by the resistance set.</returns>
|
||||
public int CalculateDamage(DamageType damageType, int amount)
|
||||
{
|
||||
if (amount > 0) //if it's damage, reduction applies
|
||||
{
|
||||
amount -= _resistances[damageType].DamageReduction;
|
||||
|
||||
if (amount <= 0)
|
||||
return 0;
|
||||
}
|
||||
|
||||
amount = (int)Math.Floor(amount * _resistances[damageType].Coefficient);
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
public bool AppliesToTotal(DamageType damageType)
|
||||
{
|
||||
//Damage that goes straight to total (for whatever reason) never applies twice
|
||||
|
||||
return damageType != DamageType.Total && _resistances[damageType].AppliesToTotal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Settings for a specific damage type in a resistance set.
|
||||
/// </summary>
|
||||
public class ResistanceSetSettings : IExposeData
|
||||
{
|
||||
public float Coefficient { get; private set; } = 1;
|
||||
public int DamageReduction { get; private set; } = 0;
|
||||
public bool AppliesToTotal { get; private set; } = true;
|
||||
|
||||
public void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(this, x => Coefficient, "coefficient", 1);
|
||||
serializer.DataField(this, x => DamageReduction, "damageReduction", 0);
|
||||
serializer.DataField(this, x => AppliesToTotal, "appliesToTotal", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Damage
|
||||
{
|
||||
/// <summary>
|
||||
/// When attached to an <see cref="IEntity"/>, allows it to take damage and
|
||||
/// "ruins" or "destroys" it after enough damage is taken.
|
||||
/// </summary>
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
public abstract class RuinableComponent : DamageableComponent
|
||||
{
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
/// <summary>
|
||||
/// How much HP this component can sustain before triggering
|
||||
/// <see cref="PerformDestruction"/>.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int MaxHp { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sound played upon destruction.
|
||||
/// </summary>
|
||||
protected string DestroySound { get; private set; }
|
||||
|
||||
public override List<DamageState> SupportedDamageStates =>
|
||||
new List<DamageState> {DamageState.Alive, DamageState.Dead};
|
||||
|
||||
public override DamageState CurrentDamageState => _currentDamageState;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
HealthChangedEvent += OnHealthChanged;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, ruinable => ruinable.MaxHp, "maxHP", 100);
|
||||
serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty);
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
HealthChangedEvent -= OnHealthChanged;
|
||||
}
|
||||
|
||||
private void OnHealthChanged(HealthChangedEventArgs e)
|
||||
{
|
||||
if (CurrentDamageState != DamageState.Dead && TotalDamage >= MaxHp)
|
||||
{
|
||||
PerformDestruction();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the Owner <see cref="IEntity"/>, setting
|
||||
/// <see cref="IDamageableComponent.CurrentDamageState"/> to
|
||||
/// <see cref="DamageState.Dead"/>
|
||||
/// </summary>
|
||||
protected void PerformDestruction()
|
||||
{
|
||||
_currentDamageState = DamageState.Dead;
|
||||
|
||||
if (!Owner.Deleted && DestroySound != string.Empty)
|
||||
{
|
||||
var pos = Owner.Transform.GridPosition;
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(DestroySound, pos);
|
||||
}
|
||||
|
||||
DestructionBehavior();
|
||||
}
|
||||
|
||||
protected abstract void DestructionBehavior();
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IDisposalTubeComponent tube))
|
||||
if (!entity.TryGetComponent(out IDisposalTubeComponent? tube))
|
||||
{
|
||||
shell.SendText(player, Loc.GetString("Entity with uid {0} doesn't have a {1} component", id, nameof(IDisposalTubeComponent)));
|
||||
return;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -41,6 +43,12 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
[ViewVariables]
|
||||
public IDisposalTubeComponent? NextTube { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of tags attached to the content, used for sorting
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public HashSet<string> Tags { get; set; } = new HashSet<string>();
|
||||
|
||||
private bool CanInsert(IEntity entity)
|
||||
{
|
||||
if (!_contents.CanInsert(entity))
|
||||
@@ -48,8 +56,14 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out ICollidableComponent? collidable) ||
|
||||
!collidable.CanCollide)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return entity.HasComponent<ItemComponent>() ||
|
||||
entity.HasComponent<SpeciesComponent>();
|
||||
entity.HasComponent<IBodyManagerComponent>();
|
||||
}
|
||||
|
||||
public bool TryInsert(IEntity entity)
|
||||
@@ -59,6 +73,11 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ICollidableComponent? collidable))
|
||||
{
|
||||
collidable.CanCollide = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -86,6 +105,11 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
foreach (var entity in _contents.ContainedEntities.ToArray())
|
||||
{
|
||||
if (entity.TryGetComponent(out ICollidableComponent? collidable))
|
||||
{
|
||||
collidable.CanCollide = true;
|
||||
}
|
||||
|
||||
_contents.ForceRemove(entity);
|
||||
|
||||
if (entity.Transform.Parent == Owner.Transform)
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Disposal
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
[ComponentReference(typeof(IDisposalTubeComponent))]
|
||||
public class DisposalRouterComponent : DisposalJunctionComponent, IActivate
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager;
|
||||
#pragma warning restore 649
|
||||
public override string Name => "DisposalRouter";
|
||||
|
||||
[ViewVariables]
|
||||
private BoundUserInterface _userInterface;
|
||||
|
||||
[ViewVariables]
|
||||
private HashSet<string> _tags;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Anchored =>
|
||||
!Owner.TryGetComponent(out CollidableComponent collidable) ||
|
||||
collidable.Anchored;
|
||||
|
||||
public override Direction NextDirection(DisposalHolderComponent holder)
|
||||
{
|
||||
var directions = ConnectableDirections();
|
||||
|
||||
if (holder.Tags.Overlaps(_tags))
|
||||
{
|
||||
return directions[1];
|
||||
}
|
||||
|
||||
return Owner.Transform.LocalRotation.GetDir();
|
||||
}
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||
.GetBoundUserInterface(DisposalRouterUiKey.Key);
|
||||
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
|
||||
|
||||
_tags = new HashSet<string>();
|
||||
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles ui messages from the client. For things such as button presses
|
||||
/// which interact with the world and require server action.
|
||||
/// </summary>
|
||||
/// <param name="obj">A user interface message from the client.</param>
|
||||
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
var msg = (UiActionMessage) obj.Message;
|
||||
|
||||
if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity))
|
||||
return;
|
||||
|
||||
//Check for correct message and ignore maleformed strings
|
||||
if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tags))
|
||||
{
|
||||
_tags.Clear();
|
||||
foreach (var tag in msg.Tags.Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
_tags.Add(tag.Trim());
|
||||
ClickSound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the player entity is able to use the configuration interface of the pipe tagger.
|
||||
/// </summary>
|
||||
/// <param name="playerEntity">The player entity.</param>
|
||||
/// <returns>Returns true if the entity can use the configuration interface, and false if it cannot.</returns>
|
||||
private bool PlayerCanUseDisposalTagger(IEntity playerEntity)
|
||||
{
|
||||
//Need player entity to check if they are still able to use the configuration interface
|
||||
if (playerEntity == null)
|
||||
return false;
|
||||
if (!Anchored)
|
||||
return false;
|
||||
//Check if player can interact in their current state
|
||||
if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets component data to be used to update the user interface client-side.
|
||||
/// </summary>
|
||||
/// <returns>Returns a <see cref="SharedDisposalRouterComponent.DisposalRouterBoundUserInterfaceState"/></returns>
|
||||
private DisposalRouterUserInterfaceState GetUserInterfaceState()
|
||||
{
|
||||
if(_tags == null || _tags.Count <= 0)
|
||||
{
|
||||
return new DisposalRouterUserInterfaceState("");
|
||||
}
|
||||
|
||||
var taglist = new System.Text.StringBuilder();
|
||||
|
||||
foreach (var tag in _tags)
|
||||
{
|
||||
taglist.Append(tag);
|
||||
taglist.Append(", ");
|
||||
}
|
||||
|
||||
taglist.Remove(taglist.Length - 2, 2);
|
||||
|
||||
return new DisposalRouterUserInterfaceState(taglist.ToString());
|
||||
}
|
||||
|
||||
private void UpdateUserInterface()
|
||||
{
|
||||
var state = GetUserInterfaceState();
|
||||
_userInterface.SetState(state);
|
||||
}
|
||||
|
||||
private void ClickSound()
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when you click the owner entity with an empty hand. Opens the UI client-side if possible.
|
||||
/// </summary>
|
||||
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
|
||||
void IActivate.Activate(ActivateEventArgs args)
|
||||
{
|
||||
if (!args.User.TryGetComponent(out IActorComponent actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.User.TryGetComponent(out IHandsComponent hands))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have no hands."));
|
||||
return;
|
||||
}
|
||||
|
||||
var activeHandEntity = hands.GetActiveHand?.Owner;
|
||||
if (activeHandEntity == null)
|
||||
{
|
||||
UpdateUserInterface();
|
||||
_userInterface.Open(actor.playerSession);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
_userInterface.CloseAll();
|
||||
base.OnRemove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalTaggerComponent;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Disposal
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IActivate))]
|
||||
[ComponentReference(typeof(IDisposalTubeComponent))]
|
||||
public class DisposalTaggerComponent : DisposalTransitComponent, IActivate
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager;
|
||||
#pragma warning restore 649
|
||||
public override string Name => "DisposalTagger";
|
||||
|
||||
[ViewVariables]
|
||||
private BoundUserInterface _userInterface;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private string _tag = "";
|
||||
|
||||
[ViewVariables]
|
||||
public bool Anchored =>
|
||||
!Owner.TryGetComponent(out CollidableComponent collidable) ||
|
||||
collidable.Anchored;
|
||||
|
||||
public override Direction NextDirection(DisposalHolderComponent holder)
|
||||
{
|
||||
holder.Tags.Add(_tag);
|
||||
return base.NextDirection(holder);
|
||||
}
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||
.GetBoundUserInterface(DisposalTaggerUiKey.Key);
|
||||
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
|
||||
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles ui messages from the client. For things such as button presses
|
||||
/// which interact with the world and require server action.
|
||||
/// </summary>
|
||||
/// <param name="obj">A user interface message from the client.</param>
|
||||
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
var msg = (UiActionMessage) obj.Message;
|
||||
|
||||
if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity))
|
||||
return;
|
||||
|
||||
//Check for correct message and ignore maleformed strings
|
||||
if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tag))
|
||||
{
|
||||
_tag = msg.Tag;
|
||||
ClickSound();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the player entity is able to use the configuration interface of the pipe tagger.
|
||||
/// </summary>
|
||||
/// <param name="playerEntity">The player entity.</param>
|
||||
/// <returns>Returns true if the entity can use the configuration interface, and false if it cannot.</returns>
|
||||
private bool PlayerCanUseDisposalTagger(IEntity playerEntity)
|
||||
{
|
||||
//Need player entity to check if they are still able to use the configuration interface
|
||||
if (playerEntity == null)
|
||||
return false;
|
||||
if (!Anchored)
|
||||
return false;
|
||||
//Check if player can interact in their current state
|
||||
if (!ActionBlockerSystem.CanInteract(playerEntity) || !ActionBlockerSystem.CanUse(playerEntity))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets component data to be used to update the user interface client-side.
|
||||
/// </summary>
|
||||
/// <returns>Returns a <see cref="SharedDisposalTaggerComponent.DisposalTaggerBoundUserInterfaceState"/></returns>
|
||||
private DisposalTaggerUserInterfaceState GetUserInterfaceState()
|
||||
{
|
||||
return new DisposalTaggerUserInterfaceState(_tag);
|
||||
}
|
||||
|
||||
private void UpdateUserInterface()
|
||||
{
|
||||
var state = GetUserInterfaceState();
|
||||
_userInterface.SetState(state);
|
||||
}
|
||||
|
||||
private void ClickSound()
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/machine_switch.ogg", Owner, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when you click the owner entity with an empty hand. Opens the UI client-side if possible.
|
||||
/// </summary>
|
||||
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
|
||||
void IActivate.Activate(ActivateEventArgs args)
|
||||
{
|
||||
if (!args.User.TryGetComponent(out IActorComponent actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.User.TryGetComponent(out IHandsComponent hands))
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
|
||||
Loc.GetString("You have no hands."));
|
||||
return;
|
||||
}
|
||||
|
||||
var activeHandEntity = hands.GetActiveHand?.Owner;
|
||||
if (activeHandEntity == null)
|
||||
{
|
||||
UpdateUserInterface();
|
||||
_userInterface.Open(actor.playerSession);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
_userInterface.CloseAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Disposal;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -44,7 +44,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
[ViewVariables]
|
||||
private bool Anchored =>
|
||||
!Owner.TryGetComponent(out CollidableComponent collidable) ||
|
||||
!Owner.TryGetComponent(out CollidableComponent? collidable) ||
|
||||
collidable.Anchored;
|
||||
|
||||
/// <summary>
|
||||
@@ -71,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
var snapGrid = Owner.GetComponent<SnapGridComponent>();
|
||||
var tube = snapGrid
|
||||
.GetInDir(nextDirection)
|
||||
.Select(x => x.TryGetComponent(out IDisposalTubeComponent c) ? c : null)
|
||||
.Select(x => x.TryGetComponent(out IDisposalTubeComponent? c) ? c : null)
|
||||
.FirstOrDefault(x => x != null && x != this);
|
||||
|
||||
if (tube == null)
|
||||
@@ -153,7 +153,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
foreach (var entity in Contents.ContainedEntities.ToArray())
|
||||
{
|
||||
if (!entity.TryGetComponent(out DisposalHolderComponent holder))
|
||||
if (!entity.TryGetComponent(out DisposalHolderComponent? holder))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -171,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
private void UpdateVisualState()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -187,7 +187,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
private void AnchoredChanged()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out CollidableComponent collidable))
|
||||
if (!Owner.TryGetComponent(out CollidableComponent? collidable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Disposal;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
@@ -85,12 +86,12 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
[ViewVariables]
|
||||
public bool Powered =>
|
||||
!Owner.TryGetComponent(out PowerReceiverComponent receiver) ||
|
||||
!Owner.TryGetComponent(out PowerReceiverComponent? receiver) ||
|
||||
receiver.Powered;
|
||||
|
||||
[ViewVariables]
|
||||
public bool Anchored =>
|
||||
!Owner.TryGetComponent(out CollidableComponent collidable) ||
|
||||
!Owner.TryGetComponent(out CollidableComponent? collidable) ||
|
||||
collidable.Anchored;
|
||||
|
||||
[ViewVariables]
|
||||
@@ -121,8 +122,14 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out ICollidableComponent? collidable) ||
|
||||
!collidable.CanCollide)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entity.HasComponent<ItemComponent>() &&
|
||||
!entity.HasComponent<SpeciesComponent>())
|
||||
!entity.HasComponent<IBodyManagerComponent>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -152,7 +159,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
{
|
||||
TryQueueEngage();
|
||||
|
||||
if (entity.TryGetComponent(out IActorComponent actor))
|
||||
if (entity.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
_userInterface.Close(actor.playerSession);
|
||||
}
|
||||
@@ -174,7 +181,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
private bool TryDrop(IEntity user, IEntity entity)
|
||||
{
|
||||
if (!user.TryGetComponent(out HandsComponent hands))
|
||||
if (!user.TryGetComponent(out HandsComponent? hands))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -266,7 +273,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
private void TogglePower()
|
||||
{
|
||||
if (!Owner.TryGetComponent(out PowerReceiverComponent receiver))
|
||||
if (!Owner.TryGetComponent(out PowerReceiverComponent? receiver))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -345,7 +352,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
private void UpdateVisualState(bool flush)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -481,7 +488,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
var collidable = Owner.EnsureComponent<CollidableComponent>();
|
||||
collidable.AnchoredChanged += UpdateVisualState;
|
||||
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent receiver))
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
|
||||
{
|
||||
receiver.OnPowerStateChanged += PowerStateChanged;
|
||||
}
|
||||
@@ -491,12 +498,12 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
if (Owner.TryGetComponent(out ICollidableComponent collidable))
|
||||
if (Owner.TryGetComponent(out ICollidableComponent? collidable))
|
||||
{
|
||||
collidable.AnchoredChanged -= UpdateVisualState;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent receiver))
|
||||
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
|
||||
{
|
||||
receiver.OnPowerStateChanged -= PowerStateChanged;
|
||||
}
|
||||
@@ -523,7 +530,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
switch (message)
|
||||
{
|
||||
case RelayMovementEntityMessage msg:
|
||||
if (!msg.Entity.TryGetComponent(out HandsComponent hands) ||
|
||||
if (!msg.Entity.TryGetComponent(out HandsComponent? hands) ||
|
||||
hands.Count == 0 ||
|
||||
_gameTiming.CurTime < _lastExitAttempt + ExitAttemptDelay)
|
||||
{
|
||||
@@ -552,7 +559,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -568,7 +575,7 @@ namespace Content.Server.GameObjects.Components.Disposal
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
return TryDrop(eventArgs.User, eventArgs.Using);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Content.Server.GameObjects.Components
|
||||
{
|
||||
connectedClient = null;
|
||||
|
||||
if (!Owner.TryGetComponent(out IActorComponent actorComponent))
|
||||
if (!Owner.TryGetComponent(out IActorComponent? actorComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.Components.VendingMachines;
|
||||
@@ -379,7 +380,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
return _powerReceiver.Powered;
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
|
||||
return false;
|
||||
@@ -397,22 +398,27 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
}
|
||||
}
|
||||
|
||||
if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Prying)) return false;
|
||||
|
||||
if (IsBolted())
|
||||
bool AirlockCheck()
|
||||
{
|
||||
var notify = IoCManager.Resolve<IServerNotifyManager>();
|
||||
notify.PopupMessage(Owner, eventArgs.User,
|
||||
Loc.GetString("The airlock's bolts prevent it from being forced!"));
|
||||
if (IsBolted())
|
||||
{
|
||||
var notify = IoCManager.Resolve<IServerNotifyManager>();
|
||||
notify.PopupMessage(Owner, eventArgs.User,
|
||||
Loc.GetString("The airlock's bolts prevent it from being forced!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsPowered())
|
||||
{
|
||||
var notify = IoCManager.Resolve<IServerNotifyManager>();
|
||||
notify.PopupMessage(Owner, eventArgs.User, Loc.GetString("The powered motors block your efforts!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsPowered())
|
||||
{
|
||||
var notify = IoCManager.Resolve<IServerNotifyManager>();
|
||||
notify.PopupMessage(Owner, eventArgs.User, Loc.GetString("The powered motors block your efforts!"));
|
||||
return true;
|
||||
}
|
||||
if (!await tool.UseTool(eventArgs.User, Owner, 3f, ToolQuality.Prying, AirlockCheck)) return false;
|
||||
|
||||
if (State == DoorState.Closed)
|
||||
Open();
|
||||
|
||||
@@ -3,9 +3,10 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.GameObjects.Components.Access;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Doors;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
@@ -58,8 +59,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
private const float DoorStunTime = 5f;
|
||||
protected bool Safety = true;
|
||||
|
||||
[ViewVariables]
|
||||
private bool _occludes;
|
||||
[ViewVariables] private bool _occludes;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
@@ -111,18 +111,25 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (entity.HasComponent(typeof(SpeciesComponent)))
|
||||
|
||||
// Disabled because it makes it suck hard to walk through double doors.
|
||||
|
||||
if (entity.HasComponent<IBodyManagerComponent>())
|
||||
{
|
||||
if (!entity.TryGetComponent<IMoverComponent>(out var mover)) return;
|
||||
|
||||
/*
|
||||
// TODO: temporary hack to fix the physics system raising collision events akwardly.
|
||||
// E.g. when moving parallel to a door by going off the side of a wall.
|
||||
var (walking, sprinting) = mover.VelocityDir;
|
||||
// Also TODO: walking and sprint dir are added together here
|
||||
// instead of calculating their contribution correctly.
|
||||
var dotProduct = Vector2.Dot((sprinting + walking).Normalized, (entity.Transform.WorldPosition - Owner.Transform.WorldPosition).Normalized);
|
||||
if (dotProduct <= -0.9f)
|
||||
if (dotProduct <= -0.85f)
|
||||
TryOpen(entity);
|
||||
*/
|
||||
|
||||
TryOpen(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +151,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return accessReader.IsAllowed(user);
|
||||
}
|
||||
|
||||
@@ -204,6 +212,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return accessReader.IsAllowed(user);
|
||||
}
|
||||
|
||||
@@ -214,6 +223,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
Deny();
|
||||
return;
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -228,7 +238,7 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
foreach (var e in collidesWith)
|
||||
{
|
||||
if (!e.TryGetComponent(out StunnableComponent stun)
|
||||
|| !e.TryGetComponent(out DamageableComponent damage)
|
||||
|| !e.TryGetComponent(out IDamageableComponent damage)
|
||||
|| !e.TryGetComponent(out ICollidableComponent otherBody)
|
||||
|| !Owner.TryGetComponent(out ICollidableComponent body))
|
||||
continue;
|
||||
@@ -238,10 +248,11 @@ namespace Content.Server.GameObjects.Components.Doors
|
||||
if (percentage < 0.1f)
|
||||
continue;
|
||||
|
||||
damage.TakeDamage(DamageType.Brute, DoorCrushDamage);
|
||||
damage.ChangeDamage(DamageType.Blunt, DoorCrushDamage, false, Owner);
|
||||
stun.Paralyze(DoorStunTime);
|
||||
hitSomeone = true;
|
||||
}
|
||||
|
||||
// If we hit someone, open up after stun (opens right when stun ends)
|
||||
if (hitSomeone)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Explosions;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Weapon;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -70,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.Using.TryGetComponent(out MopComponent mopComponent))
|
||||
{
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
|
||||
foreach (var spillEntity in entityManager.GetEntitiesAt(spillTileMapGrid.ParentMapId, spillGridCoords.Position))
|
||||
{
|
||||
if (!spillEntity.TryGetComponent(out PuddleComponent puddleComponent))
|
||||
if (!spillEntity.TryGetComponent(out PuddleComponent? puddleComponent))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -8,17 +8,19 @@ using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.EntitySystems.Click;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Interaction;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Health.BodySystem;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Physics.Pull;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
using Robust.Server.GameObjects.EntitySystemMessages;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Components.Transform;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -140,11 +142,11 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
}
|
||||
}
|
||||
|
||||
public bool PutInHand(ItemComponent item)
|
||||
public bool PutInHand(ItemComponent item, bool mobCheck = true)
|
||||
{
|
||||
foreach (var hand in ActivePriorityEnumerable())
|
||||
{
|
||||
if (PutInHand(item, hand, false))
|
||||
if (PutInHand(item, hand, false, mobCheck))
|
||||
{
|
||||
OnItemChanged?.Invoke();
|
||||
|
||||
@@ -155,10 +157,10 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool PutInHand(ItemComponent item, string index, bool fallback = true)
|
||||
public bool PutInHand(ItemComponent item, string index, bool fallback = true, bool mobChecks = true)
|
||||
{
|
||||
var hand = GetHand(index);
|
||||
if (!CanPutInHand(item, index) || hand == null)
|
||||
if (!CanPutInHand(item, index, mobChecks) || hand == null)
|
||||
{
|
||||
return fallback && PutInHand(item);
|
||||
}
|
||||
@@ -176,19 +178,23 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return success;
|
||||
}
|
||||
|
||||
public void PutInHandOrDrop(ItemComponent item)
|
||||
public void PutInHandOrDrop(ItemComponent item, bool mobCheck = true)
|
||||
{
|
||||
if (!PutInHand(item))
|
||||
if (!PutInHand(item, mobCheck))
|
||||
{
|
||||
item.Owner.Transform.GridPosition = Owner.Transform.GridPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanPutInHand(ItemComponent item)
|
||||
public bool CanPutInHand(ItemComponent item, bool mobCheck = true)
|
||||
{
|
||||
if (mobCheck && !ActionBlockerSystem.CanPickup(Owner))
|
||||
return false;
|
||||
|
||||
foreach (var handName in ActivePriorityEnumerable())
|
||||
{
|
||||
if (CanPutInHand(item, handName))
|
||||
// We already did a mobCheck, so let's not waste cycles.
|
||||
if (CanPutInHand(item, handName, false))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -197,8 +203,11 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanPutInHand(ItemComponent item, string index)
|
||||
public bool CanPutInHand(ItemComponent item, string index, bool mobCheck = true)
|
||||
{
|
||||
if (mobCheck && !ActionBlockerSystem.CanPickup(Owner))
|
||||
return false;
|
||||
|
||||
return GetHand(index)?.Container.CanInsert(item.Owner) == true;
|
||||
}
|
||||
|
||||
@@ -284,17 +293,17 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return Drop(slot, coords, doMobChecks);
|
||||
}
|
||||
|
||||
public bool Drop(string slot, bool doMobChecks = true)
|
||||
public bool Drop(string slot, bool mobChecks = true)
|
||||
{
|
||||
var hand = GetHand(slot);
|
||||
if (!CanDrop(slot) || hand?.Entity == null)
|
||||
if (!CanDrop(slot, mobChecks) || hand?.Entity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var item = hand.Entity.GetComponent<ItemComponent>();
|
||||
|
||||
if (!DroppedInteraction(item, doMobChecks))
|
||||
if (!DroppedInteraction(item, mobChecks))
|
||||
return false;
|
||||
|
||||
if (!hand.Container.Remove(hand.Entity))
|
||||
@@ -321,7 +330,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Drop(IEntity entity, bool doMobChecks = true)
|
||||
public bool Drop(IEntity entity, bool mobChecks = true)
|
||||
{
|
||||
if (entity == null)
|
||||
{
|
||||
@@ -333,7 +342,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
throw new ArgumentException("Entity must be held in one of our hands.", nameof(entity));
|
||||
}
|
||||
|
||||
return Drop(slot, doMobChecks);
|
||||
return Drop(slot, mobChecks);
|
||||
}
|
||||
|
||||
public bool Drop(string slot, BaseContainer targetContainer, bool doMobChecks = true)
|
||||
@@ -409,13 +418,15 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
/// <returns>
|
||||
/// True if there is an item in the slot and it can be dropped, false otherwise.
|
||||
/// </returns>
|
||||
public bool CanDrop(string name)
|
||||
public bool CanDrop(string name, bool mobCheck = true)
|
||||
{
|
||||
var hand = GetHand(name);
|
||||
if (hand?.Entity == null)
|
||||
{
|
||||
|
||||
if (mobCheck && !ActionBlockerSystem.CanDrop(Owner))
|
||||
return false;
|
||||
|
||||
if (hand?.Entity == null)
|
||||
return false;
|
||||
}
|
||||
|
||||
return hand.Container.CanRemove(hand.Entity);
|
||||
}
|
||||
@@ -537,15 +548,9 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return;
|
||||
}
|
||||
|
||||
var isOwnerContained = ContainerHelpers.TryGetContainer(Owner, out var ownerContainer);
|
||||
var isPullableContained = ContainerHelpers.TryGetContainer(pullable.Owner, out var pullableContainer);
|
||||
|
||||
if (isOwnerContained || isPullableContained)
|
||||
if (!Owner.IsInSameOrNoContainer(pullable.Owner))
|
||||
{
|
||||
if (ownerContainer != pullableContainer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsPulling)
|
||||
@@ -554,10 +559,8 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
}
|
||||
|
||||
PulledObject = pullable.Owner.GetComponent<ICollidableComponent>();
|
||||
var controller = PulledObject!.EnsureController<PullController>();
|
||||
controller!.StartPull(Owner.GetComponent<ICollidableComponent>());
|
||||
|
||||
AddPullingStatuses();
|
||||
var controller = PulledObject.EnsureController<PullController>();
|
||||
controller.StartPull(Owner.GetComponent<ICollidableComponent>());
|
||||
}
|
||||
|
||||
public void MovePulledObject(GridCoordinates puller, GridCoordinates to)
|
||||
@@ -569,6 +572,46 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveEvent(MoveEvent moveEvent)
|
||||
{
|
||||
if (moveEvent.Sender != Owner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsPulling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PulledObject!.WakeBody();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
if (!(message is PullMessage pullMessage) ||
|
||||
pullMessage.Puller.Owner != Owner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case PullStartedMessage msg:
|
||||
Owner.EntityManager.EventBus.SubscribeEvent<MoveEvent>(EventSource.Local, this, MoveEvent);
|
||||
|
||||
AddPullingStatuses(msg.Pulled.Owner);
|
||||
break;
|
||||
case PullStoppedMessage msg:
|
||||
Owner.EntityManager.EventBus.UnsubscribeEvent<MoveEvent>(EventSource.Local, this);
|
||||
|
||||
RemovePullingStatuses(msg.Pulled.Owner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
|
||||
{
|
||||
base.HandleNetworkMessage(message, channel, session);
|
||||
@@ -668,7 +711,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
|
||||
Dirty();
|
||||
|
||||
if (!message.Entity.TryGetComponent(out ICollidableComponent collidable))
|
||||
if (!message.Entity.TryGetComponent(out ICollidableComponent? collidable))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -679,42 +722,34 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPullingStatuses()
|
||||
private void AddPullingStatuses(IEntity pulled)
|
||||
{
|
||||
if (PulledObject?.Owner != null &&
|
||||
PulledObject.Owner.TryGetComponent(out ServerStatusEffectsComponent pulledStatus))
|
||||
if (pulled.TryGetComponent(out ServerStatusEffectsComponent? pulledStatus))
|
||||
{
|
||||
pulledStatus.ChangeStatusEffectIcon(StatusEffect.Pulled,
|
||||
"/Textures/Interface/StatusEffects/Pull/pulled.png");
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent ownerStatus))
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? ownerStatus))
|
||||
{
|
||||
ownerStatus.ChangeStatusEffectIcon(StatusEffect.Pulling,
|
||||
"/Textures/Interface/StatusEffects/Pull/pulling.png");
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePullingStatuses()
|
||||
private void RemovePullingStatuses(IEntity pulled)
|
||||
{
|
||||
if (PulledObject?.Owner != null &&
|
||||
PulledObject.Owner.TryGetComponent(out ServerStatusEffectsComponent pulledStatus))
|
||||
if (pulled.TryGetComponent(out ServerStatusEffectsComponent? pulledStatus))
|
||||
{
|
||||
pulledStatus.RemoveStatusEffect(StatusEffect.Pulled);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent ownerStatus))
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? ownerStatus))
|
||||
{
|
||||
ownerStatus.RemoveStatusEffect(StatusEffect.Pulling);
|
||||
}
|
||||
}
|
||||
|
||||
public override void StopPull()
|
||||
{
|
||||
RemovePullingStatuses();
|
||||
base.StopPull();
|
||||
}
|
||||
|
||||
void IBodyPartAdded.BodyPartAdded(BodyPartAddedEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Part.PartType != BodyPartType.Hand)
|
||||
|
||||
@@ -173,9 +173,10 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
/// </remarks>
|
||||
/// <param name="slot">The slot to put the item in.</param>
|
||||
/// <param name="item">The item to insert into the slot.</param>
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
/// <param name="reason">The translated reason why the item cannot be equipped, if this function returns false. Can be null.</param>
|
||||
/// <returns>True if the item was successfully inserted, false otherwise.</returns>
|
||||
public bool Equip(Slots slot, ItemComponent item, out string reason)
|
||||
public bool Equip(Slots slot, ItemComponent item, bool mobCheck, out string reason)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
@@ -183,7 +184,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
"Clothing must be passed here. To remove some clothing from a slot, use Unequip()");
|
||||
}
|
||||
|
||||
if (!CanEquip(slot, item, out reason))
|
||||
if (!CanEquip(slot, item, mobCheck, out reason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -203,9 +204,9 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Equip(Slots slot, ItemComponent item) => Equip(slot, item, out var _);
|
||||
public bool Equip(Slots slot, ItemComponent item, bool mobCheck = true) => Equip(slot, item, mobCheck, out var _);
|
||||
|
||||
public bool Equip(Slots slot, IEntity entity) => Equip(slot, entity.GetComponent<ItemComponent>());
|
||||
public bool Equip(Slots slot, IEntity entity, bool mobCheck = true) => Equip(slot, entity.GetComponent<ItemComponent>(), mobCheck);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether an item can be put in the specified slot.
|
||||
@@ -214,12 +215,12 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
/// <param name="item">The item to check for.</param>
|
||||
/// <param name="reason">The translated reason why the item cannot be equiped, if this function returns false. Can be null.</param>
|
||||
/// <returns>True if the item can be inserted into the specified slot.</returns>
|
||||
public bool CanEquip(Slots slot, ItemComponent item, out string reason)
|
||||
public bool CanEquip(Slots slot, ItemComponent item, bool mobCheck, out string reason)
|
||||
{
|
||||
var pass = false;
|
||||
reason = null;
|
||||
|
||||
if (!ActionBlockerSystem.CanEquip(Owner))
|
||||
if (mobCheck && !ActionBlockerSystem.CanEquip(Owner))
|
||||
return false;
|
||||
|
||||
if (item is ClothingComponent clothing)
|
||||
@@ -248,18 +249,19 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return pass && _slotContainers[slot].CanInsert(item.Owner);
|
||||
}
|
||||
|
||||
public bool CanEquip(Slots slot, ItemComponent item) => CanEquip(slot, item, out var _);
|
||||
public bool CanEquip(Slots slot, ItemComponent item, bool mobCheck = true) => CanEquip(slot, item, mobCheck, out var _);
|
||||
|
||||
public bool CanEquip(Slots slot, IEntity entity) => CanEquip(slot, entity.GetComponent<ItemComponent>());
|
||||
public bool CanEquip(Slots slot, IEntity entity, bool mobCheck = true) => CanEquip(slot, entity.GetComponent<ItemComponent>(), mobCheck);
|
||||
|
||||
/// <summary>
|
||||
/// Drops the item in a slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to drop the item from.</param>
|
||||
/// <returns>True if an item was dropped, false otherwise.</returns>
|
||||
public bool Unequip(Slots slot)
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
public bool Unequip(Slots slot, bool mobCheck = true)
|
||||
{
|
||||
if (!CanUnequip(slot))
|
||||
if (!CanUnequip(slot, mobCheck))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -288,16 +290,17 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
/// Checks whether an item can be dropped from the specified slot.
|
||||
/// </summary>
|
||||
/// <param name="slot">The slot to check for.</param>
|
||||
/// <param name="mobCheck">Whether to perform an ActionBlocker check to the entity.</param>
|
||||
/// <returns>
|
||||
/// True if there is an item in the slot and it can be dropped, false otherwise.
|
||||
/// </returns>
|
||||
public bool CanUnequip(Slots slot)
|
||||
public bool CanUnequip(Slots slot, bool mobCheck = true)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanUnequip(Owner))
|
||||
if (mobCheck && !ActionBlockerSystem.CanUnequip(Owner))
|
||||
return false;
|
||||
|
||||
var InventorySlot = _slotContainers[slot];
|
||||
return InventorySlot.ContainedEntity != null && InventorySlot.CanRemove(InventorySlot.ContainedEntity);
|
||||
var inventorySlot = _slotContainers[slot];
|
||||
return inventorySlot.ContainedEntity != null && inventorySlot.CanRemove(inventorySlot.ContainedEntity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -398,7 +401,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
if (activeHand != null && activeHand.Owner.TryGetComponent(out ItemComponent clothing))
|
||||
{
|
||||
hands.Drop(hands.ActiveHand);
|
||||
if (!Equip(msg.Inventoryslot, clothing, out var reason))
|
||||
if (!Equip(msg.Inventoryslot, clothing, true, out var reason))
|
||||
{
|
||||
hands.PutInHand(clothing);
|
||||
|
||||
@@ -434,7 +437,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
var activeHand = hands.GetActiveHand;
|
||||
if (activeHand != null && GetSlotItem(msg.Inventoryslot) == null)
|
||||
{
|
||||
var canEquip = CanEquip(msg.Inventoryslot, activeHand, out var reason);
|
||||
var canEquip = CanEquip(msg.Inventoryslot, activeHand, true, out var reason);
|
||||
_hoverEntity = new KeyValuePair<Slots, (EntityUid entity, bool fits)>(msg.Inventoryslot, (activeHand.Owner.Uid, canEquip));
|
||||
|
||||
Dirty();
|
||||
|
||||
@@ -137,7 +137,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!inventory.CanEquip(slot, item))
|
||||
if (!inventory.CanEquip(slot, item, false))
|
||||
{
|
||||
_notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot equip that there!", Owner));
|
||||
return false;
|
||||
@@ -162,7 +162,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
if (result != DoAfterStatus.Finished) return;
|
||||
|
||||
userHands.Drop(item!.Owner, false);
|
||||
inventory.Equip(slot, item!.Owner);
|
||||
inventory.Equip(slot, item!.Owner, false);
|
||||
|
||||
UpdateSubscribed();
|
||||
}
|
||||
@@ -202,7 +202,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hands.CanPutInHand(item, hand))
|
||||
if (!hands.CanPutInHand(item, hand, false))
|
||||
{
|
||||
_notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot put that there!", Owner));
|
||||
return false;
|
||||
@@ -227,7 +227,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
if (result != DoAfterStatus.Finished) return;
|
||||
|
||||
userHands.Drop(hand, false);
|
||||
hands.PutInHand(item, hand, false);
|
||||
hands.PutInHand(item!, hand, false, false);
|
||||
UpdateSubscribed();
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!inventory.CanUnequip(slot))
|
||||
if (!inventory.CanUnequip(slot, false))
|
||||
{
|
||||
_notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot unequip that!", Owner));
|
||||
return false;
|
||||
@@ -277,7 +277,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
if (result != DoAfterStatus.Finished) return;
|
||||
|
||||
var item = inventory.GetSlotItem(slot);
|
||||
inventory.Unequip(slot);
|
||||
inventory.Unequip(slot, false);
|
||||
userHands.PutInHandOrDrop(item);
|
||||
UpdateSubscribed();
|
||||
}
|
||||
@@ -304,7 +304,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hands.CanDrop(hand))
|
||||
if (!hands.CanDrop(hand, false))
|
||||
{
|
||||
_notifyManager.PopupMessageCursor(user, Loc.GetString("{0:They} cannot drop that!", Owner));
|
||||
return false;
|
||||
@@ -329,7 +329,7 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
|
||||
var item = hands.GetItem(hand);
|
||||
hands.Drop(hand, false);
|
||||
userHands.PutInHandOrDrop(item);
|
||||
userHands.PutInHandOrDrop(item!);
|
||||
UpdateSubscribed();
|
||||
}
|
||||
|
||||
@@ -364,8 +364,6 @@ namespace Content.Server.GameObjects.Components.GUI
|
||||
else
|
||||
TakeItemFromHands(user, handMessage.Hand);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.GameObjects.Components.Gravity;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.Components.UserInterface;
|
||||
@@ -18,7 +20,7 @@ using Robust.Shared.Serialization;
|
||||
namespace Content.Server.GameObjects.Components.Gravity
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class GravityGeneratorComponent: SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand
|
||||
public class GravityGeneratorComponent : SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand
|
||||
{
|
||||
private BoundUserInterface _userInterface;
|
||||
|
||||
@@ -97,19 +99,17 @@ namespace Content.Server.GameObjects.Components.Gravity
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.Using.TryGetComponent(out WelderComponent tool))
|
||||
return false;
|
||||
|
||||
if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Welding, 5f))
|
||||
if (!await tool.UseTool(eventArgs.User, Owner, 2f, ToolQuality.Welding, 5f))
|
||||
return false;
|
||||
|
||||
// Repair generator
|
||||
var damageable = Owner.GetComponent<DamageableComponent>();
|
||||
var breakable = Owner.GetComponent<BreakableComponent>();
|
||||
damageable.HealAllDamage();
|
||||
breakable.broken = false;
|
||||
breakable.FixAllDamage();
|
||||
_intact = true;
|
||||
|
||||
var notifyManager = IoCManager.Resolve<IServerNotifyManager>();
|
||||
@@ -130,13 +130,16 @@ namespace Content.Server.GameObjects.Components.Gravity
|
||||
if (!Intact)
|
||||
{
|
||||
MakeBroken();
|
||||
} else if (!Powered)
|
||||
}
|
||||
else if (!Powered)
|
||||
{
|
||||
MakeUnpowered();
|
||||
} else if (!SwitchedOn)
|
||||
}
|
||||
else if (!SwitchedOn)
|
||||
{
|
||||
MakeOff();
|
||||
} else
|
||||
}
|
||||
else
|
||||
{
|
||||
MakeOn();
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Stack;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Healing
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class HealingComponent : Component, IAfterInteract, IUse
|
||||
{
|
||||
public override string Name => "Healing";
|
||||
|
||||
public int Heal = 100;
|
||||
public DamageType Damage = DamageType.Brute;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref Heal, "heal", 100);
|
||||
serializer.DataField(ref Damage, "damage", DamageType.Brute);
|
||||
}
|
||||
|
||||
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
|
||||
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!eventArgs.Target.TryGetComponent(out DamageableComponent damagecomponent)) return;
|
||||
if (Owner.TryGetComponent(out StackComponent stackComponent))
|
||||
{
|
||||
if (!stackComponent.Use(1))
|
||||
{
|
||||
Owner.Delete();
|
||||
return;
|
||||
}
|
||||
|
||||
damagecomponent.TakeHealing(Damage, Heal);
|
||||
return;
|
||||
}
|
||||
damagecomponent.TakeHealing(Damage, Heal);
|
||||
Owner.Delete();
|
||||
}
|
||||
|
||||
bool IUse.UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out DamageableComponent damagecomponent)) return false;
|
||||
if (Owner.TryGetComponent(out StackComponent stackComponent))
|
||||
{
|
||||
if (!stackComponent.Use(1))
|
||||
{
|
||||
Owner.Delete();
|
||||
return false;
|
||||
}
|
||||
|
||||
damagecomponent.TakeHealing(Damage, Heal);
|
||||
return false;
|
||||
}
|
||||
damagecomponent.TakeHealing(Damage, Heal);
|
||||
Owner.Delete();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Clothing;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Power;
|
||||
@@ -57,7 +58,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
[ViewVariables]
|
||||
public bool Activated { get; private set; }
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.Using.HasComponent<BatteryComponent>()) return false;
|
||||
|
||||
@@ -276,7 +277,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
return;
|
||||
}
|
||||
|
||||
var cell = Owner.EntityManager.SpawnEntity("PowerCellSmallHyper", Owner.Transform.GridPosition);
|
||||
var cell = Owner.EntityManager.SpawnEntity("PowerCellSmallStandard", Owner.Transform.GridPosition);
|
||||
_cellContainer.Insert(cell);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
serializer.DataField(ref _toolComponentNeeded, "toolComponentNeeded", true);
|
||||
}
|
||||
|
||||
public void TryPryTile(IEntity user, GridCoordinates clickLocation)
|
||||
public async void TryPryTile(IEntity user, GridCoordinates clickLocation)
|
||||
{
|
||||
if (!Owner.TryGetComponent<ToolComponent>(out var tool) && _toolComponentNeeded)
|
||||
return;
|
||||
@@ -51,7 +51,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
|
||||
if (!tileDef.CanCrowbar) return;
|
||||
|
||||
if (_toolComponentNeeded && !tool.UseTool(user, null, ToolQuality.Prying))
|
||||
if (_toolComponentNeeded && !await tool!.UseTool(user, null, 0f, ToolQuality.Prying))
|
||||
return;
|
||||
|
||||
var underplating = _tileDefinitionManager["underplating"];
|
||||
|
||||
@@ -106,7 +106,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (entity.TryGetComponent(out AnchorableComponent anchorable))
|
||||
if (entity.TryGetComponent(out AnchorableComponent? anchorable))
|
||||
{
|
||||
anchorable.TryAnchor(player.AttachedEntity, force: true);
|
||||
}
|
||||
@@ -151,7 +151,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (entity.TryGetComponent(out AnchorableComponent anchorable))
|
||||
if (entity.TryGetComponent(out AnchorableComponent? anchorable))
|
||||
{
|
||||
anchorable.TryUnAnchor(player.AttachedEntity, force: true);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
@@ -89,11 +91,31 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
serializer.DataField(this, collection => UseSoundCollection, "useSoundCollection", string.Empty);
|
||||
}
|
||||
|
||||
public virtual bool UseTool(IEntity user, IEntity target, ToolQuality toolQualityNeeded)
|
||||
public virtual async Task<bool> UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded, Func<bool> doAfterCheck = null)
|
||||
{
|
||||
if (!HasQuality(toolQualityNeeded) || !ActionBlockerSystem.CanInteract(user))
|
||||
return false;
|
||||
|
||||
if (doAfterDelay > 0f)
|
||||
{
|
||||
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
|
||||
|
||||
var doAfterArgs = new DoAfterEventArgs(user, doAfterDelay / SpeedModifier, default, target)
|
||||
{
|
||||
ExtraCheck = doAfterCheck,
|
||||
BreakOnDamage = false, // TODO: Change this to true once breathing is fixed.
|
||||
BreakOnStun = true,
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
NeedHand = true,
|
||||
};
|
||||
|
||||
var result = await doAfterSystem.DoAfter(doAfterArgs);
|
||||
|
||||
if (result == DoAfterStatus.Cancelled)
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayUseSound();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
@@ -97,16 +98,37 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
return new WelderComponentState(FuelCapacity, Fuel, WelderLit);
|
||||
}
|
||||
|
||||
public override bool UseTool(IEntity user, IEntity target, ToolQuality toolQualityNeeded)
|
||||
public override async Task<bool> UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded, Func<bool>? doAfterCheck = null)
|
||||
{
|
||||
var canUse = base.UseTool(user, target, toolQualityNeeded);
|
||||
bool ExtraCheck()
|
||||
{
|
||||
var extraCheck = doAfterCheck?.Invoke() ?? true;
|
||||
|
||||
if (!CanWeld(DefaultFuelCost))
|
||||
{
|
||||
_notifyManager.PopupMessage(target, 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 bool UseTool(IEntity user, IEntity target, ToolQuality toolQualityNeeded, float fuelConsumed)
|
||||
public async Task<bool> UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded, float fuelConsumed, Func<bool>? doAfterCheck = null)
|
||||
{
|
||||
return base.UseTool(user, target, toolQualityNeeded) && TryWeld(fuelConsumed, user);
|
||||
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)
|
||||
@@ -236,11 +258,12 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
if (TryWeld(5, victim, silent: true))
|
||||
{
|
||||
PlaySoundCollection(WeldSoundCollection);
|
||||
chat.EntityMe(victim, Loc.GetString("welds {0:their} every orifice closed! It looks like {0:theyre} trying to commit suicide!", victim)); //TODO: theyre macro
|
||||
chat.EntityMe(victim, Loc.GetString("welds {0:their} every orifice closed! It looks like {0:theyre} trying to commit suicide!", victim));
|
||||
return SuicideKind.Heat;
|
||||
}
|
||||
|
||||
chat.EntityMe(victim, Loc.GetString("bashes {0:themselves} with the {1}!", victim, Owner.Name));
|
||||
return SuicideKind.Brute;
|
||||
return SuicideKind.Blunt;
|
||||
}
|
||||
|
||||
public void SolutionChanged(SolutionChangeEventArgs eventArgs)
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace Content.Server.GameObjects.Components.Items.Clothing
|
||||
|
||||
private bool TryEquip(InventoryComponent inv, Slots slot, IEntity user)
|
||||
{
|
||||
if (!inv.Equip(slot, this, out var reason))
|
||||
if (!inv.Equip(slot, this, true, out var reason))
|
||||
{
|
||||
if (reason != null)
|
||||
_serverNotifyManager.PopupMessage(Owner, user, reason);
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Storage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
@@ -168,7 +171,8 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
continue;
|
||||
|
||||
// only items that can be stored in an inventory, or a mob, can be eaten by a locker
|
||||
if (!entity.HasComponent<StorableComponent>() && !entity.HasComponent<SpeciesComponent>())
|
||||
if (!entity.HasComponent<StorableComponent>() &&
|
||||
!entity.HasComponent<BodyManagerComponent>())
|
||||
continue;
|
||||
|
||||
if (!AddToContents(entity))
|
||||
@@ -356,7 +360,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
return Contents.CanInsert(entity);
|
||||
}
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
|
||||
if (Open)
|
||||
@@ -374,10 +378,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
if (!eventArgs.Using.TryGetComponent(out WelderComponent tool))
|
||||
return false;
|
||||
|
||||
if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Welding, 1f))
|
||||
if (!await tool.UseTool(eventArgs.User, Owner, 1f, ToolQuality.Welding, 1f))
|
||||
return false;
|
||||
|
||||
|
||||
IsWeldedShut ^= true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out CollidableComponent physics) &&
|
||||
if (Owner.TryGetComponent(out ICollidableComponent physics) &&
|
||||
physics.Anchored)
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
@@ -100,13 +101,13 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
{
|
||||
EnsureInitialCalculated();
|
||||
|
||||
if (entity.TryGetComponent(out ServerStorageComponent storage) &&
|
||||
if (entity.TryGetComponent(out ServerStorageComponent? storage) &&
|
||||
storage._storageCapacityMax >= _storageCapacityMax)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out StorableComponent store) &&
|
||||
if (entity.TryGetComponent(out StorableComponent? store) &&
|
||||
store.ObjectSize > _storageCapacityMax - _storageUsed)
|
||||
{
|
||||
return false;
|
||||
@@ -163,7 +164,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
|
||||
Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) had entity (UID {message.Entity.Uid}) removed from it.");
|
||||
|
||||
if (!message.Entity.TryGetComponent(out StorableComponent storable))
|
||||
if (!message.Entity.TryGetComponent(out StorableComponent? storable))
|
||||
{
|
||||
Logger.WarningS(LoggerName, $"Removed entity {message.Entity.Uid} without a StorableComponent from storage {Owner.Uid} at {Owner.Transform.MapPosition}");
|
||||
|
||||
@@ -185,7 +186,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
{
|
||||
EnsureInitialCalculated();
|
||||
|
||||
if (!player.TryGetComponent(out IHandsComponent hands) ||
|
||||
if (!player.TryGetComponent(out IHandsComponent? hands) ||
|
||||
hands.GetActiveHand == null)
|
||||
{
|
||||
return false;
|
||||
@@ -316,7 +317,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
|
||||
private void UpdateDoorState()
|
||||
{
|
||||
if (Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
appearance.SetData(StorageVisuals.Open, SubscribedSessions.Count != 0);
|
||||
}
|
||||
@@ -381,7 +382,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
|
||||
var item = entity.GetComponent<ItemComponent>();
|
||||
if (item == null ||
|
||||
!player.TryGetComponent(out HandsComponent hands))
|
||||
!player.TryGetComponent(out HandsComponent? hands))
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -435,7 +436,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <returns>true if inserted, false otherwise</returns>
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
Logger.DebugS(LoggerName, $"Storage (UID {Owner.Uid}) attacked by user (UID {eventArgs.User.Uid}) with entity (UID {eventArgs.Using.Uid}).");
|
||||
|
||||
@@ -505,7 +506,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
|
||||
|
||||
bool IDragDrop.CanDragDrop(DragDropEventArgs eventArgs)
|
||||
{
|
||||
return eventArgs.Target.TryGetComponent(out PlaceableSurfaceComponent placeable) &&
|
||||
return eventArgs.Target.TryGetComponent(out PlaceableSurfaceComponent? placeable) &&
|
||||
placeable.IsPlaceable;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Health.BodySystem;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.Interfaces.Chat;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Power;
|
||||
using Content.Shared.Health.BodySystem;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.Kitchen;
|
||||
@@ -23,14 +26,12 @@ using Robust.Server.GameObjects.Components.UserInterface;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timers;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Kitchen
|
||||
{
|
||||
@@ -206,7 +207,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
_userInterface.Open(actor.playerSession);
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!_powered)
|
||||
{
|
||||
@@ -456,13 +457,19 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
|
||||
public SuicideKind Suicide(IEntity victim, IChatManager chat)
|
||||
{
|
||||
int headCount = 0;
|
||||
var headCount = 0;
|
||||
if (victim.TryGetComponent<BodyManagerComponent>(out var bodyManagerComponent))
|
||||
{
|
||||
var heads = bodyManagerComponent.GetBodyPartsOfType(BodyPartType.Head);
|
||||
foreach (var head in heads)
|
||||
{
|
||||
var droppedHead = bodyManagerComponent.DropBodyPart(head);
|
||||
var droppedHead = bodyManagerComponent.DropPart(head);
|
||||
|
||||
if (droppedHead == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_storage.Insert(droppedHead);
|
||||
headCount++;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
@@ -15,7 +14,8 @@ using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Damage;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Medical
|
||||
{
|
||||
@@ -35,19 +35,27 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_appearance = Owner.GetComponent<AppearanceComponent>();
|
||||
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
|
||||
.GetBoundUserInterface(MedicalScannerUiKey.Key);
|
||||
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
|
||||
_bodyContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-bodyContainer", Owner);
|
||||
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
|
||||
|
||||
//TODO: write this so that it checks for a change in power events and acts accordingly.
|
||||
var newState = GetUserInterfaceState();
|
||||
_userInterface.SetState(newState);
|
||||
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
private static readonly MedicalScannerBoundUserInterfaceState EmptyUIState =
|
||||
new MedicalScannerBoundUserInterfaceState(
|
||||
0,
|
||||
0,
|
||||
null);
|
||||
null,
|
||||
new Dictionary<DamageClass, int>(),
|
||||
new Dictionary<DamageType, int>(),
|
||||
false);
|
||||
|
||||
private MedicalScannerBoundUserInterfaceState GetUserInterfaceState()
|
||||
{
|
||||
@@ -58,58 +66,51 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
return EmptyUIState;
|
||||
}
|
||||
|
||||
var damageable = body.GetComponent<DamageableComponent>();
|
||||
var species = body.GetComponent<SpeciesComponent>();
|
||||
var deathThreshold =
|
||||
species.DamageTemplate.DamageThresholds.FirstOrNull(x => x.ThresholdType == ThresholdType.Death);
|
||||
if (!deathThreshold.HasValue)
|
||||
if (!body.TryGetComponent(out IDamageableComponent damageable) ||
|
||||
damageable.CurrentDamageState == DamageState.Dead)
|
||||
{
|
||||
return EmptyUIState;
|
||||
}
|
||||
|
||||
var deathThresholdValue = deathThreshold.Value.Value;
|
||||
var currentHealth = damageable.CurrentDamage[DamageType.Total];
|
||||
var classes = new Dictionary<DamageClass, int>(damageable.DamageClasses);
|
||||
var types = new Dictionary<DamageType, int>(damageable.DamageTypes);
|
||||
|
||||
var dmgDict = new Dictionary<string, int>();
|
||||
|
||||
foreach (var dmgType in (DamageType[]) Enum.GetValues(typeof(DamageType)))
|
||||
{
|
||||
if (damageable.CurrentDamage.TryGetValue(dmgType, out var amount))
|
||||
{
|
||||
dmgDict[dmgType.ToString()] = amount;
|
||||
}
|
||||
}
|
||||
|
||||
return new MedicalScannerBoundUserInterfaceState(
|
||||
deathThresholdValue - currentHealth,
|
||||
deathThresholdValue,
|
||||
dmgDict);
|
||||
return new MedicalScannerBoundUserInterfaceState(body.Uid, classes, types, CloningSystem.HasUid(body.Uid));
|
||||
}
|
||||
|
||||
private void UpdateUserInterface()
|
||||
{
|
||||
if (!Powered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newState = GetUserInterfaceState();
|
||||
_userInterface.SetState(newState);
|
||||
}
|
||||
|
||||
private MedicalScannerStatus GetStatusFromDamageState(IDamageState damageState)
|
||||
private MedicalScannerStatus GetStatusFromDamageState(DamageState damageState)
|
||||
{
|
||||
switch (damageState)
|
||||
{
|
||||
case NormalState _: return MedicalScannerStatus.Green;
|
||||
case CriticalState _: return MedicalScannerStatus.Red;
|
||||
case DeadState _: return MedicalScannerStatus.Death;
|
||||
case DamageState.Alive: return MedicalScannerStatus.Green;
|
||||
case DamageState.Critical: return MedicalScannerStatus.Red;
|
||||
case DamageState.Dead: return MedicalScannerStatus.Death;
|
||||
default: throw new ArgumentException(nameof(damageState));
|
||||
}
|
||||
}
|
||||
|
||||
private MedicalScannerStatus GetStatus()
|
||||
{
|
||||
var body = _bodyContainer.ContainedEntity;
|
||||
return body == null
|
||||
? MedicalScannerStatus.Open
|
||||
: GetStatusFromDamageState(body.GetComponent<SpeciesComponent>().CurrentDamageState);
|
||||
if (Powered)
|
||||
{
|
||||
var body = _bodyContainer.ContainedEntity;
|
||||
return body == null
|
||||
? MedicalScannerStatus.Open
|
||||
: GetStatusFromDamageState(body.GetComponent<IDamageableComponent>().CurrentDamageState);
|
||||
}
|
||||
|
||||
return MedicalScannerStatus.Off;
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
@@ -141,7 +142,7 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
return;
|
||||
}
|
||||
|
||||
data.Text = "Enter";
|
||||
data.Text = Loc.GetString("Enter");
|
||||
data.Visibility = component.IsOccupied ? VerbVisibility.Invisible : VerbVisibility.Visible;
|
||||
}
|
||||
|
||||
@@ -162,7 +163,7 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
return;
|
||||
}
|
||||
|
||||
data.Text = "Eject";
|
||||
data.Text = Loc.GetString("Eject");
|
||||
data.Visibility = component.IsOccupied ? VerbVisibility.Visible : VerbVisibility.Invisible;
|
||||
}
|
||||
|
||||
@@ -190,13 +191,28 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (_bodyContainer.ContainedEntity == null)
|
||||
{
|
||||
// There's no need to update if there's no one inside
|
||||
return;
|
||||
}
|
||||
UpdateUserInterface();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
|
||||
{
|
||||
if (!(obj.Message is UiButtonPressedMessage message))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.Button)
|
||||
{
|
||||
case UiButton.ScanDNA:
|
||||
if (_bodyContainer.ContainedEntity != null)
|
||||
{
|
||||
CloningSystem.AddToScannedUids(_bodyContainer.ContainedEntity.Uid);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Metabolism
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles all metabolism for mobs. All delivery methods eventually bring reagents
|
||||
/// to the bloodstream. For example, injectors put reagents directly into the bloodstream,
|
||||
/// and the stomach does with some delay.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class BloodstreamComponent : Component
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
public override string Name => "Bloodstream";
|
||||
|
||||
/// <summary>
|
||||
/// Internal solution for reagent storage
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private SolutionComponent _internalSolution;
|
||||
|
||||
/// <summary>
|
||||
/// Max volume of internal solution storage
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private ReagentUnit _initialMaxVolume;
|
||||
|
||||
/// <summary>
|
||||
/// Empty volume of internal solution
|
||||
/// </summary>
|
||||
public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250));
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
_internalSolution = Owner.GetComponent<SolutionComponent>();
|
||||
_internalSolution.MaxVolume = _initialMaxVolume;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to transfer provided solution to internal solution. Only supports complete transfers
|
||||
/// </summary>
|
||||
/// <param name="solution">Solution to be transferred</param>
|
||||
/// <returns>Whether or not transfer was a success</returns>
|
||||
public bool TryTransferSolution(Solution solution)
|
||||
{
|
||||
//For now doesn't support partial transfers
|
||||
if (solution.TotalVolume + _internalSolution.CurrentVolume > _internalSolution.MaxVolume)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_internalSolution.TryAddSolution(solution, false, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops through each reagent in _internalSolution, and calls the IMetabolizable for each of them./>
|
||||
/// </summary>
|
||||
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
|
||||
private void Metabolize(float tickTime)
|
||||
{
|
||||
if (_internalSolution.CurrentVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//Run metabolism for each reagent, remove metabolized reagents
|
||||
foreach (var reagent in _internalSolution.ReagentList.ToList()) //Using ToList here lets us edit reagents while iterating
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype proto))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
//Run metabolism code for each reagent
|
||||
foreach (var metabolizable in proto.Metabolism)
|
||||
{
|
||||
var reagentDelta = metabolizable.Metabolize(Owner, reagent.ReagentId, tickTime);
|
||||
_internalSolution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers metabolism of the reagents inside _internalSolution. Called by <see cref="BloodstreamSystem"/>
|
||||
/// </summary>
|
||||
/// <param name="tickTime">The time since the last metabolism tick in seconds.</param>
|
||||
public void OnUpdate(float tickTime)
|
||||
{
|
||||
Metabolize(tickTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.GameObjects.Components.Atmos;
|
||||
using Content.Server.GameObjects.Components.Body.Circulatory;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Interfaces.Chemistry;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Metabolism
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class MetabolismComponent : Component
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
public override string Name => "Metabolism";
|
||||
|
||||
private float _accumulatedFrameTime;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] private int _suffocationDamage;
|
||||
|
||||
[ViewVariables] public Dictionary<Gas, float> NeedsGases { get; set; }
|
||||
|
||||
[ViewVariables] public Dictionary<Gas, float> ProducesGases { get; set; }
|
||||
|
||||
[ViewVariables] public Dictionary<Gas, float> DeficitGases { get; set; }
|
||||
|
||||
[ViewVariables] public bool Suffocating => SuffocatingPercentage() > 0;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, b => b.NeedsGases, "needsGases", new Dictionary<Gas, float>());
|
||||
serializer.DataField(this, b => b.ProducesGases, "producesGases", new Dictionary<Gas, float>());
|
||||
serializer.DataField(this, b => b.DeficitGases, "deficitGases", new Dictionary<Gas, float>());
|
||||
serializer.DataField(ref _suffocationDamage, "suffocationDamage", 1);
|
||||
}
|
||||
|
||||
private Dictionary<Gas, float> NeedsAndDeficit(float frameTime)
|
||||
{
|
||||
var needs = new Dictionary<Gas, float>(NeedsGases);
|
||||
foreach (var (gas, amount) in DeficitGases)
|
||||
{
|
||||
var newAmount = (needs.GetValueOrDefault(gas) + amount) * frameTime;
|
||||
needs[gas] = newAmount;
|
||||
}
|
||||
|
||||
return needs;
|
||||
}
|
||||
|
||||
private void ClampDeficit()
|
||||
{
|
||||
var deficitGases = new Dictionary<Gas, float>(DeficitGases);
|
||||
|
||||
foreach (var (gas, deficit) in deficitGases)
|
||||
{
|
||||
if (!NeedsGases.TryGetValue(gas, out var need))
|
||||
{
|
||||
DeficitGases.Remove(gas);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (deficit > need)
|
||||
{
|
||||
DeficitGases[gas] = need;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float SuffocatingPercentage()
|
||||
{
|
||||
var percentages = new float[Atmospherics.TotalNumberOfGases];
|
||||
|
||||
foreach (var (gas, deficit) in DeficitGases)
|
||||
{
|
||||
if (!NeedsGases.TryGetValue(gas, out var needed))
|
||||
{
|
||||
percentages[(int) gas] = 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
percentages[(int) gas] = deficit / needed;
|
||||
}
|
||||
|
||||
return percentages.Average();
|
||||
}
|
||||
|
||||
private float GasProducedMultiplier(Gas gas, float usedAverage)
|
||||
{
|
||||
if (!NeedsGases.TryGetValue(gas, out var needs) ||
|
||||
!ProducesGases.TryGetValue(gas, out var produces))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return needs * produces * usedAverage;
|
||||
}
|
||||
|
||||
private Dictionary<Gas, float> GasProduced(float usedAverage)
|
||||
{
|
||||
return ProducesGases.ToDictionary(pair => pair.Key, pair => GasProducedMultiplier(pair.Key, usedAverage));
|
||||
}
|
||||
|
||||
private void ProcessGases(float frameTime)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var usedPercentages = new float[Atmospherics.TotalNumberOfGases];
|
||||
var needs = NeedsAndDeficit(frameTime);
|
||||
foreach (var (gas, amountNeeded) in needs)
|
||||
{
|
||||
var bloodstreamAmount = bloodstream.Air.GetMoles(gas);
|
||||
var deficit = 0f;
|
||||
|
||||
if (bloodstreamAmount >= amountNeeded)
|
||||
{
|
||||
bloodstream.Air.AdjustMoles(gas, -amountNeeded);
|
||||
}
|
||||
else
|
||||
{
|
||||
deficit = amountNeeded - bloodstreamAmount;
|
||||
bloodstream.Air.SetMoles(gas, 0);
|
||||
}
|
||||
|
||||
DeficitGases[gas] = deficit;
|
||||
|
||||
var used = amountNeeded - deficit;
|
||||
usedPercentages[(int) gas] = used / amountNeeded;
|
||||
}
|
||||
|
||||
var usedAverage = usedPercentages.Average();
|
||||
var produced = GasProduced(usedAverage);
|
||||
|
||||
foreach (var (gas, amountProduced) in produced)
|
||||
{
|
||||
bloodstream.Air.AdjustMoles(gas, amountProduced);
|
||||
}
|
||||
|
||||
ClampDeficit();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loops through each reagent in _internalSolution,
|
||||
/// and calls <see cref="IMetabolizable.Metabolize"/> for each of them.
|
||||
/// </summary>
|
||||
/// <param name="frameTime">The time since the last metabolism tick in seconds.</param>
|
||||
private void ProcessNutrients(float frameTime)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (bloodstream.Solution.CurrentVolume == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Run metabolism for each reagent, remove metabolized reagents
|
||||
// Using ToList here lets us edit reagents while iterating
|
||||
foreach (var reagent in bloodstream.Solution.ReagentList.ToList())
|
||||
{
|
||||
if (!_prototypeManager.TryIndex(reagent.ReagentId, out ReagentPrototype prototype))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Run metabolism code for each reagent
|
||||
foreach (var metabolizable in prototype.Metabolism)
|
||||
{
|
||||
var reagentDelta = metabolizable.Metabolize(Owner, reagent.ReagentId, frameTime);
|
||||
bloodstream.Solution.TryRemoveReagent(reagent.ReagentId, reagentDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes gases in the bloodstream and triggers metabolism of the
|
||||
/// reagents inside of it.
|
||||
/// </summary>
|
||||
/// <param name="frameTime">
|
||||
/// The time since the last metabolism tick in seconds.
|
||||
/// </param>
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
_accumulatedFrameTime += frameTime;
|
||||
|
||||
if (_accumulatedFrameTime < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_accumulatedFrameTime -= 1;
|
||||
|
||||
ProcessGases(frameTime);
|
||||
ProcessNutrients(frameTime);
|
||||
|
||||
if (Suffocating &&
|
||||
Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
// damageable.ChangeDamage(DamageClass.Airloss, _suffocationDamage, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void Transfer(BloodstreamComponent @from, GasMixture to, Gas gas, float pressure)
|
||||
{
|
||||
var transfer = new GasMixture();
|
||||
var molesInBlood = @from.Air.GetMoles(gas);
|
||||
|
||||
transfer.SetMoles(gas, molesInBlood);
|
||||
transfer.ReleaseGasTo(to, pressure);
|
||||
|
||||
@from.Air.Merge(transfer);
|
||||
}
|
||||
|
||||
public GasMixture Clean(BloodstreamComponent bloodstream, float pressure = 100)
|
||||
{
|
||||
var gasMixture = new GasMixture(bloodstream.Air.Volume);
|
||||
|
||||
for (Gas gas = 0; gas < (Gas) Atmospherics.TotalNumberOfGases; gas++)
|
||||
{
|
||||
if (NeedsGases.TryGetValue(gas, out var needed) &&
|
||||
bloodstream.Air.GetMoles(gas) < needed * 1.5f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Transfer(bloodstream, gasMixture, gas, pressure);
|
||||
}
|
||||
|
||||
return gasMixture;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Weapon.Melee;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
@@ -30,12 +31,12 @@ namespace Content.Server.GameObjects.Components.Mining
|
||||
spriteComponent.LayerSetState(0, _random.Pick(SpriteStates));
|
||||
}
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
var item = eventArgs.Using;
|
||||
if (!item.TryGetComponent(out MeleeWeaponComponent meleeWeaponComponent)) return false;
|
||||
|
||||
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Brute, meleeWeaponComponent.Damage, item, eventArgs.User);
|
||||
Owner.GetComponent<IDamageableComponent>().ChangeDamage(DamageType.Blunt, meleeWeaponComponent.Damage, false, item);
|
||||
|
||||
if (!item.TryGetComponent(out PickaxeComponent pickaxeComponent)) return true;
|
||||
if (!string.IsNullOrWhiteSpace(pickaxeComponent.MiningSound))
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
using Content.Server.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the blocking effect of each damage state, and what effects to apply upon entering or exiting the state
|
||||
/// </summary>
|
||||
public interface IDamageState : IActionBlocker
|
||||
{
|
||||
void EnterState(IEntity entity);
|
||||
|
||||
void ExitState(IEntity entity);
|
||||
|
||||
bool IsConscious { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Standard state that a species is at with no damage or negative effect
|
||||
/// </summary>
|
||||
public struct NormalState : IDamageState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
entity.TryGetComponent(out AppearanceComponent appearanceComponent);
|
||||
appearanceComponent?.SetData(DamageStateVisuals.State, DamageStateVisualData.Normal);
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsConscious => true;
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A state in which you are disabled from acting due to damage
|
||||
/// </summary>
|
||||
public struct CriticalState : IDamageState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
if(entity.TryGetComponent(out StunnableComponent stun))
|
||||
stun.CancelAll();
|
||||
|
||||
entity.TryGetComponent(out AppearanceComponent appearanceComponent);
|
||||
appearanceComponent?.SetData(DamageStateVisuals.State, DamageStateVisualData.Crit);
|
||||
StandingStateHelper.Down(entity);
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
StandingStateHelper.Standing(entity);
|
||||
}
|
||||
|
||||
public bool IsConscious => false;
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A damage state which will allow ghosting out of mobs
|
||||
/// </summary>
|
||||
public struct DeadState : IDamageState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
if(entity.TryGetComponent(out StunnableComponent stun))
|
||||
stun.CancelAll();
|
||||
|
||||
StandingStateHelper.Down(entity);
|
||||
entity.TryGetComponent(out AppearanceComponent appearanceComponent);
|
||||
appearanceComponent?.SetData(DamageStateVisuals.State, DamageStateVisualData.Dead);
|
||||
|
||||
if (entity.TryGetComponent(out ICollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
StandingStateHelper.Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out ICollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsConscious => false;
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the threshold values for each damage state for any kind of species
|
||||
/// </summary>
|
||||
public abstract class DamageTemplates
|
||||
{
|
||||
public abstract List<DamageThreshold> HealthHudThresholds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Changes the hud state when a threshold is reached
|
||||
/// </summary>
|
||||
/// <param name="damage"></param>
|
||||
/// <returns></returns>
|
||||
public abstract void ChangeHudState(DamageableComponent damage);
|
||||
|
||||
//public abstract ResistanceSet resistanceset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Shows allowed states, ordered by priority, closest to last value to have threshold reached is preferred
|
||||
/// </summary>
|
||||
public abstract List<(DamageType, int, ThresholdType)> AllowedStates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Map of ALL POSSIBLE damage states to the threshold enum value that will trigger them, normal state wont be triggered by this value but is a default that is fell back onto
|
||||
/// </summary>
|
||||
public static Dictionary<ThresholdType, IDamageState> StateThresholdMap = new Dictionary<ThresholdType, IDamageState>()
|
||||
{
|
||||
{ ThresholdType.None, new NormalState() },
|
||||
{ ThresholdType.Critical, new CriticalState() },
|
||||
{ ThresholdType.Death, new DeadState() }
|
||||
};
|
||||
|
||||
public List<DamageThreshold> DamageThresholds
|
||||
{
|
||||
get
|
||||
{
|
||||
List<DamageThreshold> thresholds = new List<DamageThreshold>();
|
||||
foreach (var element in AllowedStates)
|
||||
{
|
||||
thresholds.Add(new DamageThreshold(element.Item1, element.Item2, element.Item3));
|
||||
}
|
||||
return thresholds;
|
||||
}
|
||||
}
|
||||
|
||||
public ThresholdType CalculateDamageState(DamageableComponent damage)
|
||||
{
|
||||
ThresholdType healthstate = ThresholdType.None;
|
||||
foreach(var element in AllowedStates)
|
||||
{
|
||||
if(damage.CurrentDamage[element.Item1] >= element.Item2)
|
||||
{
|
||||
healthstate = element.Item3;
|
||||
}
|
||||
}
|
||||
|
||||
return healthstate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class Human : DamageTemplates
|
||||
{
|
||||
int critvalue = 200;
|
||||
int normalstates = 6;
|
||||
//string startsprite = "human0";
|
||||
|
||||
public override List<(DamageType, int, ThresholdType)> AllowedStates => new List<(DamageType, int, ThresholdType)>()
|
||||
{
|
||||
(DamageType.Total, critvalue-1, ThresholdType.None),
|
||||
(DamageType.Total, critvalue, ThresholdType.Critical),
|
||||
(DamageType.Total, 300, ThresholdType.Death),
|
||||
};
|
||||
|
||||
public override List<DamageThreshold> HealthHudThresholds
|
||||
{
|
||||
get
|
||||
{
|
||||
List<DamageThreshold> thresholds = new List<DamageThreshold>();
|
||||
thresholds.Add(new DamageThreshold(DamageType.Total, 1, ThresholdType.HUDUpdate));
|
||||
for (var i = 1; i <= normalstates; i++)
|
||||
{
|
||||
thresholds.Add(new DamageThreshold(DamageType.Total, i * critvalue / normalstates, ThresholdType.HUDUpdate));
|
||||
}
|
||||
return thresholds; //we don't need to respecify the state damage thresholds since we'll update hud on damage state changes as well
|
||||
}
|
||||
}
|
||||
|
||||
// for shared string dict, since we don't define these anywhere in content
|
||||
[UsedImplicitly]
|
||||
public static readonly string[] _humanStatusImages =
|
||||
{
|
||||
"/Textures/Interface/StatusEffects/Human/human0.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human1.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human2.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human3.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human4.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human5.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human6-0.png",
|
||||
"/Textures/Interface/StatusEffects/Human/human6-1.png",
|
||||
"/Textures/Interface/StatusEffects/Human/humancrit-0.png",
|
||||
"/Textures/Interface/StatusEffects/Human/humancrit-1.png",
|
||||
"/Textures/Interface/StatusEffects/Human/humandead.png",
|
||||
};
|
||||
|
||||
public override void ChangeHudState(DamageableComponent damage)
|
||||
{
|
||||
ThresholdType healthstate = CalculateDamageState(damage);
|
||||
damage.Owner.TryGetComponent(out ServerStatusEffectsComponent statusEffectsComponent);
|
||||
damage.Owner.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent);
|
||||
switch (healthstate)
|
||||
{
|
||||
case ThresholdType.None:
|
||||
var totaldamage = damage.CurrentDamage[DamageType.Total];
|
||||
if (totaldamage > critvalue)
|
||||
{
|
||||
throw new InvalidOperationException(); //these should all be below the crit value, possibly going over multiple thresholds at once?
|
||||
}
|
||||
var modifier = totaldamage / (critvalue / normalstates); //integer division floors towards zero
|
||||
statusEffectsComponent?.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
||||
|
||||
overlayComponent?.RemoveOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
overlayComponent?.RemoveOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
|
||||
return;
|
||||
case ThresholdType.Critical:
|
||||
statusEffectsComponent?.ChangeStatusEffectIcon(
|
||||
StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/humancrit-0.png");
|
||||
overlayComponent?.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
overlayComponent?.RemoveOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
|
||||
return;
|
||||
case ThresholdType.Death:
|
||||
statusEffectsComponent?.ChangeStatusEffectIcon(
|
||||
StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/humandead.png");
|
||||
overlayComponent?.RemoveOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
overlayComponent?.AddOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
|
||||
return;
|
||||
default:
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,9 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
|
||||
public int GetHeatResistance()
|
||||
{
|
||||
if (Owner.GetComponent<InventoryComponent>().TryGetSlotItem(EquipmentSlotDefines.Slots.GLOVES, itemComponent: out ClothingComponent gloves)
|
||||
| Owner.TryGetComponent(out SpeciesComponent speciesComponent))
|
||||
if (Owner.GetComponent<InventoryComponent>().TryGetSlotItem(EquipmentSlotDefines.Slots.GLOVES, itemComponent: out ClothingComponent gloves))
|
||||
{
|
||||
return Math.Max(gloves?.HeatResistance ?? int.MinValue, speciesComponent?.HeatResistance ?? int.MinValue);
|
||||
return gloves?.HeatResistance ?? int.MinValue;
|
||||
}
|
||||
return int.MinValue;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Content.Server.GameObjects.Components.Observer;
|
||||
using Content.Server.Interfaces.GameTicking;
|
||||
using Content.Server.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
@@ -78,7 +79,7 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
var visiting = Mind?.VisitingEntity;
|
||||
if (visiting != null)
|
||||
{
|
||||
if (visiting.TryGetComponent(out GhostComponent ghost))
|
||||
if (visiting.TryGetComponent(out GhostComponent? ghost))
|
||||
{
|
||||
ghost.CanReturnToBody = false;
|
||||
}
|
||||
@@ -126,8 +127,8 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
}
|
||||
|
||||
var dead =
|
||||
Owner.TryGetComponent<SpeciesComponent>(out var species) &&
|
||||
species.CurrentDamageState is DeadState;
|
||||
Owner.TryGetComponent<IDamageableComponent>(out var damageable) &&
|
||||
damageable.CurrentDamageState == DamageState.Dead;
|
||||
|
||||
if (!HasMind)
|
||||
{
|
||||
@@ -137,7 +138,8 @@ namespace Content.Server.GameObjects.Components.Mobs
|
||||
}
|
||||
else if (Mind?.Session == null)
|
||||
{
|
||||
message.AddMarkup("[color=yellow]" + Loc.GetString("{0:They} {0:have} a blank, absent-minded stare and appears completely unresponsive to anything. {0:They} may snap out of it soon.", Owner) + "[/color]");
|
||||
if(!dead)
|
||||
message.AddMarkup("[color=yellow]" + Loc.GetString("{0:They} {0:have} a blank, absent-minded stare and appears completely unresponsive to anything. {0:They} may snap out of it soon.", Owner) + "[/color]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
488
Content.Server/GameObjects/Components/Mobs/MobStateManager.cs
Normal file
488
Content.Server/GameObjects/Components/Mobs/MobStateManager.cs
Normal file
@@ -0,0 +1,488 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs
|
||||
{
|
||||
/// <summary>
|
||||
/// When attacked to an <see cref="IDamageableComponent"/>, this component will handle critical and death behaviors
|
||||
/// for mobs.
|
||||
/// Additionally, it handles sending effects to clients (such as blur effect for unconsciousness) and managing the
|
||||
/// health HUD.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
internal class MobStateManagerComponent : Component, IOnHealthChangedBehavior, IActionBlocker
|
||||
{
|
||||
private readonly Dictionary<DamageState, IMobState> _behavior = new Dictionary<DamageState, IMobState>
|
||||
{
|
||||
{DamageState.Alive, new NormalState()},
|
||||
{DamageState.Critical, new CriticalState()},
|
||||
{DamageState.Dead, new DeadState()}
|
||||
};
|
||||
|
||||
public override string Name => "MobStateManager";
|
||||
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
public IMobState CurrentMobState { get; private set; } = new NormalState();
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return CurrentMobState.CanInteract();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return CurrentMobState.CanMove();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return CurrentMobState.CanUse();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return CurrentMobState.CanThrow();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return CurrentMobState.CanSpeak();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return CurrentMobState.CanDrop();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return CurrentMobState.CanPickup();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return CurrentMobState.CanEmote();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return CurrentMobState.CanAttack();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return CurrentMobState.CanEquip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return CurrentMobState.CanUnequip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return CurrentMobState.CanChangeDirection();
|
||||
}
|
||||
|
||||
public void OnHealthChanged(HealthChangedEventArgs e)
|
||||
{
|
||||
if (e.Damageable.CurrentDamageState != _currentDamageState)
|
||||
{
|
||||
_currentDamageState = e.Damageable.CurrentDamageState;
|
||||
CurrentMobState.ExitState(Owner);
|
||||
CurrentMobState = _behavior[_currentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
}
|
||||
|
||||
CurrentMobState.UpdateState(Owner);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_currentDamageState = DamageState.Alive;
|
||||
CurrentMobState = _behavior[_currentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
CurrentMobState.UpdateState(Owner);
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
// TODO: Might want to add an OnRemove() to IMobState since those are where these components are being used
|
||||
base.OnRemove();
|
||||
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
{
|
||||
status.RemoveStatusEffect(StatusEffect.Health);
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines the blocking effects of an associated <see cref="DamageState"/>
|
||||
/// (i.e. Normal, Critical, Dead) and what effects to apply upon entering or
|
||||
/// exiting the state.
|
||||
/// </summary>
|
||||
public interface IMobState : IActionBlocker
|
||||
{
|
||||
/// <summary>
|
||||
/// Called when this state is entered.
|
||||
/// </summary>
|
||||
void EnterState(IEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Called when this state is left for a different state.
|
||||
/// </summary>
|
||||
void ExitState(IEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Called when this state is updated.
|
||||
/// </summary>
|
||||
void UpdateState(IEntity entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The standard state an entity is in; no negative effects.
|
||||
/// </summary>
|
||||
public struct NormalState : IMobState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
UpdateState(entity);
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity) { }
|
||||
|
||||
public void UpdateState(IEntity entity)
|
||||
{
|
||||
if (!entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human0.png");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO
|
||||
switch (damageable)
|
||||
{
|
||||
case RuinableComponent ruinable:
|
||||
{
|
||||
var modifier = (int) (ruinable.TotalDamage / (ruinable.MaxHp / 7f));
|
||||
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
||||
|
||||
break;
|
||||
}
|
||||
case BodyManagerComponent body:
|
||||
{
|
||||
// TODO: Declare body max normal damage (currently 100)
|
||||
var modifier = (int) (body.TotalDamage / (100f / 7f));
|
||||
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/human0.png");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A state in which an entity is disabled from acting due to sufficient damage (considered unconscious).
|
||||
/// </summary>
|
||||
public struct CriticalState : IMobState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/humancrit-0.png"); //Todo: combine humancrit-0 and humancrit-1 into a gif and display it
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay);
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out StunnableComponent stun))
|
||||
{
|
||||
stun.CancelAll();
|
||||
}
|
||||
|
||||
StandingStateHelper.Down(entity);
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
StandingStateHelper.Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateState(IEntity entity)
|
||||
{
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The state representing a dead entity; allows for ghosting.
|
||||
/// </summary>
|
||||
public struct DeadState : IMobState
|
||||
{
|
||||
public void EnterState(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
{
|
||||
status.ChangeStatusEffectIcon(StatusEffect.Health,
|
||||
"/Textures/Interface/StatusEffects/Human/humandead.png");
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent))
|
||||
{
|
||||
overlayComponent.AddOverlay(SharedOverlayID.CircleMaskOverlay);
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out StunnableComponent stun))
|
||||
{
|
||||
stun.CancelAll();
|
||||
}
|
||||
|
||||
StandingStateHelper.Down(entity);
|
||||
|
||||
if (entity.TryGetComponent(out CollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ExitState(IEntity entity)
|
||||
{
|
||||
StandingStateHelper.Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out CollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = true;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay))
|
||||
{
|
||||
overlay.ClearOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateState(IEntity entity)
|
||||
{
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Server.Observer;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Mobs
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedSpeciesComponent))]
|
||||
public class SpeciesComponent : SharedSpeciesComponent, IActionBlocker, IOnDamageBehavior, IExAct, IRelayMoveInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Damagestates are reached by reaching a certain damage threshold, they will block actions after being reached
|
||||
/// </summary>
|
||||
public IDamageState CurrentDamageState { get; private set; } = new NormalState();
|
||||
|
||||
/// <summary>
|
||||
/// Damage state enum for current health, set only via change damage state //TODO: SETTER
|
||||
/// </summary>
|
||||
private ThresholdType currentstate = ThresholdType.None;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the damage template which controls the threshold and resistance settings for this species type
|
||||
/// </summary>
|
||||
public DamageTemplates DamageTemplate { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Variable for serialization
|
||||
/// </summary>
|
||||
private string templatename;
|
||||
|
||||
private int _heatResistance;
|
||||
public int HeatResistance => _heatResistance;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref templatename, "Template", "Human");
|
||||
|
||||
var type = typeof(SpeciesComponent).Assembly.GetType("Content.Server.GameObjects.Components.Mobs.DamageThresholdTemplates." + templatename);
|
||||
DamageTemplate = (DamageTemplates) Activator.CreateInstance(type);
|
||||
serializer.DataFieldCached(ref _heatResistance, "HeatResistance", 323);
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case PlayerAttachedMsg _:
|
||||
if (CanReceiveStatusEffect(Owner)) {
|
||||
DamageableComponent damage = Owner.GetComponent<DamageableComponent>();
|
||||
DamageTemplate.ChangeHudState(damage);
|
||||
}
|
||||
break;
|
||||
case PlayerDetachedMsg _:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
Owner.TryGetComponent(out ServerStatusEffectsComponent statusEffectsComponent);
|
||||
statusEffectsComponent?.RemoveStatusEffect(StatusEffect.Health);
|
||||
|
||||
Owner.TryGetComponent(out ServerOverlayEffectsComponent overlayEffectsComponent);
|
||||
overlayEffectsComponent?.ClearOverlays();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanMove()
|
||||
{
|
||||
return CurrentDamageState.CanMove();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanInteract()
|
||||
{
|
||||
return CurrentDamageState.CanInteract();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUse()
|
||||
{
|
||||
return CurrentDamageState.CanUse();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanThrow()
|
||||
{
|
||||
return CurrentDamageState.CanThrow();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanSpeak()
|
||||
{
|
||||
return CurrentDamageState.CanSpeak();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanDrop()
|
||||
{
|
||||
return CurrentDamageState.CanDrop();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanPickup()
|
||||
{
|
||||
return CurrentDamageState.CanPickup();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEmote()
|
||||
{
|
||||
return CurrentDamageState.CanEmote();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanAttack()
|
||||
{
|
||||
return CurrentDamageState.CanAttack();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanEquip()
|
||||
{
|
||||
return CurrentDamageState.CanEquip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanUnequip()
|
||||
{
|
||||
return CurrentDamageState.CanUnequip();
|
||||
}
|
||||
|
||||
bool IActionBlocker.CanChangeDirection()
|
||||
{
|
||||
return CurrentDamageState.CanChangeDirection();
|
||||
}
|
||||
|
||||
List<DamageThreshold> IOnDamageBehavior.GetAllDamageThresholds()
|
||||
{
|
||||
var thresholdlist = DamageTemplate.DamageThresholds;
|
||||
thresholdlist.AddRange(DamageTemplate.HealthHudThresholds);
|
||||
return thresholdlist;
|
||||
}
|
||||
|
||||
void IOnDamageBehavior.OnDamageThresholdPassed(object damageable, DamageThresholdPassedEventArgs e)
|
||||
{
|
||||
DamageableComponent damage = (DamageableComponent) damageable;
|
||||
|
||||
if (e.DamageThreshold.ThresholdType != ThresholdType.HUDUpdate)
|
||||
{
|
||||
ChangeDamageState(DamageTemplate.CalculateDamageState(damage));
|
||||
}
|
||||
|
||||
//specifies if we have a client to update the hud for
|
||||
if (CanReceiveStatusEffect(Owner))
|
||||
{
|
||||
DamageTemplate.ChangeHudState(damage);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanReceiveStatusEffect(IEntity user)
|
||||
{
|
||||
if (!user.HasComponent<ServerStatusEffectsComponent>() &&
|
||||
!user.HasComponent<ServerOverlayEffectsComponent>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (user.HasComponent<DamageableComponent>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ChangeDamageState(ThresholdType threshold)
|
||||
{
|
||||
if (threshold == currentstate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentDamageState.ExitState(Owner);
|
||||
CurrentDamageState = DamageTemplates.StateThresholdMap[threshold];
|
||||
CurrentDamageState.EnterState(Owner);
|
||||
|
||||
currentstate = threshold;
|
||||
|
||||
var toRaise = new MobDamageStateChangedMessage(this);
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, toRaise);
|
||||
}
|
||||
|
||||
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
|
||||
{
|
||||
var burnDamage = 0;
|
||||
var bruteDamage = 0;
|
||||
switch(eventArgs.Severity)
|
||||
{
|
||||
case ExplosionSeverity.Destruction:
|
||||
bruteDamage += 250;
|
||||
burnDamage += 250;
|
||||
break;
|
||||
case ExplosionSeverity.Heavy:
|
||||
bruteDamage += 60;
|
||||
burnDamage += 60;
|
||||
break;
|
||||
case ExplosionSeverity.Light:
|
||||
bruteDamage += 30;
|
||||
break;
|
||||
}
|
||||
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Brute, bruteDamage, null);
|
||||
Owner.GetComponent<DamageableComponent>().TakeDamage(DamageType.Heat, burnDamage, null);
|
||||
}
|
||||
|
||||
void IRelayMoveInput.MoveInputPressed(ICommonSession session)
|
||||
{
|
||||
if (CurrentDamageState is DeadState)
|
||||
{
|
||||
new Ghost().Execute(null, (IPlayerSession) session, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fired when <see cref="SpeciesComponent.CurrentDamageState"/> changes.
|
||||
/// </summary>
|
||||
public sealed class MobDamageStateChangedMessage : EntitySystemMessage
|
||||
{
|
||||
public MobDamageStateChangedMessage(SpeciesComponent species)
|
||||
{
|
||||
Species = species;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The species component that was changed.
|
||||
/// </summary>
|
||||
public SpeciesComponent Species { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Server.GameObjects.EntitySystems.AI;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Robust.Server.AI;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -45,6 +47,8 @@ namespace Content.Server.GameObjects.Components.Movement
|
||||
// This component requires a collidable component.
|
||||
if (!Owner.HasComponent<ICollidableComponent>())
|
||||
Owner.AddComponent<CollidableComponent>();
|
||||
|
||||
EntitySystem.Get<AiSystem>().ProcessorInitialize(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
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.ViewVariables;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Server.GameObjects.Components.Body;
|
||||
using Content.Server.GameObjects.EntitySystems.DoAfter;
|
||||
using Robust.Shared.Maths;
|
||||
using System;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Movement
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IClimbable))]
|
||||
public class ClimbableComponent : SharedClimbableComponent, IDragDropOn
|
||||
{
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
#pragma warning restore 649
|
||||
|
||||
/// <summary>
|
||||
/// The range from which this entity can be climbed.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private float _range;
|
||||
|
||||
/// <summary>
|
||||
/// The time it takes to climb onto the entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private float _climbDelay;
|
||||
|
||||
private ICollidableComponent _collidableComponent;
|
||||
private DoAfterSystem _doAfterSystem;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_collidableComponent = Owner.GetComponent<ICollidableComponent>();
|
||||
_doAfterSystem = EntitySystem.Get<DoAfterSystem>();
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _range, "range", SharedInteractionSystem.InteractionRange / 1.4f);
|
||||
serializer.DataField(ref _climbDelay, "delay", 0.8f);
|
||||
}
|
||||
|
||||
bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventArgs.User == eventArgs.Dropped) // user is dragging themselves onto a climbable
|
||||
{
|
||||
if (!eventArgs.User.HasComponent<ClimbingComponent>())
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are incapable of climbing!"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var bodyManager = eventArgs.User.GetComponent<BodyManagerComponent>();
|
||||
|
||||
if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 ||
|
||||
bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0)
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are unable to climb!"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var userPosition = eventArgs.User.Transform.MapPosition;
|
||||
var climbablePosition = eventArgs.Target.Transform.MapPosition;
|
||||
var interaction = EntitySystem.Get<SharedInteractionSystem>();
|
||||
bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User);
|
||||
|
||||
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored))
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!"));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else // user is dragging some other entity onto a climbable
|
||||
{
|
||||
if (eventArgs.Target == null || !eventArgs.Dropped.HasComponent<ClimbingComponent>())
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!"));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var userPosition = eventArgs.User.Transform.MapPosition;
|
||||
var otherUserPosition = eventArgs.Dropped.Transform.MapPosition;
|
||||
var climbablePosition = eventArgs.Target.Transform.MapPosition;
|
||||
var interaction = EntitySystem.Get<SharedInteractionSystem>();
|
||||
bool Ignored(IEntity entity) => (entity == eventArgs.Target || entity == eventArgs.User || entity == eventArgs.Dropped);
|
||||
|
||||
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored) ||
|
||||
!interaction.InRangeUnobstructed(userPosition, otherUserPosition, _range, predicate: Ignored))
|
||||
{
|
||||
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't reach there!"));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.User == eventArgs.Dropped)
|
||||
{
|
||||
TryClimb(eventArgs.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
TryMoveEntity(eventArgs.User, eventArgs.Dropped);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void TryMoveEntity(IEntity user, IEntity entityToMove)
|
||||
{
|
||||
var doAfterEventArgs = new DoAfterEventArgs(user, _climbDelay, default, entityToMove)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnStun = true
|
||||
};
|
||||
|
||||
var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
|
||||
|
||||
if (result != DoAfterStatus.Cancelled && entityToMove.TryGetComponent(out ICollidableComponent body) && body.PhysicsShapes.Count >= 1)
|
||||
{
|
||||
var direction = (Owner.Transform.WorldPosition - entityToMove.Transform.WorldPosition).Normalized;
|
||||
var endPoint = Owner.Transform.WorldPosition;
|
||||
|
||||
var climbMode = entityToMove.GetComponent<ClimbingComponent>();
|
||||
climbMode.IsClimbing = true;
|
||||
|
||||
if (MathF.Abs(direction.X) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
|
||||
{
|
||||
endPoint = new Vector2(entityToMove.Transform.WorldPosition.X, endPoint.Y);
|
||||
}
|
||||
else if (MathF.Abs(direction.Y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
|
||||
{
|
||||
endPoint = new Vector2(endPoint.X, entityToMove.Transform.WorldPosition.Y);
|
||||
}
|
||||
|
||||
climbMode.TryMoveTo(entityToMove.Transform.WorldPosition, endPoint);
|
||||
// we may potentially need additional logic since we're forcing a player onto a climbable
|
||||
// there's also the cases where the user might collide with the person they are forcing onto the climbable that i haven't accounted for
|
||||
|
||||
PopupMessageOtherClientsInRange(user, Loc.GetString("{0:them} forces {1:them} onto {2:theName}!", user, entityToMove, Owner), 15);
|
||||
_notifyManager.PopupMessage(user, user, Loc.GetString("You force {0:them} onto {1:theName}!", entityToMove, Owner));
|
||||
}
|
||||
}
|
||||
|
||||
private async void TryClimb(IEntity user)
|
||||
{
|
||||
var doAfterEventArgs = new DoAfterEventArgs(user, _climbDelay, default, Owner)
|
||||
{
|
||||
BreakOnTargetMove = true,
|
||||
BreakOnUserMove = true,
|
||||
BreakOnDamage = true,
|
||||
BreakOnStun = true
|
||||
};
|
||||
|
||||
var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
|
||||
|
||||
if (result != DoAfterStatus.Cancelled && user.TryGetComponent(out ICollidableComponent body) && body.PhysicsShapes.Count >= 1)
|
||||
{
|
||||
var direction = (Owner.Transform.WorldPosition - user.Transform.WorldPosition).Normalized;
|
||||
var endPoint = Owner.Transform.WorldPosition;
|
||||
|
||||
var climbMode = user.GetComponent<ClimbingComponent>();
|
||||
climbMode.IsClimbing = true;
|
||||
|
||||
if (MathF.Abs(direction.X) < 0.6f) // user climbed mostly vertically so lets make it a clean straight line
|
||||
{
|
||||
endPoint = new Vector2(user.Transform.WorldPosition.X, endPoint.Y);
|
||||
}
|
||||
else if (MathF.Abs(direction.Y) < 0.6f) // user climbed mostly horizontally so lets make it a clean straight line
|
||||
{
|
||||
endPoint = new Vector2(endPoint.X, user.Transform.WorldPosition.Y);
|
||||
}
|
||||
|
||||
climbMode.TryMoveTo(user.Transform.WorldPosition, endPoint);
|
||||
|
||||
PopupMessageOtherClientsInRange(user, Loc.GetString("{0:them} jumps onto {1:theName}!", user, Owner), 15);
|
||||
_notifyManager.PopupMessage(user, user, Loc.GetString("You jump onto {0:theName}!", Owner));
|
||||
}
|
||||
}
|
||||
|
||||
private void PopupMessageOtherClientsInRange(IEntity source, string message, int maxReceiveDistance)
|
||||
{
|
||||
var viewers = _playerManager.GetPlayersInRange(source.Transform.GridPosition, maxReceiveDistance);
|
||||
|
||||
foreach (var viewer in viewers)
|
||||
{
|
||||
var viewerEntity = viewer.AttachedEntity;
|
||||
|
||||
if (viewerEntity == null || source == viewerEntity)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
source.PopupMessage(viewer.AttachedEntity, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Content.Shared.Physics;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Movement
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class ClimbingComponent : SharedClimbingComponent, IActionBlocker
|
||||
{
|
||||
private bool _isClimbing = false;
|
||||
private ClimbController _climbController = default;
|
||||
|
||||
public override bool IsClimbing
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isClimbing;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!value && Body != null)
|
||||
{
|
||||
Body.TryRemoveController<ClimbController>();
|
||||
}
|
||||
|
||||
_isClimbing = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make the owner climb from one point to another
|
||||
/// </summary>
|
||||
public void TryMoveTo(Vector2 from, Vector2 to)
|
||||
{
|
||||
if (Body != null)
|
||||
{
|
||||
_climbController = Body.EnsureController<ClimbController>();
|
||||
_climbController.TryMoveTo(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(float frameTime)
|
||||
{
|
||||
if (Body != null && IsClimbing)
|
||||
{
|
||||
if (_climbController != null && (_climbController.IsBlocked || !_climbController.IsActive))
|
||||
{
|
||||
if (Body.TryRemoveController<ClimbController>())
|
||||
{
|
||||
_climbController = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsClimbing)
|
||||
{
|
||||
Body.WakeBody();
|
||||
}
|
||||
|
||||
if (!IsOnClimbableThisFrame && IsClimbing && _climbController == null)
|
||||
{
|
||||
IsClimbing = false;
|
||||
}
|
||||
|
||||
IsOnClimbableThisFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new ClimbModeComponentState(_isClimbing);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ namespace Content.Server.GameObjects.Components.Movement
|
||||
_entityManager.TryGetEntity(grid.GridEntityId, out var gridEntity))
|
||||
{
|
||||
//TODO: Switch to shuttle component
|
||||
if (!gridEntity.TryGetComponent(out ICollidableComponent collidable))
|
||||
if (!gridEntity.TryGetComponent(out ICollidableComponent? collidable))
|
||||
{
|
||||
collidable = gridEntity.AddComponent<CollidableComponent>();
|
||||
collidable.Mass = 1;
|
||||
@@ -137,9 +137,9 @@ namespace Content.Server.GameObjects.Components.Movement
|
||||
private void SetController(IEntity entity)
|
||||
{
|
||||
if (_controller != null ||
|
||||
!entity.TryGetComponent(out MindComponent mind) ||
|
||||
!entity.TryGetComponent(out MindComponent? mind) ||
|
||||
mind.Mind == null ||
|
||||
!Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
!Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -179,17 +179,17 @@ namespace Content.Server.GameObjects.Components.Movement
|
||||
/// <param name="entity">The entity to update</param>
|
||||
private void UpdateRemovedEntity(IEntity entity)
|
||||
{
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
|
||||
if (Owner.TryGetComponent(out ServerStatusEffectsComponent? status))
|
||||
{
|
||||
status.RemoveStatusEffect(StatusEffect.Piloting);
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out MindComponent mind))
|
||||
if (entity.TryGetComponent(out MindComponent? mind))
|
||||
{
|
||||
mind.Mind?.UnVisit();
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out BuckleComponent buckle))
|
||||
if (entity.TryGetComponent(out BuckleComponent? buckle))
|
||||
{
|
||||
buckle.TryUnbuckle(entity, true);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Fluids;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Audio;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Body.Digestive;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
@@ -180,11 +180,11 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
}
|
||||
if (_currentHungerThreshold == HungerThreshold.Dead)
|
||||
{
|
||||
if (Owner.TryGetComponent(out DamageableComponent damage))
|
||||
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
if (!damage.IsDead())
|
||||
if (damageable.CurrentDamageState != DamageState.Dead)
|
||||
{
|
||||
damage.TakeDamage(DamageType.Brute, 2);
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Movement;
|
||||
@@ -181,11 +181,11 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
|
||||
if (_currentThirstThreshold == ThirstThreshold.Dead)
|
||||
{
|
||||
if (Owner.TryGetComponent(out DamageableComponent damage))
|
||||
if (Owner.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
if (!damage.IsDead())
|
||||
if (damageable.CurrentDamageState != DamageState.Dead)
|
||||
{
|
||||
damage.TakeDamage(DamageType.Brute, 2);
|
||||
damageable.ChangeDamage(DamageType.Blunt, 2, true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Access;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
@@ -140,10 +141,10 @@ namespace Content.Server.GameObjects.Components.PDA
|
||||
|
||||
private void UpdatePDAAppearance()
|
||||
{
|
||||
_appearance?.SetData(PDAVisuals.ScreenLit, _lightOn);
|
||||
_appearance?.SetData(PDAVisuals.FlashlightLit, _lightOn);
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
var item = eventArgs.Using;
|
||||
if (!IdSlotEmpty)
|
||||
@@ -163,7 +164,7 @@ namespace Content.Server.GameObjects.Components.PDA
|
||||
|
||||
void IActivate.Activate(ActivateEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -174,7 +175,7 @@ namespace Content.Server.GameObjects.Components.PDA
|
||||
|
||||
public bool UseEntity(UseEntityEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -66,7 +67,7 @@ namespace Content.Server.GameObjects.Components.Paper
|
||||
UpdateUserInterface();
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.Using.HasComponent<WriteComponent>())
|
||||
return false;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
@@ -12,6 +13,8 @@ namespace Content.Server.GameObjects.Components
|
||||
private bool _isPlaceable;
|
||||
public bool IsPlaceable { get => _isPlaceable; set => _isPlaceable = value; }
|
||||
|
||||
int IInteractUsing.Priority => 1;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
@@ -19,7 +22,8 @@ namespace Content.Server.GameObjects.Components
|
||||
serializer.DataField(ref _isPlaceable, "IsPlaceable", true);
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!IsPlaceable)
|
||||
return false;
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Content.Server.GameObjects.Components.Pointing
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
if (Owner.TryGetComponent(out SpriteComponent sprite))
|
||||
if (Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.DrawDepth = (int) DrawDepth.Overlays;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Content.Server.GameObjects.Components.Pointing
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
if (_chasing == null ||
|
||||
!Owner.TryGetComponent(out AppearanceComponent appearance))
|
||||
!Owner.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -69,7 +69,7 @@ namespace Content.Server.GameObjects.Components.Pointing
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
if (Owner.TryGetComponent(out SpriteComponent sprite))
|
||||
if (Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.DrawDepth = (int) DrawDepth.Overlays;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Items;
|
||||
@@ -30,7 +31,7 @@ namespace Content.Server.GameObjects.Components
|
||||
ContainerManagerComponent.Ensure<ContainerSlot>("potted_plant_hide", Owner, out _);
|
||||
}
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (_itemContainer.ContainedEntity != null)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
|
||||
@@ -65,7 +66,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
||||
base.OnRemove();
|
||||
}
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
var result = TryInsertItem(eventArgs.Using);
|
||||
if (!result)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.Components.Container;
|
||||
@@ -69,14 +70,14 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
||||
}
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
return InsertBulb(eventArgs.Using);
|
||||
}
|
||||
|
||||
public bool InteractHand(InteractHandEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.User.TryGetComponent(out DamageableComponent damageableComponent))
|
||||
if (!eventArgs.User.TryGetComponent(out IDamageableComponent damageableComponent))
|
||||
{
|
||||
Eject();
|
||||
return false;
|
||||
@@ -99,7 +100,7 @@ namespace Content.Server.GameObjects.Components.Power.ApcNetComponents.PowerRece
|
||||
|
||||
void Burn()
|
||||
{
|
||||
damageableComponent.TakeDamage(DamageType.Heat, 20, Owner);
|
||||
damageableComponent.ChangeDamage(DamageType.Heat, 20, false, Owner);
|
||||
var audioSystem = EntitySystem.Get<AudioSystem>();
|
||||
audioSystem.PlayFromEntity("/Audio/Effects/lightburn.ogg", Owner);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Interactable;
|
||||
using Content.Server.GameObjects.Components.Stack;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
@@ -34,10 +35,10 @@ namespace Content.Server.GameObjects.Components.Power
|
||||
serializer.DataField(ref _wireType, "wireType", WireType.HighVoltage);
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.Using.TryGetComponent(out ToolComponent tool)) return false;
|
||||
if (!tool.UseTool(eventArgs.User, Owner, ToolQuality.Cutting)) return false;
|
||||
if (!await tool.UseTool(eventArgs.User, Owner, 0.25f, ToolQuality.Cutting)) return false;
|
||||
|
||||
Owner.Delete();
|
||||
var droppedEnt = Owner.EntityManager.SpawnEntity(_wireDroppedOnCutPrototype, eventArgs.ClickLocation);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Projectiles;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
@@ -71,9 +71,11 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_deleteOnCollide = true;
|
||||
}
|
||||
|
||||
if (_soundHitSpecies != null && entity.HasComponent<SpeciesComponent>())
|
||||
if (_soundHitSpecies != null && entity.HasComponent<IDamageableComponent>())
|
||||
{
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundHitSpecies, entity.Transform.GridPosition);
|
||||
} else if (_soundHit != null)
|
||||
@@ -81,13 +83,13 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords(_soundHit, entity.Transform.GridPosition);
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out DamageableComponent damage))
|
||||
if (entity.TryGetComponent(out IDamageableComponent damage))
|
||||
{
|
||||
Owner.EntityManager.TryGetEntity(_shooter, out var shooter);
|
||||
|
||||
foreach (var (damageType, amount) in _damages)
|
||||
{
|
||||
damage.TakeDamage(damageType, amount, Owner, shooter);
|
||||
damage.ChangeDamage(damageType, amount, false, shooter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.EntitySystems.Click;
|
||||
using Content.Server.GameObjects.EntitySystems.Click;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Physics;
|
||||
@@ -40,9 +40,9 @@ namespace Content.Server.GameObjects.Components.Projectiles
|
||||
|
||||
_shouldStop = true; // hit something hard => stop after this collision
|
||||
}
|
||||
if (entity.TryGetComponent(out DamageableComponent damage))
|
||||
if (entity.TryGetComponent(out IDamageableComponent damage))
|
||||
{
|
||||
damage.TakeDamage(DamageType.Brute, 10, Owner, User);
|
||||
damage.ChangeDamage(DamageType.Blunt, 10, false, Owner);
|
||||
}
|
||||
|
||||
// Stop colliding with mobs, this mimics not having enough velocity to do damage
|
||||
|
||||
@@ -5,7 +5,10 @@ using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Recycling;
|
||||
using Content.Shared.GameObjects.Components.Rotation;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -65,9 +68,7 @@ namespace Content.Server.GameObjects.Components.Recycling
|
||||
|
||||
private bool CanGib(IEntity entity)
|
||||
{
|
||||
return entity.HasComponent<SpeciesComponent>() &&
|
||||
!_safe &&
|
||||
Powered;
|
||||
return entity.HasComponent<IBodyManagerComponent>() && !_safe && Powered;
|
||||
}
|
||||
|
||||
private bool CanRecycle(IEntity entity, [MaybeNullWhen(false)] out ConstructionPrototype prototype)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
|
||||
using Content.Server.GameObjects.Components.Stack;
|
||||
using Content.Shared.GameObjects.Components.Materials;
|
||||
@@ -149,7 +150,8 @@ namespace Content.Server.GameObjects.Components.Research
|
||||
|
||||
OpenUserInterface(actor.playerSession);
|
||||
}
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out MaterialStorageComponent storage)
|
||||
|| !eventArgs.Using.TryGetComponent(out MaterialComponent material)) return false;
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Content.Server.GameObjects.Components.Rotatable
|
||||
|
||||
private void TryFlip(IEntity user)
|
||||
{
|
||||
if (Owner.TryGetComponent(out ICollidableComponent collidable) &&
|
||||
if (Owner.TryGetComponent(out ICollidableComponent? collidable) &&
|
||||
collidable.Anchored)
|
||||
{
|
||||
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("It's stuck."));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -61,7 +62,7 @@ namespace Content.Server.GameObjects.Components.Stack
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Using.TryGetComponent<StackComponent>(out var stack))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.Mobs.Roles;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Suspicion
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class SuspicionRoleComponent : Component, IExamine
|
||||
{
|
||||
public override string Name => "SuspicionRole";
|
||||
|
||||
public bool IsDead()
|
||||
{
|
||||
return Owner.TryGetComponent(out IDamageableComponent damageable) &&
|
||||
damageable.CurrentDamageState == DamageState.Dead;
|
||||
}
|
||||
|
||||
public bool IsTraitor()
|
||||
{
|
||||
return Owner.TryGetComponent(out MindComponent mind) &&
|
||||
mind.HasMind &&
|
||||
mind.Mind!.HasRole<SuspicionTraitorRole>();
|
||||
}
|
||||
|
||||
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
|
||||
{
|
||||
if (!IsDead())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tooltip = IsTraitor()
|
||||
? Loc.GetString($"They were a [color=red]traitor[/color]!")
|
||||
: Loc.GetString($"They were an [color=green]innocent[/color]!");
|
||||
|
||||
message.AddMarkup(tooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.Maths;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -44,16 +44,16 @@ namespace Content.Server.GameObjects.Components.Temperature
|
||||
/// <inheritdoc />
|
||||
public void OnUpdate(float frameTime)
|
||||
{
|
||||
int fireDamage =
|
||||
var fireDamage =
|
||||
(int) Math.Floor(Math.Max(0, CurrentTemperature - _fireDamageThreshold) / _fireDamageCoefficient);
|
||||
|
||||
_secondsSinceLastDamageUpdate += frameTime;
|
||||
|
||||
Owner.TryGetComponent(out DamageableComponent component);
|
||||
Owner.TryGetComponent(out IDamageableComponent component);
|
||||
|
||||
while (_secondsSinceLastDamageUpdate >= 1)
|
||||
{
|
||||
component?.TakeDamage(DamageType.Heat, fireDamage);
|
||||
component?.ChangeDamage(DamageType.Heat, fireDamage, false, null);
|
||||
_secondsSinceLastDamageUpdate -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Damage;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Server.Interfaces.GameObjects.Components.Interaction;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
@@ -18,6 +15,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Weapon.Melee
|
||||
@@ -116,9 +114,9 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
|
||||
if (!entity.Transform.IsMapTransform || entity == eventArgs.User)
|
||||
continue;
|
||||
|
||||
if (entity.TryGetComponent(out DamageableComponent damageComponent))
|
||||
if (entity.TryGetComponent(out IDamageableComponent damageComponent))
|
||||
{
|
||||
damageComponent.TakeDamage(DamageType.Brute, Damage, Owner, eventArgs.User);
|
||||
damageComponent.ChangeDamage(DamageType.Blunt, Damage, false, Owner);
|
||||
hitEntities.Add(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
@@ -192,7 +193,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.Using.HasComponent<BatteryComponent>()) return false;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.Components.Weapon.Ranged.Barrels;
|
||||
@@ -119,7 +120,7 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Using.HasComponent<AmmoComponent>())
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user