Merge branch 'master' into mathmerge

This commit is contained in:
Pieter-Jan Briers
2020-08-20 20:33:43 +02:00
808 changed files with 18173 additions and 5666 deletions

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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?

View File

@@ -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)
{

File diff suppressed because it is too large Load Diff

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
{

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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))
{

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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))

View File

@@ -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))
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>());
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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))
{

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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();

View File

@@ -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;
}
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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"];

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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++;
}

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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))

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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;
}

View File

@@ -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]");
}
}
}

View 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;
}
}
}

View File

@@ -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; }
}
}

View File

@@ -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 />

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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;

View File

@@ -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."));

View File

@@ -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))
{

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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