Merge branch 'master' into 2020-08-19-firelocks

# Conflicts:
#	Content.Server/GameObjects/Components/Atmos/AirtightComponent.cs
#	Content.Server/GameObjects/Components/Atmos/GridAtmosphereComponent.cs
#	SpaceStation14.sln.DotSettings
This commit is contained in:
Víctor Aguilera Puerto
2020-08-28 14:35:45 +02:00
494 changed files with 11527 additions and 3829 deletions

View File

@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.AI
{
[RegisterComponent]
public sealed class AiFactionTagComponent : Component
{
public override string Name => "AiFactionTag";
public Faction Factions { get; private set; } = Faction.None;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataReadWriteFunction(
"factions",
new List<Faction>(),
factions => factions.ForEach(faction => Factions |= faction),
() =>
{
var writeFactions = new List<Faction>();
foreach (Faction fac in Enum.GetValues(typeof(Faction)))
{
if ((Factions & fac) != 0)
{
writeFactions.Add(fac);
}
}
return writeFactions;
});
}
}
[Flags]
public enum Faction
{
None = 0,
NanoTransen = 1 << 0,
SimpleHostile = 1 << 1,
SimpleNeutral = 1 << 2,
Syndicate = 1 << 3,
Xeno = 1 << 4,
}
}

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Access;
using Content.Shared.GameObjects.Components.Access;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -15,6 +17,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Access
{
@@ -22,16 +25,13 @@ namespace Content.Server.GameObjects.Components.Access
[ComponentReference(typeof(IActivate))]
public class IdCardConsoleComponent : SharedIdCardConsoleComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private BoundUserInterface _userInterface;
private ContainerSlot _privilegedIdContainer;
private ContainerSlot _targetIdContainer;
private AccessReader _accessReader;
private ContainerSlot _privilegedIdContainer = default!;
private ContainerSlot _targetIdContainer = default!;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(IdCardConsoleUiKey.Key);
public override void Initialize()
{
@@ -40,16 +40,30 @@ namespace Content.Server.GameObjects.Components.Access
_privilegedIdContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-privilegedId", Owner);
_targetIdContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-targetId", Owner);
_accessReader = Owner.GetComponent<AccessReader>();
if (!Owner.EnsureComponent(out AccessReader _))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(AccessReader)}");
}
if (UserInterface == null)
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} doesn't have a {nameof(ServerUserInterfaceComponent)}");
}
else
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(IdCardConsoleUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
UpdateUserInterface();
}
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
switch (obj.Message)
{
case IdButtonPressedMessage msg:
@@ -72,13 +86,19 @@ namespace Content.Server.GameObjects.Components.Access
}
/// <summary>
/// Returns true if there is an ID in <see cref="_privilegedIdContainer"/> and said ID satisfies the requirements of <see cref="_accessReader"/>.
/// Returns true if there is an ID in <see cref="_privilegedIdContainer"/> and said ID satisfies the requirements of <see cref="AccessReader"/>.
/// </summary>
private bool PrivilegedIdIsAuthorized()
{
if (!Owner.TryGetComponent(out AccessReader? reader))
{
return true;
}
var privilegedIdEntity = _privilegedIdContainer.ContainedEntity;
return privilegedIdEntity != null && _accessReader.IsAllowed(privilegedIdEntity);
return privilegedIdEntity != null && reader.IsAllowed(privilegedIdEntity);
}
/// <summary>
/// Called when the "Submit" button in the UI gets pressed.
/// Writes data passed from the UI into the ID stored in <see cref="_targetIdContainer"/>, if present.
@@ -110,9 +130,9 @@ namespace Content.Server.GameObjects.Components.Access
/// </summary>
private void HandleId(IEntity user, ContainerSlot container)
{
if (!user.TryGetComponent(out IHandsComponent hands))
if (!user.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You have no hands."));
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You have no hands."));
return;
}
@@ -133,9 +153,15 @@ namespace Content.Server.GameObjects.Components.Access
{
return;
}
if(!hands.Drop(hands.ActiveHand, container))
if (hands.ActiveHand == null)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, _localizationManager.GetString("You can't let go of the ID card!"));
return;
}
if (!hands.Drop(hands.ActiveHand, container))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, user, Loc.GetString("You can't let go of the ID card!"));
return;
}
UpdateUserInterface();
@@ -185,17 +211,17 @@ namespace Content.Server.GameObjects.Components.Access
_privilegedIdContainer.ContainedEntity?.Name ?? "",
_targetIdContainer.ContainedEntity?.Name ?? "");
}
_userInterface.SetState(newState);
UserInterface?.SetState(newState);
}
public void Activate(ActivateEventArgs eventArgs)
{
if(!eventArgs.User.TryGetComponent(out IActorComponent actor))
if(!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
}

View File

@@ -0,0 +1,355 @@

using Robust.Server.GameObjects;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Robust.Shared.ViewVariables;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects.Components.ActionBlocking;
using Content.Shared.GameObjects.Verbs;
using Content.Server.GameObjects.Components.Items.Storage;
using Robust.Shared.Log;
using System.Linq;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.GameObjects.EntitySystems;
using Content.Server.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.Components.Mobs;
using Robust.Shared.Maths;
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.GUI;
namespace Content.Server.GameObjects.Components.ActionBlocking
{
[RegisterComponent]
public class CuffableComponent : SharedCuffableComponent
{
[Dependency]
private readonly ISharedNotifyManager _notifyManager;
/// <summary>
/// How many of this entity's hands are currently cuffed.
/// </summary>
[ViewVariables]
public int CuffedHandCount => _container.ContainedEntities.Count * 2;
protected IEntity LastAddedCuffs => _container.ContainedEntities[_container.ContainedEntities.Count - 1];
public IReadOnlyList<IEntity> StoredEntities => _container.ContainedEntities;
/// <summary>
/// Container of various handcuffs currently applied to the entity.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
private Container _container = default!;
private float _interactRange;
private IHandsComponent _hands;
public event Action OnCuffedStateChanged;
public override void Initialize()
{
base.Initialize();
_container = ContainerManagerComponent.Ensure<Container>(Name, Owner);
_interactRange = SharedInteractionSystem.InteractionRange / 2;
Owner.EntityManager.EventBus.SubscribeEvent<HandCountChangedEvent>(EventSource.Local, this, HandleHandCountChange);
if (!Owner.TryGetComponent(out _hands))
{
Logger.Warning("Player does not have an IHandsComponent!");
}
}
public override ComponentState GetComponentState()
{
// there are 2 approaches i can think of to handle the handcuff overlay on players
// 1 - make the current RSI the handcuff type that's currently active. all handcuffs on the player will appear the same.
// 2 - allow for several different player overlays for each different cuff type.
// approach #2 would be more difficult/time consuming to do and the payoff doesn't make it worth it.
// right now we're doing approach #1.
if (CuffedHandCount > 0)
{
if (LastAddedCuffs.TryGetComponent<HandcuffComponent>(out var cuffs))
{
return new CuffableComponentState(CuffedHandCount,
CanStillInteract,
cuffs.CuffedRSI,
$"{cuffs.OverlayIconState}-{CuffedHandCount}",
cuffs.Color);
// the iconstate is formatted as blah-2, blah-4, blah-6, etc.
// the number corresponds to how many hands are cuffed.
}
}
return new CuffableComponentState(CuffedHandCount,
CanStillInteract,
"/Objects/Misc/handcuffs.rsi",
"body-overlay-2",
Color.White);
}
/// <summary>
/// Add a set of cuffs to an existing CuffedComponent.
/// </summary>
/// <param name="prototype"></param>
public void AddNewCuffs(IEntity handcuff)
{
if (!handcuff.HasComponent<HandcuffComponent>())
{
Logger.Warning($"Handcuffs being applied to player are missing a {nameof(HandcuffComponent)}!");
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
handcuff.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{
Logger.Warning("Handcuffs being applied to player are obstructed or too far away! This should not happen!");
return;
}
_container.Insert(handcuff);
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
OnCuffedStateChanged.Invoke();
UpdateStatusEffect();
UpdateHeldItems();
Dirty();
}
/// <summary>
/// Check the current amount of hands the owner has, and if there's less hands than active cuffs we remove some cuffs.
/// </summary>
private void UpdateHandCount()
{
var dirty = false;
var handCount = _hands.Hands.Count();
while (CuffedHandCount > handCount && CuffedHandCount > 0)
{
dirty = true;
var entity = _container.ContainedEntities[_container.ContainedEntities.Count - 1];
_container.Remove(entity);
entity.Transform.WorldPosition = Owner.Transform.GridPosition.Position;
}
if (dirty)
{
CanStillInteract = handCount > CuffedHandCount;
OnCuffedStateChanged.Invoke();
Dirty();
}
}
private void HandleHandCountChange(HandCountChangedEvent message)
{
if (message.Sender == Owner)
{
UpdateHandCount();
}
}
/// <summary>
/// Check how many items the user is holding and if it's more than the number of cuffed hands, drop some items.
/// </summary>
public void UpdateHeldItems()
{
var itemCount = _hands.GetAllHeldItems().Count();
var freeHandCount = _hands.Hands.Count() - CuffedHandCount;
if (freeHandCount < itemCount)
{
foreach (ItemComponent item in _hands.GetAllHeldItems())
{
if (freeHandCount < itemCount)
{
freeHandCount++;
_hands.Drop(item.Owner);
}
else
{
break;
}
}
}
}
/// <summary>
/// Updates the status effect indicator on the HUD.
/// </summary>
private void UpdateStatusEffect()
{
if (Owner.TryGetComponent(out ServerStatusEffectsComponent status))
{
status.ChangeStatusEffectIcon(StatusEffect.Cuffed,
CanStillInteract ? "/Textures/Interface/StatusEffects/Handcuffed/Uncuffed.png" : "/Textures/Interface/StatusEffects/Handcuffed/Handcuffed.png");
}
}
/// <summary>
/// Attempt to uncuff a cuffed entity. Can be called by the cuffed entity, or another entity trying to help uncuff them.
/// If the uncuffing succeeds, the cuffs will drop on the floor.
/// </summary>
/// <param name="user">The cuffed entity</param>
/// <param name="cuffsToRemove">Optional param for the handcuff entity to remove from the cuffed entity. If null, uses the most recently added handcuff entity.</param>
public async void TryUncuff(IEntity user, IEntity cuffsToRemove = null)
{
var isOwner = user == Owner;
if (cuffsToRemove == null)
{
cuffsToRemove = LastAddedCuffs;
}
else
{
if (!_container.ContainedEntities.Contains(cuffsToRemove))
{
Logger.Warning("A user is trying to remove handcuffs that aren't in the owner's container. This should never happen!");
}
}
if (!cuffsToRemove.TryGetComponent<HandcuffComponent>(out var cuff))
{
Logger.Warning($"A user is trying to remove handcuffs without a {nameof(HandcuffComponent)}. This should never happen!");
return;
}
if (!isOwner && !ActionBlockerSystem.CanInteract(user))
{
user.PopupMessage(user, Loc.GetString("You can't do that!"));
return;
}
if (!isOwner &&
!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
user.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{
user.PopupMessage(user, Loc.GetString("You are too far away to remove the cuffs."));
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
cuffsToRemove.Transform.MapPosition,
Owner.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{
Logger.Warning("Handcuffs being removed from player are obstructed or too far away! This should not happen!");
return;
}
user.PopupMessage(user, Loc.GetString("You start removing the cuffs."));
var audio = EntitySystem.Get<AudioSystem>();
audio.PlayFromEntity(isOwner ? cuff.StartBreakoutSound : cuff.StartUncuffSound, Owner);
var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;
var doAfterEventArgs = new DoAfterEventArgs(user, uncuffTime)
{
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true
};
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
var result = await doAfterSystem.DoAfter(doAfterEventArgs);
if (result != DoAfterStatus.Cancelled)
{
audio.PlayFromEntity(cuff.EndUncuffSound, Owner);
_container.ForceRemove(cuffsToRemove);
cuffsToRemove.Transform.AttachToGridOrMap();
cuffsToRemove.Transform.WorldPosition = Owner.Transform.WorldPosition;
if (cuff.BreakOnRemove)
{
cuff.Broken = true;
cuffsToRemove.Name = cuff.BrokenName;
cuffsToRemove.Description = cuff.BrokenDesc;
if (cuffsToRemove.TryGetComponent<SpriteComponent>(out var sprite))
{
sprite.LayerSetState(0, cuff.BrokenState); // TODO: safety check to see if RSI contains the state?
}
}
CanStillInteract = _hands.Hands.Count() > CuffedHandCount;
OnCuffedStateChanged.Invoke();
UpdateStatusEffect();
Dirty();
if (CuffedHandCount == 0)
{
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs."));
if (!isOwner)
{
_notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} uncuffs your hands.", user));
}
}
else
{
if (!isOwner)
{
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of {1:theName}'s hands remain cuffed.", CuffedHandCount, user));
_notifyManager.PopupMessage(user, Owner, Loc.GetString("{0:theName} removes your cuffs. {1} of your hands remain cuffed.", user, CuffedHandCount));
}
else
{
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully remove the cuffs. {0} of your hands remain cuffed.", CuffedHandCount));
}
}
}
else
{
_notifyManager.PopupMessage(user, user, Loc.GetString("You fail to remove the cuffs."));
}
return;
}
/// <summary>
/// Allows the uncuffing of a cuffed person. Used by other people and by the component owner to break out of cuffs.
/// </summary>
[Verb]
private sealed class UncuffVerb : Verb<CuffableComponent>
{
protected override void GetData(IEntity user, CuffableComponent component, VerbData data)
{
if ((user != component.Owner && !ActionBlockerSystem.CanInteract(user)) || component.CuffedHandCount == 0)
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Text = Loc.GetString("Uncuff");
}
protected override void Activate(IEntity user, CuffableComponent component)
{
if (component.CuffedHandCount > 0)
{
component.TryUncuff(user);
}
}
}
}
}

View File

@@ -0,0 +1,248 @@
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Content.Server.GameObjects.Components.GUI;
using Robust.Shared.Serialization;
using Robust.Shared.Log;
using Robust.Shared.Localization;
using Robust.Shared.ViewVariables;
using Robust.Server.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Components.ActionBlocking;
using Content.Server.GameObjects.Components.Mobs;
using Robust.Shared.Maths;
using System;
namespace Content.Server.GameObjects.Components.ActionBlocking
{
[RegisterComponent]
public class HandcuffComponent : SharedHandcuffComponent, IAfterInteract
{
[Dependency]
private readonly ISharedNotifyManager _notifyManager;
/// <summary>
/// The time it takes to apply a <see cref="CuffedComponent"/> to an entity.
/// </summary>
[ViewVariables]
public float CuffTime { get; set; }
/// <summary>
/// The time it takes to remove a <see cref="CuffedComponent"/> from an entity.
/// </summary>
[ViewVariables]
public float UncuffTime { get; set; }
/// <summary>
/// The time it takes for a cuffed entity to remove <see cref="CuffedComponent"/> from itself.
/// </summary>
[ViewVariables]
public float BreakoutTime { get; set; }
/// <summary>
/// If an entity being cuffed is stunned, this amount of time is subtracted from the time it takes to add/remove their cuffs.
/// </summary>
[ViewVariables]
public float StunBonus { get; set; }
/// <summary>
/// Will the cuffs break when removed?
/// </summary>
[ViewVariables]
public bool BreakOnRemove { get; set; }
/// <summary>
/// The path of the RSI file used for the player cuffed overlay.
/// </summary>
[ViewVariables]
public string CuffedRSI { get; set; }
/// <summary>
/// The iconstate used with the RSI file for the player cuffed overlay.
/// </summary>
[ViewVariables]
public string OverlayIconState { get; set; }
/// <summary>
/// The iconstate used for broken handcuffs
/// </summary>
[ViewVariables]
public string BrokenState { get; set; }
/// <summary>
/// The iconstate used for broken handcuffs
/// </summary>
[ViewVariables]
public string BrokenName { get; set; }
/// <summary>
/// The iconstate used for broken handcuffs
/// </summary>
[ViewVariables]
public string BrokenDesc { get; set; }
[ViewVariables]
public bool Broken
{
get
{
return _isBroken;
}
set
{
if (_isBroken != value)
{
_isBroken = value;
Dirty();
}
}
}
public string StartCuffSound { get; set; }
public string EndCuffSound { get; set; }
public string StartBreakoutSound { get; set; }
public string StartUncuffSound { get; set; }
public string EndUncuffSound { get; set; }
public Color Color { get; set; }
// Non-exposed data fields
private bool _isBroken = false;
private float _interactRange;
private DoAfterSystem _doAfterSystem;
private AudioSystem _audioSystem;
public override void Initialize()
{
base.Initialize();
_audioSystem = EntitySystem.Get<AudioSystem>();
_doAfterSystem = EntitySystem.Get<DoAfterSystem>();
_interactRange = SharedInteractionSystem.InteractionRange / 2;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => x.CuffTime, "cuffTime", 5.0f);
serializer.DataField(this, x => x.BreakoutTime, "breakoutTime", 30.0f);
serializer.DataField(this, x => x.UncuffTime, "uncuffTime", 5.0f);
serializer.DataField(this, x => x.StunBonus, "stunBonus", 2.0f);
serializer.DataField(this, x => x.StartCuffSound, "startCuffSound", "/Audio/Items/Handcuffs/cuff_start.ogg");
serializer.DataField(this, x => x.EndCuffSound, "endCuffSound", "/Audio/Items/Handcuffs/cuff_end.ogg");
serializer.DataField(this, x => x.StartUncuffSound, "startUncuffSound", "/Audio/Items/Handcuffs/cuff_takeoff_start.ogg");
serializer.DataField(this, x => x.EndUncuffSound, "endUncuffSound", "/Audio/Items/Handcuffs/cuff_takeoff_end.ogg");
serializer.DataField(this, x => x.StartBreakoutSound, "startBreakoutSound", "/Audio/Items/Handcuffs/cuff_breakout_start.ogg");
serializer.DataField(this, x => x.CuffedRSI, "cuffedRSI", "Objects/Misc/handcuffs.rsi");
serializer.DataField(this, x => x.OverlayIconState, "bodyIconState", "body-overlay");
serializer.DataField(this, x => x.Color, "color", Color.White);
serializer.DataField(this, x => x.BreakOnRemove, "breakOnRemove", false);
serializer.DataField(this, x => x.BrokenState, "brokenIconState", string.Empty);
serializer.DataField(this, x => x.BrokenName, "brokenName", string.Empty);
serializer.DataField(this, x => x.BrokenDesc, "brokenDesc", string.Empty);
}
public override ComponentState GetComponentState()
{
return new HandcuffedComponentState(Broken ? BrokenState : string.Empty);
}
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent<CuffableComponent>(out var cuffed))
{
return;
}
if (eventArgs.Target == eventArgs.User)
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't cuff yourself!"));
return;
}
if (Broken)
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("The cuffs are broken!"));
return;
}
if (!eventArgs.Target.TryGetComponent<HandsComponent>(out var hands))
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no hands!", eventArgs.Target));
return;
}
if (cuffed.CuffedHandCount == hands.Count)
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("{0:theName} has no free hands to handcuff!", eventArgs.Target));
return;
}
if (!EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(
eventArgs.User.Transform.MapPosition,
eventArgs.Target.Transform.MapPosition,
_interactRange,
ignoredEnt: Owner))
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You are too far away to use the cuffs!"));
return;
}
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target));
_notifyManager.PopupMessage(eventArgs.User, eventArgs.Target, Loc.GetString("{0:theName} starts cuffing you!", eventArgs.User));
_audioSystem.PlayFromEntity(StartCuffSound, Owner);
TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed);
}
/// <summary>
/// Update the cuffed state of an entity
/// </summary>
private async void TryUpdateCuff(IEntity user, IEntity target, CuffableComponent cuffs)
{
var cuffTime = CuffTime;
if (target.TryGetComponent<StunnableComponent>(out var stun) && stun.Stunned)
{
cuffTime = MathF.Max(0.1f, cuffTime - StunBonus);
}
var doAfterEventArgs = new DoAfterEventArgs(user, cuffTime, default, target)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnDamage = true,
BreakOnStun = true,
NeedHand = true
};
var result = await _doAfterSystem.DoAfter(doAfterEventArgs);
if (result != DoAfterStatus.Cancelled)
{
_audioSystem.PlayFromEntity(EndCuffSound, Owner);
_notifyManager.PopupMessage(user, user, Loc.GetString("You successfully cuff {0:theName}.", target));
_notifyManager.PopupMessage(target, target, Loc.GetString("You have been cuffed by {0:theName}!", user));
if (user.TryGetComponent<HandsComponent>(out var hands))
{
hands.Drop(Owner);
cuffs.AddNewCuffs(Owner);
}
else
{
Logger.Warning("Unable to remove handcuffs from player's hands! This should not happen!");
}
}
else
{
user.PopupMessage(user, Loc.GetString("You were interrupted while cuffing {0:theName}!", target));
target.PopupMessage(target, Loc.GetString("You interrupt {0:theName} while they are cuffing you!", user));
}
}
}
}

View File

@@ -1,9 +1,12 @@
using System;
#nullable enable
using Content.Server.GameObjects.EntitySystems;
using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -13,7 +16,8 @@ namespace Content.Server.GameObjects.Components.Atmos
[RegisterComponent]
public class AirtightComponent : Component, IMapInit
{
private SnapGridComponent _snapGrid;
[Dependency] private readonly IMapManager _mapManager = default!;
private (GridId, MapIndices) _lastPosition;
public override string Name => "Airtight";
@@ -28,7 +32,11 @@ namespace Content.Server.GameObjects.Components.Atmos
set
{
_airBlocked = value;
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(Owner.Transform.GridID)?.Revalidate(_snapGrid.Position);
if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
{
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(Owner.Transform.GridID)?.Invalidate(snapGrid.Position);
}
}
}
@@ -48,19 +56,23 @@ namespace Content.Server.GameObjects.Components.Atmos
base.Initialize();
// Using the SnapGrid is critical for the performance of the room builder, and thus if
// it is absent the component will not be airtight. An exception is much easier to track
// down than the object magically not being airtight, so throw one if the SnapGrid component
// it is absent the component will not be airtight. A warning is much easier to track
// down than the object magically not being airtight, so log one if the SnapGrid component
// is missing.
if (!Owner.TryGetComponent(out _snapGrid))
throw new Exception("Airtight entities must have a SnapGrid component");
if (!Owner.EnsureComponent(out SnapGridComponent _))
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition.ToString()} didn't have a {nameof(SnapGridComponent)}");
UpdatePosition();
}
public void MapInit()
{
_snapGrid.OnPositionChanged += OnTransformMove;
_lastPosition = (Owner.Transform.GridID, _snapGrid.Position);
if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
{
snapGrid.OnPositionChanged += OnTransformMove;
_lastPosition = (Owner.Transform.GridID, snapGrid.Position);
}
UpdatePosition();
}
@@ -70,11 +82,16 @@ namespace Content.Server.GameObjects.Components.Atmos
_airBlocked = false;
_snapGrid.OnPositionChanged -= OnTransformMove;
if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
{
snapGrid.OnPositionChanged -= OnTransformMove;
}
if(_fixVacuum)
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(Owner.Transform.GridID)?
.FixVacuum(_snapGrid.Position);
if (_fixVacuum)
{
var mapIndices = Owner.Transform.GridPosition.ToMapIndices(_mapManager);
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(Owner.Transform.GridID)?.FixVacuum(mapIndices);
}
UpdatePosition();
}
@@ -83,15 +100,22 @@ namespace Content.Server.GameObjects.Components.Atmos
{
UpdatePosition(_lastPosition.Item1, _lastPosition.Item2);
UpdatePosition();
_lastPosition = (Owner.Transform.GridID, _snapGrid.Position);
if (Owner.TryGetComponent(out SnapGridComponent? snapGrid))
{
_lastPosition = (Owner.Transform.GridID, snapGrid.Position);
}
}
private void UpdatePosition() => UpdatePosition(Owner.Transform.GridID, _snapGrid.Position);
private void UpdatePosition()
{
var mapIndices = Owner.Transform.GridPosition.ToMapIndices(_mapManager);
UpdatePosition(Owner.Transform.GridID, mapIndices);
}
private void UpdatePosition(GridId gridId, MapIndices pos)
{
EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(gridId)?.Invalidate(pos);
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Atmos;
using Content.Shared.GameObjects.Components;
using Content.Shared.GameObjects.EntitySystems;
@@ -16,30 +17,32 @@ using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
{
[RegisterComponent]
public class GasAnalyzerComponent : SharedGasAnalyzerComponent, IAfterInteract, IDropped, IUse
{
#pragma warning disable 649
[Dependency] private IServerNotifyManager _notifyManager = default!;
[Dependency] private IMapManager _mapManager = default!;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
private BoundUserInterface _userInterface = default!;
private GasAnalyzerDanger _pressureDanger;
private float _timeSinceSync;
private const float TimeBetweenSyncs = 2f;
private bool _checkPlayer = false; // Check at the player pos or at some other tile?
private GridCoordinates? _position; // The tile that we scanned
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GasAnalyzerUiKey.Key);
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GasAnalyzerUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
}
}
public override ComponentState GetComponentState()
@@ -56,7 +59,7 @@ namespace Content.Server.GameObjects.Components.Atmos
{
_checkPlayer = true;
_position = null;
_userInterface.Open(session);
UserInterface?.Open(session);
UpdateUserInterface();
Resync();
}
@@ -71,7 +74,7 @@ namespace Content.Server.GameObjects.Components.Atmos
{
_checkPlayer = false;
_position = pos;
_userInterface.Open(session);
UserInterface?.Open(session);
UpdateUserInterface();
Resync();
}
@@ -79,7 +82,7 @@ namespace Content.Server.GameObjects.Components.Atmos
public void CloseInterface(IPlayerSession session)
{
_position = null;
_userInterface.Close(session);
UserInterface?.Close(session);
Resync();
}
@@ -123,10 +126,15 @@ namespace Content.Server.GameObjects.Components.Atmos
private void UpdateUserInterface()
{
if (UserInterface == null)
{
return;
}
string? error = null;
// Check if the player is still holding the gas analyzer => if not, don't update
foreach (var session in _userInterface.SubscribedSessions)
foreach (var session in UserInterface.SubscribedSessions)
{
if (session.AttachedEntity == null)
return;
@@ -151,12 +159,13 @@ namespace Content.Server.GameObjects.Components.Atmos
pos = _position.Value;
}
var gam = EntitySystem.Get<AtmosphereSystem>().GetGridAtmosphere(pos.GridID);
var atmosSystem = EntitySystem.Get<AtmosphereSystem>();
var gam = atmosSystem.GetGridAtmosphere(pos.GridID);
var tile = gam?.GetTile(pos).Air;
if (tile == null)
{
error = "No Atmosphere!";
_userInterface.SetState(
UserInterface.SetState(
new GasAnalyzerBoundUserInterfaceState(
0,
0,
@@ -166,16 +175,17 @@ namespace Content.Server.GameObjects.Components.Atmos
}
var gases = new List<GasEntry>();
for (int i = 0; i < Atmospherics.TotalNumberOfGases; i++)
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
{
var gas = Atmospherics.GetGas(i);
var gas = atmosSystem.GetGas(i);
if (tile.Gases[i] <= Atmospherics.GasMinMoles) continue;
gases.Add(new GasEntry(gas.Name, tile.Gases[i], gas.Color));
}
_userInterface.SetState(
UserInterface.SetState(
new GasAnalyzerBoundUserInterfaceState(
tile.Pressure,
tile.Temperature,

View File

@@ -6,16 +6,23 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Atmos
{
[RegisterComponent]
public class GasMixtureComponent : Component
public class GasMixtureHolderComponent : Component
{
public override string Name => "GasMixture";
public override string Name => "GasMixtureHolder";
[ViewVariables] public GasMixture GasMixture { get; set; } = new GasMixture();
[ViewVariables] public GasMixture GasMixture { get; set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, x => GasMixture.Volume, "volume", 0f);
GasMixture = new GasMixture();
serializer.DataReadWriteFunction(
"volume",
0f,
vol => GasMixture.Volume = vol,
() => GasMixture.Volume);
}
}
}

View File

@@ -1,9 +1,12 @@
using System;
#nullable enable
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.Atmos.Piping;
using Content.Server.GameObjects.Components.NodeContainer.NodeGroups;
using Content.Shared.Atmos;
using Content.Shared.Maps;
using Robust.Server.Interfaces.GameObjects;
@@ -33,7 +36,7 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <summary>
/// Check current execution time every n instances processed.
/// </summary>
private const int LagCheckIterations = 15;
private const int LagCheckIterations = 30;
/// <summary>
/// Max milliseconds allowed for atmos updates.
@@ -47,32 +50,94 @@ namespace Content.Server.GameObjects.Components.Atmos
public override string Name => "GridAtmosphere";
private bool _paused = false;
private float _timer = 0f;
private Stopwatch _stopwatch = new Stopwatch();
[ViewVariables]
public int UpdateCounter { get; private set; } = 0;
private IMapGrid _grid;
[ViewVariables]
private double _tileEqualizeLastProcess;
[ViewVariables]
private readonly HashSet<ExcitedGroup> _excitedGroups = new HashSet<ExcitedGroup>(1000);
[ViewVariables]
private int ExcitedGroupCount => _excitedGroups.Count;
[ViewVariables]
private double _excitedGroupLastProcess;
[ViewVariables]
private readonly Dictionary<MapIndices, TileAtmosphere> _tiles = new Dictionary<MapIndices, TileAtmosphere>(1000);
[ViewVariables]
private readonly HashSet<TileAtmosphere> _activeTiles = new HashSet<TileAtmosphere>(1000);
[ViewVariables]
private int ActiveTilesCount => _activeTiles.Count;
[ViewVariables]
private double _activeTilesLastProcess;
[ViewVariables]
private readonly HashSet<TileAtmosphere> _hotspotTiles = new HashSet<TileAtmosphere>(1000);
[ViewVariables]
private int HotspotTilesCount => _hotspotTiles.Count;
[ViewVariables]
private double _hotspotsLastProcess;
[ViewVariables]
private readonly HashSet<TileAtmosphere> _superconductivityTiles = new HashSet<TileAtmosphere>(1000);
[ViewVariables]
private int SuperconductivityTilesCount => _superconductivityTiles.Count;
[ViewVariables]
private double _superconductivityLastProcess;
[ViewVariables]
private readonly HashSet<MapIndices> _invalidatedCoords = new HashSet<MapIndices>(1000);
[ViewVariables]
private int InvalidatedCoordsCount => _invalidatedCoords.Count;
[ViewVariables]
private HashSet<TileAtmosphere> _highPressureDelta = new HashSet<TileAtmosphere>(1000);
[ViewVariables]
private int HighPressureDeltaCount => _highPressureDelta.Count;
[ViewVariables]
private double _highPressureDeltaLastProcess;
[ViewVariables]
private readonly HashSet<IPipeNet> _pipeNets = new HashSet<IPipeNet>();
[ViewVariables]
private double _pipeNetLastProcess;
[ViewVariables]
private readonly HashSet<PipeNetDeviceComponent> _pipeNetDevices = new HashSet<PipeNetDeviceComponent>();
[ViewVariables]
private double _pipeNetDevicesLastProcess;
[ViewVariables]
private Queue<TileAtmosphere> _currentRunTiles = new Queue<TileAtmosphere>();
[ViewVariables]
private Queue<ExcitedGroup> _currentRunExcitedGroups = new Queue<ExcitedGroup>();
[ViewVariables]
private Queue<IPipeNet> _currentRunPipeNet = new Queue<IPipeNet>();
[ViewVariables]
private Queue<PipeNetDeviceComponent> _currentRunPipeNetDevice = new Queue<PipeNetDeviceComponent>();
[ViewVariables]
private ProcessState _state = ProcessState.TileEqualize;
@@ -84,47 +149,47 @@ namespace Content.Server.GameObjects.Components.Atmos
HighPressureDelta,
Hotspots,
Superconductivity,
PipeNet,
PipeNetDevices,
}
/// <inheritdoc />
public void PryTile(MapIndices indices)
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGridComponent)) return;
if (IsSpace(indices) || IsAirBlocked(indices)) return;
var tile = _grid.GetTileRef(indices).Tile;
var mapGrid = mapGridComponent.Grid;
var tile = mapGrid.GetTileRef(indices).Tile;
var tileDefinitionManager = IoCManager.Resolve<ITileDefinitionManager>();
var tileDef = (ContentTileDefinition)tileDefinitionManager[tile.TypeId];
var underplating = tileDefinitionManager["underplating"];
_grid.SetTile(indices, new Tile(underplating.TileId));
mapGrid.SetTile(indices, new Tile(underplating.TileId));
//Actually spawn the relevant tile item at the right position and give it some offset to the corner.
var tileItem = IoCManager.Resolve<IServerEntityManager>().SpawnEntity(tileDef.ItemDropPrototypeName, new GridCoordinates(indices.X, indices.Y, _grid));
var tileItem = IoCManager.Resolve<IServerEntityManager>().SpawnEntity(tileDef.ItemDropPrototypeName, new GridCoordinates(indices.X, indices.Y, mapGrid));
tileItem.Transform.WorldPosition += (0.2f, 0.2f);
}
public override void Initialize()
{
base.Initialize();
_grid = Owner.GetComponent<IMapGridComponent>().Grid;
RepopulateTiles();
}
public override void OnAdd()
{
base.OnAdd();
_grid = Owner.GetComponent<IMapGridComponent>().Grid;
RepopulateTiles();
}
public void RepopulateTiles()
{
foreach (var tile in _grid.GetAllTiles())
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
foreach (var tile in mapGrid.Grid.GetAllTiles())
{
if(!_tiles.ContainsKey(tile.GridIndices))
_tiles.Add(tile.GridIndices, new TileAtmosphere(this, tile.GridIndex, tile.GridIndices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C}));
@@ -145,69 +210,66 @@ namespace Content.Server.GameObjects.Components.Atmos
private void Revalidate()
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
foreach (var indices in _invalidatedCoords.ToArray())
{
Revalidate(indices);
var tile = GetTile(indices);
AddActiveTile(tile);
if (tile == null)
{
tile = new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C});
_tiles[indices] = tile;
}
if (IsSpace(indices))
{
tile.Air = new GasMixture(GetVolumeForCells(1));
tile.Air.MarkImmutable();
_tiles[indices] = tile;
} else if (IsAirBlocked(indices))
{
tile.Air = null;
}
else
{
var obs = GetObstructingComponent(indices);
if (obs != null)
{
if (tile.Air == null && obs.FixVacuum)
{
FixVacuum(tile.GridIndices);
}
}
tile.Air ??= new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
}
tile.UpdateAdjacent();
tile.UpdateVisuals();
for (var i = 0; i < Atmospherics.Directions; i++)
{
var direction = (AtmosDirection) (1 << i);
var otherIndices = indices.Offset(direction.ToDirection());
var otherTile = GetTile(otherIndices);
AddActiveTile(otherTile);
otherTile?.UpdateAdjacent(direction.GetOpposite());
}
}
_invalidatedCoords.Clear();
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Revalidate(MapIndices indices)
{
var tile = GetTile(indices);
AddActiveTile(tile);
if (tile == null)
{
tile = new TileAtmosphere(this, _grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C});
_tiles[indices] = tile;
}
if (IsSpace(indices))
{
tile.Air = new GasMixture(GetVolumeForCells(1));
tile.Air.MarkImmutable();
_tiles[indices] = tile;
} else if (IsAirBlocked(indices))
{
tile.Air = null;
}
else
{
var obs = GetObstructingComponent(indices);
if (obs != null)
{
if (tile.Air == null && obs.FixVacuum)
{
FixVacuum(tile.GridIndices);
}
}
tile.Air ??= new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
}
tile.UpdateAdjacent();
tile.UpdateVisuals();
foreach (var direction in Cardinal)
{
var otherIndices = indices.Offset(direction);
var otherTile = GetTile(otherIndices);
AddActiveTile(otherTile);
otherTile?.UpdateAdjacent(direction.GetOpposite());
}
}
/// <inheritdoc />
public void FixVacuum(MapIndices indices)
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
var tile = GetTile(indices);
if (tile?.GridIndex != _grid.Index) return;
if (tile?.GridIndex != mapGrid.Grid.Index) return;
var adjacent = GetAdjacentTiles(indices);
tile.Air = new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.T20C};
_tiles[indices] = tile;
@@ -224,16 +286,17 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddActiveTile(TileAtmosphere tile)
public void AddActiveTile(TileAtmosphere? tile)
{
if (tile?.GridIndex != _grid.Index || tile?.Air == null) return;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return;
tile.Excited = true;
_activeTiles.Add(tile);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveActiveTile(TileAtmosphere tile)
public void RemoveActiveTile(TileAtmosphere? tile)
{
if (tile == null) return;
_activeTiles.Remove(tile);
@@ -243,27 +306,29 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHotspotTile(TileAtmosphere tile)
public void AddHotspotTile(TileAtmosphere? tile)
{
if (tile?.GridIndex != _grid.Index || tile?.Air == null) return;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index || tile?.Air == null) return;
_hotspotTiles.Add(tile);
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveHotspotTile(TileAtmosphere tile)
public void RemoveHotspotTile(TileAtmosphere? tile)
{
if (tile == null) return;
_hotspotTiles.Remove(tile);
}
public void AddSuperconductivityTile(TileAtmosphere tile)
public void AddSuperconductivityTile(TileAtmosphere? tile)
{
if (tile?.GridIndex != _grid.Index) return;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index) return;
_superconductivityTiles.Add(tile);
}
public void RemoveSuperconductivityTile(TileAtmosphere tile)
public void RemoveSuperconductivityTile(TileAtmosphere? tile)
{
if (tile == null) return;
_superconductivityTiles.Remove(tile);
@@ -271,9 +336,10 @@ namespace Content.Server.GameObjects.Components.Atmos
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddHighPressureDelta(TileAtmosphere tile)
public void AddHighPressureDelta(TileAtmosphere? tile)
{
if (tile?.GridIndex != _grid.Index) return;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return;
if (tile?.GridIndex != mapGrid.Grid.Index) return;
_highPressureDelta.Add(tile);
}
@@ -298,23 +364,43 @@ namespace Content.Server.GameObjects.Components.Atmos
_excitedGroups.Remove(excitedGroup);
}
/// <inheritdoc />
public TileAtmosphere GetTile(GridCoordinates coordinates)
public void AddPipeNet(IPipeNet pipeNet)
{
return GetTile(coordinates.ToMapIndices(_mapManager));
_pipeNets.Add(pipeNet);
}
public void RemovePipeNet(IPipeNet pipeNet)
{
_pipeNets.Remove(pipeNet);
}
public void AddPipeNetDevice(PipeNetDeviceComponent pipeNetDevice)
{
_pipeNetDevices.Add(pipeNetDevice);
}
public void RemovePipeNetDevice(PipeNetDeviceComponent pipeNetDevice)
{
_pipeNetDevices.Remove(pipeNetDevice);
}
/// <inheritdoc />
public TileAtmosphere GetTile(MapIndices indices)
public TileAtmosphere? GetTile(GridCoordinates coordinates, bool createSpace = true)
{
return GetTile(coordinates.ToMapIndices(_mapManager), createSpace);
}
/// <inheritdoc />
public TileAtmosphere? GetTile(MapIndices indices, bool createSpace = true)
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return null;
if (_tiles.TryGetValue(indices, out var tile)) return tile;
// We don't have that tile!
if (IsSpace(indices))
if (IsSpace(indices) && createSpace)
{
var space = new TileAtmosphere(this, _grid.Index, indices, new GasMixture(int.MaxValue){Temperature = Atmospherics.TCMB});
space.Air.MarkImmutable();
return space;
return new TileAtmosphere(this, mapGrid.Grid.Index, indices, new GasMixture(GetVolumeForCells(1)){Temperature = Atmospherics.TCMB}, true);
}
return null;
@@ -331,32 +417,34 @@ namespace Content.Server.GameObjects.Components.Atmos
public bool IsSpace(MapIndices indices)
{
// TODO ATMOS use ContentTileDefinition to define in YAML whether or not a tile is considered space
return _grid.GetTileRef(indices).Tile.IsEmpty;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default;
return mapGrid.Grid.GetTileRef(indices).Tile.IsEmpty;
}
public Dictionary<Direction, TileAtmosphere> GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false)
public Dictionary<AtmosDirection, TileAtmosphere> GetAdjacentTiles(MapIndices indices, bool includeAirBlocked = false)
{
var sides = new Dictionary<Direction, TileAtmosphere>();
foreach (var dir in Cardinal)
var sides = new Dictionary<AtmosDirection, TileAtmosphere>();
for (var i = 0; i < Atmospherics.Directions; i++)
{
var side = indices.Offset(dir);
var direction = (AtmosDirection) (1 << i);
var side = indices.Offset(direction.ToDirection());
var tile = GetTile(side);
if(tile?.Air != null || includeAirBlocked)
sides[dir] = tile;
if (tile != null && (tile.Air != null || includeAirBlocked))
sides[direction] = tile;
}
return sides;
}
/// <inheritdoc />
public int HighPressureDeltaCount => _highPressureDelta.Count;
public long EqualizationQueueCycleControl { get; set; }
/// <inheritdoc />
public float GetVolumeForCells(int cellCount)
{
return _grid.TileSize * cellCount * Atmospherics.CellVolume;
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default;
return mapGrid.Grid.TileSize * cellCount * Atmospherics.CellVolume;
}
/// <inheritdoc />
@@ -376,27 +464,83 @@ namespace Content.Server.GameObjects.Components.Atmos
switch (_state)
{
case ProcessState.TileEqualize:
ProcessTileEqualize();
if (!ProcessTileEqualize(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.ActiveTiles;
return;
case ProcessState.ActiveTiles:
ProcessActiveTiles();
if (!ProcessActiveTiles(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.ExcitedGroups;
return;
case ProcessState.ExcitedGroups:
ProcessExcitedGroups();
if (!ProcessExcitedGroups(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.HighPressureDelta;
return;
case ProcessState.HighPressureDelta:
ProcessHighPressureDelta();
if (!ProcessHighPressureDelta(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.Hotspots;
break;
case ProcessState.Hotspots:
ProcessHotspots();
if (!ProcessHotspots(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.Superconductivity;
break;
case ProcessState.Superconductivity:
ProcessSuperconductivity();
if (!ProcessSuperconductivity(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.PipeNet;
break;
case ProcessState.PipeNet:
if (!ProcessPipeNets(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.PipeNetDevices;
break;
case ProcessState.PipeNetDevices:
if (!ProcessPipeNetDevices(_paused))
{
_paused = true;
return;
}
_paused = false;
_state = ProcessState.TileEqualize;
break;
}
@@ -404,47 +548,71 @@ namespace Content.Server.GameObjects.Components.Atmos
UpdateCounter++;
}
public void ProcessTileEqualize()
public bool ProcessTileEqualize(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_activeTiles);
var number = 0;
foreach (var tile in _activeTiles.ToArray())
while (_currentRunTiles.Count > 0)
{
var tile = _currentRunTiles.Dequeue();
tile.EqualizePressureInZone(UpdateCounter);
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_tileEqualizeLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_tileEqualizeLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
public void ProcessActiveTiles()
public bool ProcessActiveTiles(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_activeTiles);
var number = 0;
foreach (var tile in _activeTiles.ToArray())
while (_currentRunTiles.Count > 0)
{
var tile = _currentRunTiles.Dequeue();
tile.ProcessCell(UpdateCounter);
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_activeTilesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_activeTilesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
public void ProcessExcitedGroups()
public bool ProcessExcitedGroups(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunExcitedGroups = new Queue<ExcitedGroup>(_excitedGroups);
var number = 0;
foreach (var excitedGroup in _excitedGroups.ToArray())
while (_currentRunExcitedGroups.Count > 0)
{
var excitedGroup = _currentRunExcitedGroups.Dequeue();
excitedGroup.BreakdownCooldown++;
excitedGroup.DismantleCooldown++;
@@ -458,17 +626,27 @@ namespace Content.Server.GameObjects.Components.Atmos
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_excitedGroupLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_excitedGroupLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
public void ProcessHighPressureDelta()
public bool ProcessHighPressureDelta(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_highPressureDelta);
var number = 0;
foreach (var tile in _highPressureDelta.ToArray())
while (_currentRunTiles.Count > 0)
{
var tile = _currentRunTiles.Dequeue();
tile.HighPressureMovements();
tile.PressureDifference = 0f;
tile.PressureSpecificTarget = null;
@@ -478,47 +656,129 @@ namespace Content.Server.GameObjects.Components.Atmos
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_highPressureDeltaLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_highPressureDeltaLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private void ProcessHotspots()
private bool ProcessHotspots(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_hotspotTiles);
var number = 0;
foreach (var hotspot in _hotspotTiles.ToArray())
while (_currentRunTiles.Count > 0)
{
var hotspot = _currentRunTiles.Dequeue();
hotspot.ProcessHotspot();
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_hotspotsLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_hotspotsLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private void ProcessSuperconductivity()
private bool ProcessSuperconductivity(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunTiles = new Queue<TileAtmosphere>(_superconductivityTiles);
var number = 0;
foreach (var superconductivity in _superconductivityTiles.ToArray())
while (_currentRunTiles.Count > 0)
{
var superconductivity = _currentRunTiles.Dequeue();
superconductivity.Superconduct();
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
return;
{
_superconductivityLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_superconductivityLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private AirtightComponent GetObstructingComponent(MapIndices indices)
private bool ProcessPipeNets(bool resumed = false)
{
foreach (var v in _grid.GetSnapGridCell(indices, SnapGridOffset.Center))
_stopwatch.Restart();
if(!resumed)
_currentRunPipeNet = new Queue<IPipeNet>(_pipeNets);
var number = 0;
while (_currentRunPipeNet.Count > 0)
{
var pipenet = _currentRunPipeNet.Dequeue();
pipenet.Update();
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
{
_pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_pipeNetLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private bool ProcessPipeNetDevices(bool resumed = false)
{
_stopwatch.Restart();
if(!resumed)
_currentRunPipeNetDevice = new Queue<PipeNetDeviceComponent>(_pipeNetDevices);
var number = 0;
while (_currentRunPipeNet.Count > 0)
{
var device = _currentRunPipeNetDevice.Dequeue();
device.Update();
if (number++ < LagCheckIterations) continue;
number = 0;
// Process the rest next time.
if (_stopwatch.Elapsed.TotalMilliseconds >= LagCheckMaxMilliseconds)
{
_pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return false;
}
}
_pipeNetDevicesLastProcess = _stopwatch.Elapsed.TotalMilliseconds;
return true;
}
private AirtightComponent? GetObstructingComponent(MapIndices indices)
{
if (!Owner.TryGetComponent(out IMapGridComponent? mapGrid)) return default;
foreach (var v in mapGrid.Grid.GetSnapGridCell(indices, SnapGridOffset.Center))
{
if (v.Owner.TryGetComponent<AirtightComponent>(out var ac))
return ac;
@@ -527,12 +787,6 @@ namespace Content.Server.GameObjects.Components.Atmos
return null;
}
private static readonly Direction[] Cardinal =
new []
{
Direction.North, Direction.East, Direction.South, Direction.West
};
public void Dispose()
{
@@ -541,22 +795,24 @@ namespace Content.Server.GameObjects.Components.Atmos
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
if (serializer.Reading)
if (serializer.Reading &&
Owner.TryGetComponent(out IMapGridComponent? mapGrid))
{
var gridId = Owner.GetComponent<IMapGridComponent>().Grid.Index;
var gridId = mapGrid.Grid.Index;
if (!serializer.TryReadDataField("uniqueMixes", out List<GasMixture> uniqueMixes) ||
!serializer.TryReadDataField("tiles", out Dictionary<MapIndices, int> tiles))
if (!serializer.TryReadDataField("uniqueMixes", out List<GasMixture>? uniqueMixes) ||
!serializer.TryReadDataField("tiles", out Dictionary<MapIndices, int>? tiles))
return;
_tiles.Clear();
foreach (var (indices, mix) in tiles)
foreach (var (indices, mix) in tiles!)
{
_tiles.Add(indices, new TileAtmosphere(this, gridId, indices, (GasMixture)uniqueMixes[mix].Clone()));
_tiles.Add(indices, new TileAtmosphere(this, gridId, indices, (GasMixture)uniqueMixes![mix].Clone()));
Invalidate(indices);
}
} else if (serializer.Writing)
}
else if (serializer.Writing)
{
var uniqueMixes = new List<GasMixture>();
var uniqueMixHash = new Dictionary<GasMixture, int>();

View File

@@ -0,0 +1,50 @@
using Content.Server.Atmos;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Log;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Adds itself to a <see cref="IGridAtmosphereComponent"/> to be updated by.
/// TODO: Make compatible with unanchoring/anchoring. Currently assumes that the Owner does not move.
/// </summary>
public abstract class PipeNetDeviceComponent : Component
{
public abstract void Update();
protected IGridAtmosphereComponent JoinedGridAtmos { get; private set; }
public override void Initialize()
{
base.Initialize();
JoinGridAtmos();
}
public override void OnRemove()
{
base.OnRemove();
LeaveGridAtmos();
}
private void JoinGridAtmos()
{
var gridAtmos = EntitySystem.Get<AtmosphereSystem>()
.GetGridAtmosphere(Owner.Transform.GridID);
if (gridAtmos == null)
{
Logger.Error($"{nameof(PipeNetDeviceComponent)} on entity {Owner.Uid} could not find an {nameof(IGridAtmosphereComponent)}.");
return;
}
JoinedGridAtmos = gridAtmos;
JoinedGridAtmos.AddPipeNetDevice(this);
}
private void LeaveGridAtmos()
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
JoinedGridAtmos = null;
}
}
}

View File

@@ -0,0 +1,68 @@
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using System.Linq;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Transfer gas from one <see cref="PipeNode"/> to another.
/// </summary>
public abstract class BasePumpComponent : PipeNetDeviceComponent
{
/// <summary>
/// Needs to be same <see cref="PipeDirection"/> as that of a <see cref="Pipe"/> on this entity.
/// </summary>
[ViewVariables]
private PipeDirection _inletDirection;
/// <summary>
/// Needs to be same <see cref="PipeDirection"/> as that of a <see cref="Pipe"/> on this entity.
/// </summary>
[ViewVariables]
private PipeDirection _outletDirection;
[ViewVariables]
private PipeNode _inletPipe;
[ViewVariables]
private PipeNode _outletPipe;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _inletDirection, "inletDirection", PipeDirection.None);
serializer.DataField(ref _outletDirection, "outletDirection", PipeDirection.None);
}
public override void Initialize()
{
base.Initialize();
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}.");
return;
}
var pipeNodes = container.Nodes.OfType<PipeNode>();
_inletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _inletDirection).FirstOrDefault();
_outletPipe = pipeNodes.Where(pipe => pipe.PipeDirection == _outletDirection).FirstOrDefault();
if (_inletPipe == null | _outletPipe == null)
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BasePumpComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
return;
}
}
public override void Update()
{
PumpGas(_inletPipe.Air, _outletPipe.Air);
}
protected abstract void PumpGas(GasMixture inletGas, GasMixture outletGas);
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Atmos;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Placeholder example of pump functionality.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(BasePumpComponent))]
public class DebugPumpComponent : BasePumpComponent
{
public override string Name => "DebugPump";
protected override void PumpGas(GasMixture inletGas, GasMixture outletGas)
{
outletGas.Merge(inletGas);
inletGas.Clear();
}
}
}

View File

@@ -0,0 +1,52 @@
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
using System.Linq;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Transfers gas from the tile it is on to a <see cref="PipeNode"/>.
/// </summary>
public abstract class BaseSiphonComponent : PipeNetDeviceComponent
{
[ViewVariables]
private PipeNode _scrubberOutlet;
private AtmosphereSystem _atmosSystem;
public override void Initialize()
{
base.Initialize();
_atmosSystem = EntitySystem.Get<AtmosphereSystem>();
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BaseSiphonComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}.");
return;
}
_scrubberOutlet = container.Nodes.OfType<PipeNode>().FirstOrDefault();
if (_scrubberOutlet == null)
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BaseSiphonComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
return;
}
}
public override void Update()
{
var tileAtmos = AtmosHelpers.GetTileAtmosphere(Owner.Transform.GridPosition);
if (tileAtmos == null)
return;
ScrubGas(tileAtmos.Air, _scrubberOutlet.Air);
_atmosSystem.GetGridAtmosphere(Owner.Transform.GridID).Invalidate(tileAtmos.GridIndices);
}
protected abstract void ScrubGas(GasMixture inletGas, GasMixture outletGas);
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Atmos;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Placeholder example of scrubber functionality.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(BaseSiphonComponent))]
public class DebugSiphonComponent : BaseSiphonComponent
{
public override string Name => "DebugSiphon";
protected override void ScrubGas(GasMixture inletGas, GasMixture outletGas)
{
outletGas.Merge(inletGas);
inletGas.Clear();
}
}
}

View File

@@ -0,0 +1,54 @@
using Content.Server.Atmos;
using Content.Server.GameObjects.Components.NodeContainer;
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
using Content.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
using System.Linq;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Transfers gas from a <see cref="PipeNode"/> to the tile it is on.
/// </summary>
public abstract class BaseVentComponent : PipeNetDeviceComponent
{
[ViewVariables]
private PipeNode _ventInlet;
private AtmosphereSystem _atmosSystem;
public override void Initialize()
{
base.Initialize();
_atmosSystem = EntitySystem.Get<AtmosphereSystem>();
if (!Owner.TryGetComponent<NodeContainerComponent>(out var container))
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BaseVentComponent)} on entity {Owner.Uid} did not have a {nameof(NodeContainerComponent)}.");
return;
}
_ventInlet = container.Nodes.OfType<PipeNode>().FirstOrDefault();
if (_ventInlet == null)
{
JoinedGridAtmos?.RemovePipeNetDevice(this);
Logger.Error($"{typeof(BaseVentComponent)} on entity {Owner.Uid} could not find compatible {nameof(PipeNode)}s on its {nameof(NodeContainerComponent)}.");
return;
}
}
public override void Update()
{
var tileAtmos = AtmosHelpers.GetTileAtmosphere(Owner.Transform.GridPosition);
if (tileAtmos == null)
return;
VentGas(_ventInlet.Air, tileAtmos.Air);
_atmosSystem.GetGridAtmosphere(Owner.Transform.GridID).Invalidate(tileAtmos.GridIndices);
}
protected abstract void VentGas(GasMixture inletGas, GasMixture outletGas);
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Atmos;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Atmos.Piping
{
/// <summary>
/// Placeholder example of vent functionality.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(BaseVentComponent))]
public class DebugVentComponent : BaseVentComponent
{
public override string Name => "DebugVent";
protected override void VentGas(GasMixture inletGas, GasMixture outletGas)
{
outletGas.Merge(inletGas);
inletGas.Clear();
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Linq;
#nullable enable
using System.Linq;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.GameObjects;
@@ -19,18 +20,13 @@ namespace Content.Server.GameObjects.Components.BarSign
{
public override string Name => "BarSign";
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly IRobustRandom _robustRandom;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
private string _currentSign;
private PowerReceiverComponent _power;
private SpriteComponent _sprite;
private string? _currentSign;
[ViewVariables(VVAccess.ReadWrite)]
public string CurrentSign
public string? CurrentSign
{
get => _currentSign;
set
@@ -40,6 +36,8 @@ namespace Content.Server.GameObjects.Components.BarSign
}
}
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
private void UpdateSignInfo()
{
if (_currentSign == null)
@@ -53,15 +51,18 @@ namespace Content.Server.GameObjects.Components.BarSign
return;
}
if (!_power.Powered)
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
_sprite.LayerSetState(0, "empty");
_sprite.LayerSetShader(0, "shaded");
}
else
{
_sprite.LayerSetState(0, prototype.Icon);
_sprite.LayerSetShader(0, "unshaded");
if (!Powered)
{
sprite.LayerSetState(0, "empty");
sprite.LayerSetShader(0, "shaded");
}
else
{
sprite.LayerSetState(0, prototype.Icon);
sprite.LayerSetShader(0, "unshaded");
}
}
if (!string.IsNullOrEmpty(prototype.Name))
@@ -80,21 +81,25 @@ namespace Content.Server.GameObjects.Components.BarSign
{
base.Initialize();
_power = Owner.GetComponent<PowerReceiverComponent>();
_sprite = Owner.GetComponent<SpriteComponent>();
_power.OnPowerStateChanged += PowerOnOnPowerStateChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += PowerOnOnPowerStateChanged;
}
UpdateSignInfo();
}
public override void OnRemove()
{
_power.OnPowerStateChanged -= PowerOnOnPowerStateChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged -= PowerOnOnPowerStateChanged;
}
base.OnRemove();
}
private void PowerOnOnPowerStateChanged(object sender, PowerStateEventArgs e)
private void PowerOnOnPowerStateChanged(object? sender, PowerStateEventArgs e)
{
UpdateSignInfo();
}

View File

@@ -18,14 +18,12 @@ using Content.Shared.Body.Template;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Movement;
using Robust.Server.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Reflection;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
@@ -43,11 +41,9 @@ namespace Content.Server.GameObjects.Components.Body
[ComponentReference(typeof(IBodyManagerComponent))]
public class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput
{
#pragma warning disable CS0649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IBodyNetworkFactory _bodyNetworkFactory = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
#pragma warning restore
[ViewVariables] private string _presetName = default!;
@@ -138,11 +134,6 @@ namespace Content.Server.GameObjects.Components.Body
base.Initialize();
LoadBodyPreset(Preset);
foreach (var behavior in Owner.GetAllComponents<IOnHealthChangedBehavior>())
{
HealthChangedEvent += behavior.OnHealthChanged;
}
}
protected override void Startup()

View File

@@ -1,10 +1,14 @@
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using Content.Server.Body;
using Content.Server.Utility;
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;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body
{
@@ -12,32 +16,39 @@ namespace Content.Server.GameObjects.Components.Body
[ComponentReference(typeof(IActivate))]
public class BodyScannerComponent : Component, IActivate
{
private BoundUserInterface _userInterface;
public sealed override string Name => "BodyScanner";
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(BodyScannerUiKey.Key);
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor) ||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor) ||
actor.playerSession.AttachedEntity == null)
{
return;
}
if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent attempt))
if (actor.playerSession.AttachedEntity.TryGetComponent(out BodyManagerComponent? attempt))
{
var state = InterfaceState(attempt.Template, attempt.Parts);
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(BodyScannerUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
if (UserInterface == null)
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} doesn't have a {nameof(ServerUserInterfaceComponent)}");
}
else
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg) { }

View File

@@ -29,7 +29,7 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory
/// </summary>
[ViewVariables] public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume;
[ViewVariables] public GasMixture Air { get; set; } = new GasMixture(6);
[ViewVariables] public GasMixture Air { get; set; }
[ViewVariables] public SolutionComponent Solution => _internalSolution;
@@ -45,6 +45,8 @@ namespace Content.Server.GameObjects.Components.Body.Circulatory
{
base.ExposeData(serializer);
Air = new GasMixture(6);
serializer.DataField(ref _initialMaxVolume, "maxVolume", ReagentUnit.New(250));
}

View File

@@ -1,12 +1,11 @@
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Server.GameObjects.Components.Body.Circulatory;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Nutrition;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -20,25 +19,21 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
[RegisterComponent]
public class StomachComponent : SharedStomachComponent
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
/// <summary>
/// Max volume of internal solution storage
/// </summary>
public ReagentUnit MaxVolume
{
get => _stomachContents.MaxVolume;
set => _stomachContents.MaxVolume = value;
get => Owner.TryGetComponent(out SolutionComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
set
{
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution.MaxVolume = value;
}
}
}
/// <summary>
/// Internal solution storage
/// </summary>
[ViewVariables]
private SolutionComponent _stomachContents;
/// <summary>
/// Initial internal solution storage volume
/// </summary>
@@ -68,20 +63,29 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
{
base.Startup();
_stomachContents = Owner.GetComponent<SolutionComponent>();
_stomachContents.MaxVolume = _initialMaxVolume;
if (!Owner.EnsureComponent(out SolutionComponent solution))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
solution.MaxVolume = _initialMaxVolume;
}
public bool TryTransferSolution(Solution solution)
{
if (!Owner.TryGetComponent(out SolutionComponent? solutionComponent))
{
return false;
}
// TODO: For now no partial transfers. Potentially change by design
if (solution.TotalVolume + _stomachContents.CurrentVolume > _stomachContents.MaxVolume)
if (solution.TotalVolume + solutionComponent.CurrentVolume > solutionComponent.MaxVolume)
{
return false;
}
// Add solution to _stomachContents
_stomachContents.TryAddSolution(solution, false, true);
solutionComponent.TryAddSolution(solution, false, true);
// Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach
foreach (var reagent in solution.Contents)
{
@@ -99,7 +103,8 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
/// <param name="frameTime">The time since the last update in seconds.</param>
public void Update(float frameTime)
{
if (!Owner.TryGetComponent(out BloodstreamComponent bloodstream))
if (!Owner.TryGetComponent(out SolutionComponent? solutionComponent) ||
!Owner.TryGetComponent(out BloodstreamComponent? bloodstream))
{
return;
}
@@ -114,7 +119,7 @@ namespace Content.Server.GameObjects.Components.Body.Digestive
delta.Increment(frameTime);
if (delta.Lifetime > _digestionDelay)
{
_stomachContents.TryRemoveReagent(delta.ReagentId, delta.Quantity);
solutionComponent.TryRemoveReagent(delta.ReagentId, delta.Quantity);
transferSolution.AddReagent(delta.ReagentId, delta.Quantity);
_reagentDeltas.Remove(delta);
}

View File

@@ -1,11 +1,14 @@
using System.Collections.Generic;
#nullable enable
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Server.Body;
using Content.Server.Utility;
using Content.Shared.Body.Surgery;
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;
@@ -21,20 +24,18 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent]
public class DroppedBodyPartComponent : Component, IAfterInteract, IBodyPartContainer
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
#pragma warning restore 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
private BodyManagerComponent _bodyManagerComponentCache;
private BodyManagerComponent? _bodyManagerComponentCache;
private int _idHash;
private IEntity _performerCache;
private BoundUserInterface _userInterface;
private IEntity? _performerCache;
public sealed override string Name => "DroppedBodyPart";
[ViewVariables] public BodyPart ContainedBodyPart { get; private set; }
[ViewVariables] public BodyPart ContainedBodyPart { get; private set; } = default!;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
@@ -48,7 +49,7 @@ namespace Content.Server.GameObjects.Components.Body
_performerCache = null;
_bodyManagerComponentCache = null;
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent bodyManager))
if (eventArgs.Target.TryGetComponent(out BodyManagerComponent? bodyManager))
{
SendBodySlotListToUser(eventArgs, bodyManager);
}
@@ -58,9 +59,10 @@ namespace Content.Server.GameObjects.Components.Body
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
public void TransferBodyPartData(BodyPart data)
@@ -68,7 +70,7 @@ namespace Content.Server.GameObjects.Components.Body
ContainedBodyPart = data;
Owner.Name = Loc.GetString(ContainedBodyPart.Name);
if (Owner.TryGetComponent(out SpriteComponent component))
if (Owner.TryGetComponent(out SpriteComponent? component))
{
component.LayerSetRSI(0, data.RSIPath);
component.LayerSetState(0, data.RSIState);
@@ -91,7 +93,7 @@ namespace Content.Server.GameObjects.Components.Body
foreach (var slot in unoccupiedSlots)
{
if (!bodyManager.TryGetSlotType(slot, out var typeResult) ||
typeResult != ContainedBodyPart.PartType ||
typeResult != ContainedBodyPart?.PartType ||
!bodyManager.TryGetBodyPartConnections(slot, out var parts))
{
continue;
@@ -129,7 +131,18 @@ namespace Content.Server.GameObjects.Components.Body
/// </summary>
private void HandleReceiveBodyPartSlot(int key)
{
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
if (_performerCache == null ||
!_performerCache.TryGetComponent(out IActorComponent? actor))
{
return;
}
CloseSurgeryUI(actor.playerSession);
if (_bodyManagerComponentCache == null)
{
return;
}
// 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))
@@ -138,34 +151,42 @@ namespace Content.Server.GameObjects.Components.Body
Loc.GetString("You see no useful way to attach {0:theName} anymore.", Owner));
}
var target = targetObject as string;
var target = (string) targetObject!;
string message;
if (_bodyManagerComponentCache.InstallDroppedBodyPart(this, target))
{
message = Loc.GetString("You attach {0:theName}.", ContainedBodyPart);
}
else
{
message = Loc.GetString("You can't attach it!");
}
_sharedNotifyManager.PopupMessage(
_bodyManagerComponentCache.Owner,
_performerCache,
!_bodyManagerComponentCache.InstallDroppedBodyPart(this, target)
? Loc.GetString("You can't attach it!")
: Loc.GetString("You attach {0:theName}.", ContainedBodyPart));
message);
}
private void OpenSurgeryUI(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
private void UpdateSurgeryUIBodyPartSlotRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
UserInterface?.SendMessage(new RequestBodyPartSlotSurgeryUIMessage(options), session);
}
private void CloseSurgeryUI(IPlayerSession session)
{
_userInterface.Close(session);
UserInterface?.Close(session);
}
private void CloseAllSurgeryUIs()
{
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)

View File

@@ -1,21 +1,23 @@
using System;
#nullable enable
using System.Collections.Generic;
using Content.Server.Body;
using Content.Server.Body.Mechanisms;
using Content.Server.Utility;
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.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.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body
@@ -26,24 +28,22 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent]
public class DroppedMechanismComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
[Dependency] private IPrototypeManager _prototypeManager;
#pragma warning restore 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public sealed override string Name => "DroppedMechanism";
private readonly Dictionary<int, object> _optionsCache = new Dictionary<int, object>();
private BodyManagerComponent _bodyManagerComponentCache;
private BodyManagerComponent? _bodyManagerComponentCache;
private int _idHash;
private IEntity _performerCache;
private IEntity? _performerCache;
private BoundUserInterface _userInterface;
[ViewVariables] public Mechanism ContainedMechanism { get; private set; } = default!;
[ViewVariables] public Mechanism ContainedMechanism { get; private set; }
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
@@ -63,12 +63,7 @@ namespace Content.Server.GameObjects.Components.Body
}
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!");
}
DebugTools.AssertNotNull(droppedBodyPart.ContainedBodyPart);
if (!droppedBodyPart.ContainedBodyPart.TryInstallDroppedMechanism(this))
{
@@ -82,9 +77,10 @@ namespace Content.Server.GameObjects.Components.Body
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
public void InitializeDroppedMechanism(Mechanism data)
@@ -92,7 +88,7 @@ namespace Content.Server.GameObjects.Components.Body
ContainedMechanism = data;
Owner.Name = Loc.GetString(ContainedMechanism.Name);
if (Owner.TryGetComponent(out SpriteComponent component))
if (Owner.TryGetComponent(out SpriteComponent? component))
{
component.LayerSetRSI(0, data.RSIPath);
component.LayerSetState(0, data.RSIState);
@@ -111,7 +107,7 @@ namespace Content.Server.GameObjects.Components.Body
if (serializer.Reading && debugLoadMechanismData != "")
{
_prototypeManager.TryIndex(debugLoadMechanismData, out MechanismPrototype data);
_prototypeManager.TryIndex(debugLoadMechanismData!, out MechanismPrototype data);
var mechanism = new Mechanism(data);
mechanism.EnsureInitialize();
@@ -155,7 +151,18 @@ namespace Content.Server.GameObjects.Components.Body
/// </summary>
private void HandleReceiveBodyPart(int key)
{
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
if (_performerCache == null ||
!_performerCache.TryGetComponent(out IActorComponent? actor))
{
return;
}
CloseSurgeryUI(actor.playerSession);
if (_bodyManagerComponentCache == null)
{
return;
}
// 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))
@@ -165,36 +172,37 @@ namespace Content.Server.GameObjects.Components.Body
return;
}
var target = targetObject as BodyPart;
var target = (BodyPart) targetObject;
var message = target.TryInstallDroppedMechanism(this)
? Loc.GetString("You jam the {0} inside {1:them}.", ContainedMechanism.Name, _performerCache)
: Loc.GetString("You can't fit it in!");
_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));
message);
// TODO: {1:theName}
}
private void OpenSurgeryUI(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
UserInterface?.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
}
private void CloseSurgeryUI(IPlayerSession session)
{
_userInterface.Close(session);
UserInterface?.Close(session);
}
private void CloseAllSurgeryUIs()
{
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)

View File

@@ -21,7 +21,7 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] private float Pressure { get; set; }
[ViewVariables] public GasMixture Air { get; set; } = new GasMixture();
[ViewVariables] public GasMixture Air { get; set; }
[ViewVariables] public LungStatus Status { get; set; }
@@ -29,6 +29,8 @@ namespace Content.Server.GameObjects.Components.Body.Respiratory
{
base.ExposeData(serializer);
Air = new GasMixture();
serializer.DataReadWriteFunction(
"volume",
6,

View File

@@ -1,8 +1,10 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using Content.Server.Body;
using Content.Server.Body.Mechanisms;
using Content.Server.Body.Surgery;
using Content.Server.Utility;
using Content.Shared.Body.Surgery;
using Content.Shared.GameObjects;
using Content.Shared.GameObjects.Components.Body;
@@ -18,6 +20,8 @@ using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Body
{
@@ -30,9 +34,7 @@ namespace Content.Server.GameObjects.Components.Body
[RegisterComponent]
public class SurgeryToolComponent : Component, ISurgeon, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager;
#pragma warning restore 649
[Dependency] private readonly ISharedNotifyManager _sharedNotifyManager = default!;
public override string Name => "SurgeryTool";
public override uint? NetID => ContentNetIDs.SURGERY;
@@ -41,17 +43,17 @@ namespace Content.Server.GameObjects.Components.Body
private float _baseOperateTime;
private BodyManagerComponent _bodyManagerComponentCache;
private BodyManagerComponent? _bodyManagerComponentCache;
private ISurgeon.MechanismRequestCallback _callbackCache;
private ISurgeon.MechanismRequestCallback? _callbackCache;
private int _idHash;
private IEntity _performerCache;
private IEntity? _performerCache;
private SurgeryType _surgeryType;
private BoundUserInterface _userInterface;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GenericSurgeryUiKey.Key);
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
@@ -60,7 +62,7 @@ namespace Content.Server.GameObjects.Components.Body
return;
}
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
@@ -73,7 +75,7 @@ namespace Content.Server.GameObjects.Components.Body
_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))
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>();
@@ -105,13 +107,7 @@ namespace Content.Server.GameObjects.Components.Body
// 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!");
}
DebugTools.AssertNotNull(droppedBodyPart.ContainedBodyPart);
// If surgery can be performed...
if (!droppedBodyPart.ContainedBodyPart.SurgeryCheck(_surgeryType))
@@ -144,7 +140,7 @@ namespace Content.Server.GameObjects.Components.Body
toSend.Add(mechanism.Name, _idHash++);
}
if (_optionsCache.Count > 0)
if (_optionsCache.Count > 0 && _performerCache != null)
{
OpenSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
UpdateSurgeryUIMechanismRequest(_performerCache.GetComponent<BasicActorComponent>().playerSession,
@@ -162,34 +158,35 @@ namespace Content.Server.GameObjects.Components.Body
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GenericSurgeryUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
}
private void OpenSurgeryUI(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
private void UpdateSurgeryUIBodyPartRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
UserInterface?.SendMessage(new RequestBodyPartSurgeryUIMessage(options), session);
}
private void UpdateSurgeryUIMechanismRequest(IPlayerSession session, Dictionary<string, int> options)
{
_userInterface.SendMessage(new RequestMechanismSurgeryUIMessage(options), session);
UserInterface?.SendMessage(new RequestMechanismSurgeryUIMessage(options), session);
}
private void CloseSurgeryUI(IPlayerSession session)
{
_userInterface.Close(session);
UserInterface?.Close(session);
}
private void CloseAllSurgeryUIs()
{
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage message)
@@ -211,14 +208,22 @@ namespace Content.Server.GameObjects.Components.Body
/// </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))
if (_performerCache == null ||
!_performerCache.TryGetComponent(out IActorComponent? actor))
{
SendNoUsefulWayToUseAnymorePopup();
return;
}
var target = targetObject as BodyPart;
CloseSurgeryUI(actor.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) ||
_bodyManagerComponentCache == null)
{
SendNoUsefulWayToUseAnymorePopup();
return;
}
var target = (BodyPart) targetObject!;
if (!target.AttemptSurgery(_surgeryType, _bodyManagerComponentCache, this, _performerCache))
{
@@ -233,19 +238,27 @@ namespace Content.Server.GameObjects.Components.Body
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))
if (!_optionsCache.TryGetValue(key, out var targetObject) ||
_performerCache == null ||
!_performerCache.TryGetComponent(out IActorComponent? actor))
{
SendNoUsefulWayToUseAnymorePopup();
return;
}
var target = targetObject as Mechanism;
CloseSurgeryUI(_performerCache.GetComponent<BasicActorComponent>().playerSession);
_callbackCache(target, _bodyManagerComponentCache, this, _performerCache);
CloseSurgeryUI(actor.playerSession);
_callbackCache?.Invoke(target, _bodyManagerComponentCache, this, _performerCache);
}
private void SendNoUsefulWayToUsePopup()
{
if (_bodyManagerComponentCache == null)
{
return;
}
_sharedNotifyManager.PopupMessage(
_bodyManagerComponentCache.Owner,
_performerCache,
@@ -254,6 +267,11 @@ namespace Content.Server.GameObjects.Components.Body
private void SendNoUsefulWayToUseAnymorePopup()
{
if (_bodyManagerComponentCache == null)
{
return;
}
_sharedNotifyManager.PopupMessage(
_bodyManagerComponentCache.Owner,
_performerCache,

View File

@@ -34,13 +34,11 @@ namespace Content.Server.GameObjects.Components.Buckle
[RegisterComponent]
public class BuckleComponent : SharedBuckleComponent, IInteractHand, IDragDrop
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
#pragma warning restore 649
private int _size;

View File

@@ -2,6 +2,7 @@
using Content.Server.Cargo;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Cargo;
using Content.Shared.Interfaces.GameObjects.Components;
using Content.Shared.Prototypes.Cargo;
@@ -10,6 +11,7 @@ using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -19,21 +21,11 @@ namespace Content.Server.GameObjects.Components.Cargo
[ComponentReference(typeof(IActivate))]
public class CargoConsoleComponent : SharedCargoConsoleComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly ICargoOrderDataManager _cargoOrderDataManager = default!;
#pragma warning restore 649
[ViewVariables]
public int Points = 1000;
private BoundUserInterface _userInterface = default!;
[ViewVariables]
public GalacticMarketComponent Market { get; private set; } = default!;
[ViewVariables]
public CargoOrderDatabaseComponent Orders { get; private set; } = default!;
private CargoBankAccount? _bankAccount;
[ViewVariables]
@@ -65,22 +57,44 @@ namespace Content.Server.GameObjects.Components.Cargo
private bool _requestOnly = false;
private PowerReceiverComponent _powerReceiver = default!;
private bool Powered => _powerReceiver.Powered;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
private CargoConsoleSystem _cargoConsoleSystem = default!;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CargoConsoleUiKey.Key);
public override void Initialize()
{
base.Initialize();
Market = Owner.GetComponent<GalacticMarketComponent>();
Orders = Owner.GetComponent<CargoOrderDatabaseComponent>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CargoConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
if (!Owner.EnsureComponent(out GalacticMarketComponent _))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} had no {nameof(GalacticMarketComponent)}");
}
if (!Owner.EnsureComponent(out CargoOrderDatabaseComponent _))
{
Logger.Warning($"Entity {Owner} at {Owner.Transform.MapPosition} had no {nameof(GalacticMarketComponent)}");
}
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
_cargoConsoleSystem = EntitySystem.Get<CargoConsoleSystem>();
BankAccount = _cargoConsoleSystem.StationAccount;
}
public override void OnRemove()
{
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
base.OnRemove();
}
/// <summary>
/// Reads data from YAML
/// </summary>
@@ -93,8 +107,13 @@ namespace Content.Server.GameObjects.Components.Cargo
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage serverMsg)
{
if (!Owner.TryGetComponent(out CargoOrderDatabaseComponent? orders))
{
return;
}
var message = serverMsg.Message;
if (!Orders.ConnectedToDatabase)
if (!orders.ConnectedToDatabase)
return;
if (!Powered)
return;
@@ -107,45 +126,45 @@ namespace Content.Server.GameObjects.Components.Cargo
break;
}
_cargoOrderDataManager.AddOrder(Orders.Database.Id, msg.Requester, msg.Reason, msg.ProductId, msg.Amount, _bankAccount.Id);
_cargoOrderDataManager.AddOrder(orders.Database.Id, msg.Requester, msg.Reason, msg.ProductId, msg.Amount, _bankAccount.Id);
break;
}
case CargoConsoleRemoveOrderMessage msg:
{
_cargoOrderDataManager.RemoveOrder(Orders.Database.Id, msg.OrderNumber);
_cargoOrderDataManager.RemoveOrder(orders.Database.Id, msg.OrderNumber);
break;
}
case CargoConsoleApproveOrderMessage msg:
{
if (_requestOnly ||
!Orders.Database.TryGetOrder(msg.OrderNumber, out var order) ||
!orders.Database.TryGetOrder(msg.OrderNumber, out var order) ||
_bankAccount == null)
{
break;
}
_prototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product);
if (product == null)
PrototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product);
if (product == null!)
break;
var capacity = _cargoOrderDataManager.GetCapacity(Orders.Database.Id);
var capacity = _cargoOrderDataManager.GetCapacity(orders.Database.Id);
if (capacity.CurrentCapacity == capacity.MaxCapacity)
break;
if (!_cargoConsoleSystem.ChangeBalance(_bankAccount.Id, (-product.PointCost) * order.Amount))
break;
_cargoOrderDataManager.ApproveOrder(Orders.Database.Id, msg.OrderNumber);
_cargoOrderDataManager.ApproveOrder(orders.Database.Id, msg.OrderNumber);
UpdateUIState();
break;
}
case CargoConsoleShuttleMessage _:
{
var approvedOrders = _cargoOrderDataManager.RemoveAndGetApprovedFrom(Orders.Database);
Orders.Database.ClearOrderCapacity();
var approvedOrders = _cargoOrderDataManager.RemoveAndGetApprovedFrom(orders.Database);
orders.Database.ClearOrderCapacity();
// TODO replace with shuttle code
// TEMPORARY loop for spawning stuff on top of console
foreach (var order in approvedOrders)
{
if (!_prototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product))
if (!PrototypeManager.TryIndex(order.ProductId, out CargoProductPrototype product))
continue;
for (var i = 0; i < order.Amount; i++)
{
@@ -166,12 +185,12 @@ namespace Content.Server.GameObjects.Components.Cargo
if (!Powered)
return;
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
private void UpdateUIState()
{
if (_bankAccount == null)
if (_bankAccount == null || !Owner.IsValid())
{
return;
}
@@ -180,7 +199,7 @@ namespace Content.Server.GameObjects.Components.Cargo
var name = _bankAccount.Name;
var balance = _bankAccount.Balance;
var capacity = _cargoOrderDataManager.GetCapacity(id);
_userInterface.SetState(new CargoConsoleInterfaceState(_requestOnly, id, name, balance, capacity));
UserInterface?.SetState(new CargoConsoleInterfaceState(_requestOnly, id, name, balance, capacity));
}
}
}

View File

@@ -8,9 +8,7 @@ namespace Content.Server.GameObjects.Components.Cargo
[RegisterComponent]
public class CargoOrderDatabaseComponent : SharedCargoOrderDatabaseComponent
{
#pragma warning disable 649
[Dependency] private readonly ICargoOrderDataManager _cargoOrderDataManager;
#pragma warning restore 649
[Dependency] private readonly ICargoOrderDataManager _cargoOrderDataManager = default!;
public CargoOrderDatabase Database { get; set; }
public bool ConnectedToDatabase => Database != null;

View File

@@ -1,4 +1,6 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI;
@@ -7,6 +9,7 @@ using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry.ChemMaster;
using Content.Shared.GameObjects.EntitySystems;
@@ -40,24 +43,20 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ComponentReference(typeof(IInteractUsing))]
public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[ViewVariables] private BoundUserInterface _userInterface;
[ViewVariables] private ContainerSlot _beakerContainer;
[ViewVariables] private string _packPrototypeId;
[ViewVariables] private ContainerSlot _beakerContainer = default!;
[ViewVariables] private string _packPrototypeId = "";
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private bool BufferModeTransfer = true;
[ViewVariables] private bool _bufferModeTransfer = true;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
private readonly SolutionComponent BufferSolution = new SolutionComponent();
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ChemMasterUiKey.Key);
/// <summary>
/// Shows the serializer how to save/load this components yaml prototype.
@@ -77,14 +76,19 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(ChemMasterUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
_beakerContainer =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_powerReceiver.OnPowerStateChanged += OnPowerChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += OnPowerChanged;
}
//BufferSolution = Owner.BufferSolution
BufferSolution.Solution = new Solution();
@@ -93,7 +97,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
UpdateUserInterface();
}
private void OnPowerChanged(object sender, PowerStateEventArgs e)
private void OnPowerChanged(object? sender, PowerStateEventArgs e)
{
UpdateUserInterface();
}
@@ -105,6 +109,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
var msg = (UiActionMessage) obj.Message;
var needsPower = msg.action switch
{
@@ -124,11 +133,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
TransferReagent(msg.id, msg.amount, msg.isBuffer);
break;
case UiAction.Transfer:
BufferModeTransfer = true;
_bufferModeTransfer = true;
UpdateUserInterface();
break;
case UiAction.Discard:
BufferModeTransfer = false;
_bufferModeTransfer = false;
UpdateUserInterface();
break;
case UiAction.CreatePills:
@@ -147,7 +156,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the chem master, and false if it cannot.</returns>
private bool PlayerCanUseChemMaster(IEntity playerEntity, bool needsPower = true)
private bool PlayerCanUseChemMaster(IEntity? playerEntity, bool needsPower = true)
{
//Need player entity to check if they are still able to use the chem master
if (playerEntity == null)
@@ -172,18 +181,18 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (beaker == null)
{
return new ChemMasterBoundUserInterfaceState(Powered, false, ReagentUnit.New(0), ReagentUnit.New(0),
"", Owner.Name, null, BufferSolution.ReagentList.ToList(), BufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
"", Owner.Name, new List<Solution.ReagentQuantity>(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
}
var solution = beaker.GetComponent<SolutionComponent>();
return new ChemMasterBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume,
beaker.Name, Owner.Name, solution.ReagentList.ToList(), BufferSolution.ReagentList.ToList(), BufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
beaker.Name, Owner.Name, solution.ReagentList.ToList(), BufferSolution.ReagentList.ToList(), _bufferModeTransfer, BufferSolution.CurrentVolume, BufferSolution.MaxVolume);
}
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
/// <summary>
@@ -207,7 +216,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TransferReagent(string id, ReagentUnit amount, bool isBuffer)
{
if (!HasBeaker && BufferModeTransfer) return;
if (!HasBeaker && _bufferModeTransfer) return;
var beaker = _beakerContainer.ContainedEntity;
var beakerSolution = beaker.GetComponent<SolutionComponent>();
if (isBuffer)
@@ -227,7 +236,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
BufferSolution.Solution.RemoveReagent(id, actualAmount);
if (BufferModeTransfer)
if (_bufferModeTransfer)
{
beakerSolution.Solution.AddReagent(id, actualAmount);
}
@@ -351,22 +360,22 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <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))
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return;
}
var activeHandEntity = hands.GetActiveHand?.Owner;
if (activeHandEntity == null)
{
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
@@ -379,26 +388,33 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <returns></returns>
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs args)
{
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return true;
}
if (hands.GetActiveHand == null)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have nothing on your hand."));
return false;
}
var activeHandEntity = hands.GetActiveHand.Owner;
if (activeHandEntity.TryGetComponent<SolutionComponent>(out var solution))
{
if (HasBeaker)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("This ChemMaster already has a container in it."));
Loc.GetString("This ChemMaster already has a container in it."));
}
else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0) //Close enough to a chem master...
{
//If it can't fit in the chem master, don't put it in. For example, buckets and mop buckets can't fit.
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("That can't fit in the ChemMaster."));
Loc.GetString("That can't fit in the ChemMaster."));
}
else
{
@@ -409,7 +425,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
else
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You can't put this in the ChemMaster."));
Loc.GetString("You can't put this in the ChemMaster."));
}
return true;

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using Content.Server.GameObjects.Components.Body.Circulatory;
using Content.Server.Interfaces;
using Content.Server.Utility;
@@ -22,9 +23,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
/// <summary>
/// Whether or not the injector is able to draw from containers or if it's a single use
@@ -53,11 +52,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
private InjectorToggleMode _toggleState;
/// <summary>
/// Internal solution container
/// </summary>
[ViewVariables]
private SolutionComponent _internalContents;
public override void ExposeData(ObjectSerializer serializer)
{
@@ -69,9 +63,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
protected override void Startup()
{
base.Startup();
_internalContents = Owner.GetComponent<SolutionComponent>();
_internalContents.Capabilities |= SolutionCaps.Injector;
//Set _toggleState based on prototype
Owner.EnsureComponent<SolutionComponent>();
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution.Capabilities |= SolutionCaps.Injector;
}
// Set _toggleState based on prototype
_toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw;
}
@@ -114,7 +114,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
//Make sure we have the attacking entity
if (eventArgs.Target == null || !_internalContents.Injector)
if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionComponent? solution) || !solution.Injector)
{
return;
}
@@ -134,7 +134,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
else //Handle injecting into bloodstream
{
if (targetEntity.TryGetComponent(out BloodstreamComponent bloodstream) &&
if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) &&
_toggleState == InjectorToggleMode.Inject)
{
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
@@ -155,7 +155,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TryInjectIntoBloodstream(BloodstreamComponent targetBloodstream, IEntity user)
{
if (_internalContents.CurrentVolume == 0)
if (!Owner.TryGetComponent(out SolutionComponent? solution) ||
solution.CurrentVolume == 0)
{
return;
}
@@ -170,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Move units from attackSolution to targetSolution
var removedSolution = _internalContents.SplitSolution(realTransferAmount);
var removedSolution = solution.SplitSolution(realTransferAmount);
if (!targetBloodstream.TryTransferSolution(removedSolution))
{
return;
@@ -183,7 +184,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TryInject(SolutionComponent targetSolution, IEntity user)
{
if (_internalContents.CurrentVolume == 0)
if (!Owner.TryGetComponent(out SolutionComponent? solution) ||
solution.CurrentVolume == 0)
{
return;
}
@@ -198,7 +200,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
//Move units from attackSolution to targetSolution
var removedSolution = _internalContents.SplitSolution(realTransferAmount);
var removedSolution = solution.SplitSolution(realTransferAmount);
if (!targetSolution.TryAddSolution(removedSolution))
{
return;
@@ -211,7 +213,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void TryDraw(SolutionComponent targetSolution, IEntity user)
{
if (_internalContents.EmptyVolume == 0)
if (!Owner.TryGetComponent(out SolutionComponent? solution) ||
solution.EmptyVolume == 0)
{
return;
}
@@ -227,7 +230,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
//Move units from attackSolution to targetSolution
var removedSolution = targetSolution.SplitSolution(realTransferAmount);
if (!_internalContents.TryAddSolution(removedSolution))
if (!solution.TryAddSolution(removedSolution))
{
return;
}
@@ -239,7 +242,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override ComponentState GetComponentState()
{
return new InjectorComponentState(_internalContents.CurrentVolume, _internalContents.MaxVolume, _toggleState);
Owner.TryGetComponent(out SolutionComponent? solution);
var currentVolume = solution?.CurrentVolume ?? ReagentUnit.Zero;
var maxVolume = solution?.MaxVolume ?? ReagentUnit.Zero;
return new InjectorComponentState(currentVolume, maxVolume, _toggleState);
}
}
}

View File

@@ -20,9 +20,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ComponentReference(typeof(IAfterInteract))]
public class PillComponent : FoodComponent, IUse, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystem;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
public override string Name => "Pill";
[ViewVariables]

View File

@@ -19,10 +19,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
class PourableComponent : Component, IInteractUsing
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "Pourable";
@@ -91,7 +88,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
if (realTransferAmount <= 0) //Special message if container is full
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
_localizationManager.GetString("Container is full"));
Loc.GetString("Container is full"));
return false;
}
@@ -101,7 +98,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return false;
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
_localizationManager.GetString("Transferred {0}u", removedSolution.TotalVolume));
Loc.GetString("Transferred {0}u", removedSolution.TotalVolume));
return true;
}

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI;
@@ -7,6 +8,7 @@ using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Chemistry.ReagentDispenser;
using Content.Shared.GameObjects.EntitySystems;
@@ -38,14 +40,10 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ComponentReference(typeof(IInteractUsing))]
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[ViewVariables] private BoundUserInterface _userInterface;
[ViewVariables] private ContainerSlot _beakerContainer;
[ViewVariables] private string _packPrototypeId;
[ViewVariables] private ContainerSlot _beakerContainer = default!;
[ViewVariables] private string _packPrototypeId = "";
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private ReagentUnit _dispenseAmount = ReagentUnit.New(10);
@@ -53,9 +51,9 @@ namespace Content.Server.GameObjects.Components.Chemistry
[ViewVariables]
private SolutionComponent Solution => _beakerContainer.ContainedEntity.GetComponent<SolutionComponent>();
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentDispenserUiKey.Key);
/// <summary>
/// Shows the serializer how to save/load this components yaml prototype.
@@ -75,14 +73,19 @@ namespace Content.Server.GameObjects.Components.Chemistry
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(ReagentDispenserUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
_beakerContainer =
ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-reagentContainerContainer", Owner);
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_powerReceiver.OnPowerStateChanged += OnPowerChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged += OnPowerChanged;
}
InitializeFromPrototype();
UpdateUserInterface();
@@ -108,7 +111,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
}
}
private void OnPowerChanged(object sender, PowerStateEventArgs e)
private void OnPowerChanged(object? sender, PowerStateEventArgs e)
{
UpdateUserInterface();
}
@@ -120,6 +123,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
var msg = (UiButtonPressedMessage) obj.Message;
var needsPower = msg.Button switch
{
@@ -175,7 +183,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// </summary>
/// <param name="playerEntity">The player entity.</param>
/// <returns>Returns true if the entity can use the dispenser, and false if it cannot.</returns>
private bool PlayerCanUseDispenser(IEntity playerEntity, bool needsPower = true)
private bool PlayerCanUseDispenser(IEntity? playerEntity, bool needsPower = true)
{
//Need player entity to check if they are still able to use the dispenser
if (playerEntity == null)
@@ -211,7 +219,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
/// <summary>
@@ -265,22 +273,22 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <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))
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return;
}
var activeHandEntity = hands.GetActiveHand?.Owner;
if (activeHandEntity == null)
{
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
@@ -293,26 +301,33 @@ namespace Content.Server.GameObjects.Components.Chemistry
/// <returns></returns>
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs args)
{
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You have no hands."));
Loc.GetString("You have no hands."));
return true;
}
if (hands.GetActiveHand == null)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have nothing on your hand."));
return false;
}
var activeHandEntity = hands.GetActiveHand.Owner;
if (activeHandEntity.TryGetComponent<SolutionComponent>(out var solution))
{
if (HasBeaker)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("This dispenser already has a container in it."));
Loc.GetString("This dispenser already has a container in it."));
}
else if ((solution.Capabilities & SolutionCaps.FitsInDispenser) == 0)
{
//If it can't fit in the dispenser, don't put it in. For example, buckets and mop buckets can't fit.
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("That can't fit in the dispenser."));
Loc.GetString("That can't fit in the dispenser."));
}
else
{
@@ -323,7 +338,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
else
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
_localizationManager.GetString("You can't put this in the dispenser."));
Loc.GetString("You can't put this in the dispenser."));
}
return true;

View File

@@ -29,11 +29,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
public class SolutionComponent : SharedSolutionComponent, IExamine
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly ILocalizationManager _loc;
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
private IEnumerable<ReactionPrototype> _reactions;
private AudioSystem _audioSystem;
@@ -276,7 +273,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
return;
}
message.AddText(_loc.GetString("Contains:\n"));
message.AddText(Loc.GetString("Contains:\n"));
if (ReagentList.Count == 0)
{
message.AddText("Nothing.\n");
@@ -303,12 +300,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
colorIsh = "Blue";
}
message.AddText(_loc.GetString("A {0} liquid\n", colorIsh));
message.AddText(Loc.GetString("A {0} liquid\n", colorIsh));
}
}
else
{
message.AddText(_loc.GetString("Unknown reagent: {0}u\n", reagent.Quantity));
message.AddText(Loc.GetString("Unknown reagent: {0}u\n", reagent.Quantity));
}
}
}

View File

@@ -1,8 +1,10 @@
using Content.Server.GameObjects.EntitySystems;
#nullable enable
using Content.Server.GameObjects.EntitySystems;
using Content.Shared.Chemistry;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -11,28 +13,27 @@ namespace Content.Server.GameObjects.Components.Chemistry
[RegisterComponent]
public class TransformableContainerComponent : Component, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override string Name => "TransformableContainer";
private bool _transformed = false;
public bool Transformed { get => _transformed; }
private SpriteSpecifier? _initialSprite;
private string _initialName = default!;
private string _initialDescription = default!;
private ReagentPrototype? _currentReagent;
private SpriteSpecifier _initialSprite;
private string _initialName;
private string _initialDescription;
private SpriteComponent _sprite;
private ReagentPrototype _currentReagent;
public bool Transformed { get; private set; }
public override void Initialize()
{
base.Initialize();
_sprite = Owner.GetComponent<SpriteComponent>();
_initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(_sprite.BaseRSIPath), "icon");
if (Owner.TryGetComponent(out SpriteComponent? sprite) &&
sprite.BaseRSIPath != null)
{
_initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(sprite.BaseRSIPath), "icon");
}
_initialName = Owner.Name;
_initialDescription = Owner.Description;
}
@@ -40,14 +41,27 @@ namespace Content.Server.GameObjects.Components.Chemistry
protected override void Startup()
{
base.Startup();
Owner.GetComponent<SolutionComponent>().Capabilities |= SolutionCaps.FitsInDispenser;;
if (!Owner.EnsureComponent(out SolutionComponent solution))
{
Logger.Warning(
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
solution.Capabilities |= SolutionCaps.FitsInDispenser;
}
public void CancelTransformation()
{
_currentReagent = null;
_transformed = false;
_sprite.LayerSetSprite(0, _initialSprite);
Transformed = false;
if (Owner.TryGetComponent(out SpriteComponent? sprite) &&
_initialSprite != null)
{
sprite.LayerSetSprite(0, _initialSprite);
}
Owner.Name = _initialName;
Owner.Description = _initialDescription;
}
@@ -76,11 +90,16 @@ namespace Content.Server.GameObjects.Components.Chemistry
!string.IsNullOrWhiteSpace(proto.SpriteReplacementPath))
{
var spriteSpec = new SpriteSpecifier.Rsi(new ResourcePath("Objects/Drinks/" + proto.SpriteReplacementPath),"icon");
_sprite.LayerSetSprite(0, spriteSpec);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite?.LayerSetSprite(0, spriteSpec);
}
Owner.Name = proto.Name + " glass";
Owner.Description = proto.Description;
_currentReagent = proto;
_transformed = true;
Transformed = true;
}
}
}

View File

@@ -7,6 +7,7 @@ using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -19,8 +20,6 @@ namespace Content.Server.GameObjects.Components.Chemistry
[Dependency] private readonly IMapManager _mapManager = default!;
public override string Name => "Vapor";
[ViewVariables]
private SolutionComponent _contents;
[ViewVariables]
private ReagentUnit _transferAmount;
@@ -28,11 +27,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
private Vector2 _direction;
private float _velocity;
public override void Initialize()
{
base.Initialize();
_contents = Owner.GetComponent<SolutionComponent>();
if (!Owner.EnsureComponent(out SolutionComponent _))
{
Logger.Warning(
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
}
public void Start(Vector2 dir, float velocity)
@@ -56,6 +59,9 @@ namespace Content.Server.GameObjects.Components.Chemistry
public void Update()
{
if (!Owner.TryGetComponent(out SolutionComponent contents))
return;
if (!_running)
return;
@@ -70,11 +76,11 @@ namespace Content.Server.GameObjects.Components.Chemistry
foreach (var tile in tiles)
{
var pos = tile.GridIndices.ToGridCoordinates(_mapManager, tile.GridIndex);
SpillHelper.SpillAt(pos, _contents.SplitSolution(amount), "PuddleSmear", false); //make non PuddleSmear?
SpillHelper.SpillAt(pos, contents.SplitSolution(amount), "PuddleSmear", false); //make non PuddleSmear?
}
}
if (_contents.CurrentVolume == 0)
if (contents.CurrentVolume == 0)
{
// Delete this
Owner.Delete();
@@ -87,7 +93,14 @@ namespace Content.Server.GameObjects.Components.Chemistry
{
return false;
}
var result = _contents.TryAddSolution(solution);
if (!Owner.TryGetComponent(out SolutionComponent contents))
{
return false;
}
var result = contents.TryAddSolution(solution);
if (!result)
{
return false;

View File

@@ -1,5 +1,7 @@
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
#nullable enable
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Command;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.UserInterface;
@@ -8,6 +10,7 @@ using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Command
{
@@ -15,22 +18,22 @@ namespace Content.Server.GameObjects.Components.Command
[ComponentReference(typeof(IActivate))]
public class CommunicationsConsoleComponent : SharedCommunicationsConsoleComponent, IActivate
{
#pragma warning disable 649
[Dependency] private IEntitySystemManager _entitySystemManager;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
private BoundUserInterface _userInterface;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
private RoundEndSystem RoundEndSystem => _entitySystemManager.GetEntitySystem<RoundEndSystem>();
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(CommunicationsConsoleUiKey.Key);
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(CommunicationsConsoleUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnOnReceiveMessage;
}
RoundEndSystem.OnRoundEndCountdownStarted += UpdateBoundInterface;
RoundEndSystem.OnRoundEndCountdownCancelled += UpdateBoundInterface;
@@ -39,7 +42,7 @@ namespace Content.Server.GameObjects.Components.Command
private void UpdateBoundInterface()
{
_userInterface.SetState(new CommunicationsConsoleInterfaceState(RoundEndSystem.ExpectedCountdownEnd));
UserInterface?.SetState(new CommunicationsConsoleInterfaceState(RoundEndSystem.ExpectedCountdownEnd));
}
private void UserInterfaceOnOnReceiveMessage(ServerBoundUserInterfaceMessage obj)
@@ -58,12 +61,12 @@ namespace Content.Server.GameObjects.Components.Command
public void OpenUserInterface(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
return;
if (!Powered)

View File

@@ -28,10 +28,8 @@ namespace Content.Server.GameObjects.Components.Conveyor
[RegisterComponent]
public class ConveyorComponent : Component, IInteractUsing
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
#pragma warning restore 649
public override string Name => "Conveyor";

View File

@@ -21,10 +21,8 @@ namespace Content.Server.GameObjects.Components.Damage
[ComponentReference(typeof(IDamageableComponent))]
public class BreakableComponent : RuinableComponent, IExAct
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override string Name => "Breakable";
@@ -41,8 +39,6 @@ namespace Content.Server.GameObjects.Components.Damage
switch (eventArgs.Severity)
{
case ExplosionSeverity.Destruction:
PerformDestruction();
break;
case ExplosionSeverity.Heavy:
PerformDestruction();
break;

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Interactable;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
@@ -27,12 +28,6 @@ namespace Content.Server.GameObjects.Components.Damage
serializer.DataField(ref _tools, "tools", new List<ToolQuality>());
}
public override void Initialize()
{
base.Initialize();
Owner.EnsureComponent<DestructibleComponent>();
}
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{
if (eventArgs.Using.TryGetComponent<ToolComponent>(out var tool))
@@ -56,7 +51,7 @@ namespace Content.Server.GameObjects.Components.Damage
protected bool CallDamage(InteractUsingEventArgs eventArgs, ToolComponent tool)
{
if (eventArgs.Target.TryGetComponent<DestructibleComponent>(out var damageable))
if (eventArgs.Target.TryGetComponent<IDamageableComponent>(out var damageable))
{
damageable.ChangeDamage(tool.HasQuality(ToolQuality.Welding)
? DamageType.Heat

View File

@@ -16,9 +16,7 @@ namespace Content.Server.GameObjects.Components.Damage
[ComponentReference(typeof(IDamageableComponent))]
public class DestructibleComponent : RuinableComponent, IDestroyAct
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
protected ActSystem ActSystem;

View File

@@ -5,7 +5,6 @@ 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
{
@@ -18,13 +17,6 @@ namespace Content.Server.GameObjects.Components.Damage
{
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>
@@ -35,29 +27,24 @@ namespace Content.Server.GameObjects.Components.Damage
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.DataReadWriteFunction(
"deadThreshold",
100,
t => DeadThreshold = t ,
() => DeadThreshold ?? -1);
serializer.DataField(this, ruinable => ruinable.DestroySound, "destroySound", string.Empty);
}
public override void OnRemove()
protected override void EnterState(DamageState state)
{
base.OnRemove();
HealthChangedEvent -= OnHealthChanged;
}
base.EnterState(state);
private void OnHealthChanged(HealthChangedEventArgs e)
{
if (CurrentDamageState != DamageState.Dead && TotalDamage >= MaxHp)
if (state == DamageState.Dead)
{
PerformDestruction();
}

View File

@@ -1,7 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Random;
namespace Content.Server.GameObjects.Components.Disposal
{
@@ -9,6 +13,8 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalEntryComponent : DisposalTubeComponent
{
[Dependency] private readonly IRobustRandom _random = default!;
private const string HolderPrototypeId = "DisposalHolder";
public override string Name => "DisposalEntry";
@@ -43,8 +49,19 @@ namespace Content.Server.GameObjects.Components.Disposal
return new[] {Owner.Transform.LocalRotation.GetDir()};
}
/// <summary>
/// Ejects contents when they come from the same direction the entry is facing.
/// </summary>
public override Direction NextDirection(DisposalHolderComponent holder)
{
if (holder.PreviousTube != null && DirectionTo(holder.PreviousTube) == ConnectableDirections()[0])
{
var invalidDirections = new Direction[] { ConnectableDirections()[0], Direction.Invalid };
var directions = System.Enum.GetValues(typeof(Direction))
.Cast<Direction>().Except(invalidDirections).ToList();
return _random.Pick<Direction>(directions);
}
return ConnectableDirections()[0];
}
}

View File

@@ -14,9 +14,7 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalJunctionComponent : DisposalTubeComponent
{
#pragma warning disable 649
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
[Dependency] private readonly IRobustRandom _random = default!;
/// <summary>
/// The angles to connect to.

View File

@@ -1,4 +1,5 @@
using Content.Server.Interfaces;
#nullable enable
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -16,6 +17,7 @@ using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using Content.Server.Utility;
using static Content.Shared.GameObjects.Components.Disposal.SharedDisposalRouterComponent;
namespace Content.Server.GameObjects.Components.Disposal
@@ -25,22 +27,19 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalRouterComponent : DisposalJunctionComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "DisposalRouter";
[ViewVariables]
private BoundUserInterface _userInterface;
[ViewVariables]
private HashSet<string> _tags;
private readonly HashSet<string> _tags = new HashSet<string>();
[ViewVariables]
public bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
!Owner.TryGetComponent(out ICollidableComponent? collidable) ||
collidable.Anchored;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalRouterUiKey.Key);
public override Direction NextDirection(DisposalHolderComponent holder)
{
var directions = ConnectableDirections();
@@ -53,15 +52,14 @@ namespace Content.Server.GameObjects.Components.Disposal
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>();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
UpdateUserInterface();
}
@@ -73,6 +71,11 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <param name="obj">A user interface message from the client.</param>
private void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (obj.Session.AttachedEntity == null)
{
return;
}
var msg = (UiActionMessage) obj.Message;
if (!PlayerCanUseDisposalTagger(obj.Session.AttachedEntity))
@@ -112,10 +115,10 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <summary>
/// Gets component data to be used to update the user interface client-side.
/// </summary>
/// <returns>Returns a <see cref="SharedDisposalRouterComponent.DisposalRouterBoundUserInterfaceState"/></returns>
/// <returns>Returns a <see cref="DisposalRouterUserInterfaceState"/></returns>
private DisposalRouterUserInterfaceState GetUserInterfaceState()
{
if(_tags == null || _tags.Count <= 0)
if(_tags.Count <= 0)
{
return new DisposalRouterUserInterfaceState("");
}
@@ -136,7 +139,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
private void ClickSound()
@@ -150,12 +153,12 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <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))
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have no hands."));
@@ -166,13 +169,13 @@ namespace Content.Server.GameObjects.Components.Disposal
if (activeHandEntity == null)
{
UpdateUserInterface();
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
public override void OnRemove()
{
_userInterface.CloseAll();
UserInterface?.CloseAll();
base.OnRemove();
}
}

View File

@@ -1,5 +1,7 @@
using Content.Server.Interfaces;
#nullable enable
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.UserInterface;
@@ -23,35 +25,33 @@ namespace Content.Server.GameObjects.Components.Disposal
[ComponentReference(typeof(IDisposalTubeComponent))]
public class DisposalTaggerComponent : DisposalTransitComponent, IActivate
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public override string Name => "DisposalTagger";
[ViewVariables]
private BoundUserInterface _userInterface;
[ViewVariables(VVAccess.ReadWrite)]
private string _tag = "";
[ViewVariables]
public bool Anchored =>
!Owner.TryGetComponent(out CollidableComponent collidable) ||
!Owner.TryGetComponent(out CollidableComponent? collidable) ||
collidable.Anchored;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalTaggerUiKey.Key);
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;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
UpdateUserInterface();
}
@@ -70,7 +70,7 @@ namespace Content.Server.GameObjects.Components.Disposal
//Check for correct message and ignore maleformed strings
if (msg.Action == UiAction.Ok && TagRegex.IsMatch(msg.Tag))
{
{
_tag = msg.Tag;
ClickSound();
}
@@ -81,7 +81,7 @@ namespace Content.Server.GameObjects.Components.Disposal
/// </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)
private bool PlayerCanUseDisposalTagger(IEntity? playerEntity)
{
//Need player entity to check if they are still able to use the configuration interface
if (playerEntity == null)
@@ -98,7 +98,7 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <summary>
/// Gets component data to be used to update the user interface client-side.
/// </summary>
/// <returns>Returns a <see cref="SharedDisposalTaggerComponent.DisposalTaggerBoundUserInterfaceState"/></returns>
/// <returns>Returns a <see cref="DisposalTaggerUserInterfaceState"/></returns>
private DisposalTaggerUserInterfaceState GetUserInterfaceState()
{
return new DisposalTaggerUserInterfaceState(_tag);
@@ -107,7 +107,7 @@ namespace Content.Server.GameObjects.Components.Disposal
private void UpdateUserInterface()
{
var state = GetUserInterfaceState();
_userInterface.SetState(state);
UserInterface?.SetState(state);
}
private void ClickSound()
@@ -121,12 +121,12 @@ namespace Content.Server.GameObjects.Components.Disposal
/// <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))
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!args.User.TryGetComponent(out IHandsComponent hands))
if (!args.User.TryGetComponent(out IHandsComponent? hands))
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, args.User,
Loc.GetString("You have no hands."));
@@ -137,14 +137,14 @@ namespace Content.Server.GameObjects.Components.Disposal
if (activeHandEntity == null)
{
UpdateUserInterface();
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
}
public override void OnRemove()
{
base.OnRemove();
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
}
}

View File

@@ -24,7 +24,6 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Disposal
{
// TODO: Make unanchored pipes pullable
public abstract class DisposalTubeComponent : Component, IDisposalTubeComponent, IBreakAct
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -69,21 +68,11 @@ namespace Content.Server.GameObjects.Components.Disposal
{
var nextDirection = NextDirection(holder);
var snapGrid = Owner.GetComponent<SnapGridComponent>();
var oppositeDirection = new Angle(nextDirection.ToAngle().Theta + Math.PI).GetDir();
var tube = snapGrid
.GetInDir(nextDirection)
.Select(x => x.TryGetComponent(out IDisposalTubeComponent? c) ? c : null)
.FirstOrDefault(x => x != null && x != this);
if (tube == null)
{
return null;
}
var oppositeDirection = new Angle(nextDirection.ToAngle().Theta + Math.PI).GetDir();
if (!tube.CanConnect(oppositeDirection, this))
{
return null;
}
.FirstOrDefault(x => x != null && x != this && x.CanConnect(oppositeDirection, this));
return tube;
}
@@ -192,6 +181,8 @@ namespace Content.Server.GameObjects.Components.Disposal
return;
}
collidable.CanCollide = !collidable.Anchored;
if (collidable.Anchored)
{
OnAnchor();
@@ -230,6 +221,8 @@ namespace Content.Server.GameObjects.Components.Disposal
var collidable = Owner.EnsureComponent<CollidableComponent>();
collidable.AnchoredChanged += AnchoredChanged;
collidable.CanCollide = !collidable.Anchored;
}
protected override void Startup()

View File

@@ -7,8 +7,10 @@ using System.Threading.Tasks;
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.DoAfter;
using Content.Server.Interfaces;
using Content.Server.Interfaces.GameObjects.Components.Items;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.Components.Disposal;
using Content.Shared.GameObjects.EntitySystems;
@@ -29,6 +31,7 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Timer = Robust.Shared.Timers.Timer;
@@ -36,13 +39,12 @@ using Timer = Robust.Shared.Timers.Timer;
namespace Content.Server.GameObjects.Components.Disposal
{
[RegisterComponent]
[ComponentReference(typeof(SharedDisposalUnitComponent))]
[ComponentReference(typeof(IInteractUsing))]
public class DisposalUnitComponent : SharedDisposalUnitComponent, IInteractHand, IInteractUsing, IDragDropOn
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
#pragma warning restore 649
public override string Name => "DisposalUnit";
@@ -67,6 +69,12 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables]
private TimeSpan _automaticEngageTime;
[ViewVariables]
private TimeSpan _flushDelay;
[ViewVariables]
private float _entryDelay;
/// <summary>
/// Token used to cancel the automatic engage of a disposal unit
/// after an entity enters it.
@@ -81,9 +89,6 @@ namespace Content.Server.GameObjects.Components.Disposal
[ViewVariables] public IReadOnlyList<IEntity> ContainedEntities => _container.ContainedEntities;
[ViewVariables]
private BoundUserInterface _userInterface = default!;
[ViewVariables]
public bool Powered =>
!Owner.TryGetComponent(out PowerReceiverComponent? receiver) ||
@@ -115,6 +120,15 @@ namespace Content.Server.GameObjects.Components.Disposal
}
}
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(DisposalUnitUiKey.Key);
private DisposalUnitBoundUserInterfaceState? _lastUiState;
/// <summary>
/// Store the translated state.
/// </summary>
private (PressureState State, string Localized) _locState;
public bool CanInsert(IEntity entity)
{
if (!Anchored)
@@ -161,19 +175,40 @@ namespace Content.Server.GameObjects.Components.Disposal
if (entity.TryGetComponent(out IActorComponent? actor))
{
_userInterface.Close(actor.playerSession);
UserInterface?.Close(actor.playerSession);
}
UpdateVisualState();
}
public bool TryInsert(IEntity entity)
public async Task<bool> TryInsert(IEntity entity, IEntity? user = default)
{
if (!CanInsert(entity) || !_container.Insert(entity))
{
if (!CanInsert(entity))
return false;
if (user != null && _entryDelay > 0f)
{
var doAfterSystem = EntitySystem.Get<DoAfterSystem>();
var doAfterArgs = new DoAfterEventArgs(user, _entryDelay, default, Owner)
{
BreakOnDamage = true,
BreakOnStun = true,
BreakOnTargetMove = true,
BreakOnUserMove = true,
NeedHand = false,
};
var result = await doAfterSystem.DoAfter(doAfterArgs);
if (result == DoAfterStatus.Cancelled)
return false;
}
if (!_container.Insert(entity))
return false;
AfterInsert(entity);
return true;
@@ -218,9 +253,9 @@ namespace Content.Server.GameObjects.Components.Disposal
{
Engaged ^= true;
if (Engaged)
if (Engaged && CanFlush())
{
TryFlush();
Timer.Spawn(_flushDelay, () => TryFlush());
}
}
@@ -284,17 +319,35 @@ namespace Content.Server.GameObjects.Components.Disposal
private DisposalUnitBoundUserInterfaceState GetInterfaceState()
{
var state = Loc.GetString($"{State}");
return new DisposalUnitBoundUserInterfaceState(Owner.Name, state, _pressure, Powered, Engaged);
string stateString;
if (_locState.State != State)
{
stateString = Loc.GetString($"{State}");
_locState = (State, stateString);
}
else
{
stateString = _locState.Localized;
}
return new DisposalUnitBoundUserInterfaceState(Owner.Name, stateString, _pressure, Powered, Engaged);
}
private void UpdateInterface()
{
var state = GetInterfaceState();
_userInterface.SetState(state);
if (_lastUiState != null && _lastUiState.Equals(state))
{
return;
}
_lastUiState = state;
UserInterface?.SetState(state);
}
private bool PlayerCanUse(IEntity player)
private bool PlayerCanUse(IEntity? player)
{
if (player == null)
{
@@ -402,8 +455,9 @@ namespace Content.Server.GameObjects.Components.Disposal
: LightState.Ready);
}
public void Update(float frameTime)
public override void Update(float frameTime)
{
base.Update(frameTime);
if (!Powered)
{
return;
@@ -461,10 +515,16 @@ namespace Content.Server.GameObjects.Components.Disposal
() => (int) _automaticEngageTime.TotalSeconds);
serializer.DataReadWriteFunction(
"automaticEngageTime",
30,
seconds => _automaticEngageTime = TimeSpan.FromSeconds(seconds),
() => (int) _automaticEngageTime.TotalSeconds);
"flushDelay",
3,
seconds => _flushDelay = TimeSpan.FromSeconds(seconds),
() => (int) _flushDelay.TotalSeconds);
serializer.DataReadWriteFunction(
"entryDelay",
0.5f,
seconds => _entryDelay = seconds,
() => (int) _entryDelay);
}
public override void Initialize()
@@ -472,9 +532,11 @@ namespace Content.Server.GameObjects.Components.Disposal
base.Initialize();
_container = ContainerManagerComponent.Ensure<Container>(Name, Owner);
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(DisposalUnitUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
UpdateInterface();
}
@@ -483,10 +545,15 @@ namespace Content.Server.GameObjects.Components.Disposal
{
base.Startup();
Owner.EnsureComponent<AnchorableComponent>();
if(!Owner.HasComponent<AnchorableComponent>())
{
Logger.WarningS("VitalComponentMissing", $"Disposal unit {Owner.Uid} is missing an anchorable component");
}
var collidable = Owner.EnsureComponent<CollidableComponent>();
collidable.AnchoredChanged += UpdateVisualState;
if (Owner.TryGetComponent(out CollidableComponent? collidable))
{
collidable.AnchoredChanged += UpdateVisualState;
}
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
@@ -513,7 +580,7 @@ namespace Content.Server.GameObjects.Components.Disposal
_container.ForceRemove(entity);
}
_userInterface.CloseAll();
UserInterface?.CloseAll();
_automaticEngageToken?.Cancel();
_automaticEngageToken = null;
@@ -571,7 +638,7 @@ namespace Content.Server.GameObjects.Components.Disposal
return false;
}
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
return true;
}
@@ -587,7 +654,8 @@ namespace Content.Server.GameObjects.Components.Disposal
bool IDragDropOn.DragDropOn(DragDropEventArgs eventArgs)
{
return TryInsert(eventArgs.Dropped);
_ = TryInsert(eventArgs.Dropped, eventArgs.User);
return true;
}
[Verb]
@@ -609,7 +677,7 @@ namespace Content.Server.GameObjects.Components.Disposal
protected override void Activate(IEntity user, DisposalUnitComponent component)
{
component.TryInsert(user);
_ = component.TryInsert(user, user);
}
}

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Interactable;
@@ -34,10 +35,7 @@ namespace Content.Server.GameObjects.Components.Doors
/// </summary>
private static readonly TimeSpan PowerWiresTimeout = TimeSpan.FromSeconds(5.0);
private PowerReceiverComponent _powerReceiver;
private WiresComponent _wires;
private CancellationTokenSource _powerWiresPulsedTimerCancel;
private CancellationTokenSource _powerWiresPulsedTimerCancel = new CancellationTokenSource();
private bool _powerWiresPulsed;
@@ -89,13 +87,15 @@ namespace Content.Server.GameObjects.Components.Doors
private void UpdateWiresStatus()
{
WiresComponent? wires;
var powerLight = new StatusLightData(Color.Yellow, StatusLightState.On, "POWR");
if (PowerWiresPulsed)
{
powerLight = new StatusLightData(Color.Yellow, StatusLightState.BlinkingFast, "POWR");
}
else if (_wires.IsWireCut(Wires.MainPower) &&
_wires.IsWireCut(Wires.BackupPower))
else if (Owner.TryGetComponent(out wires) &&
wires.IsWireCut(Wires.MainPower) &&
wires.IsWireCut(Wires.BackupPower))
{
powerLight = new StatusLightData(Color.Red, StatusLightState.On, "POWR");
}
@@ -114,12 +114,17 @@ namespace Content.Server.GameObjects.Components.Doors
var safetyStatus =
new StatusLightData(Color.Red, Safety ? StatusLightState.On : StatusLightState.Off, "SAFE");
_wires.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
_wires.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
_wires.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
_wires.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT"));
_wires.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus);
_wires.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus);
if (!Owner.TryGetComponent(out wires))
{
return;
}
wires.SetStatus(AirlockWireStatus.PowerIndicator, powerLight);
wires.SetStatus(AirlockWireStatus.BoltIndicator, boltStatus);
wires.SetStatus(AirlockWireStatus.BoltLightIndicator, boltLightsStatus);
wires.SetStatus(AirlockWireStatus.AIControlIndicator, new StatusLightData(Color.Purple, StatusLightState.BlinkingSlow, "AICT"));
wires.SetStatus(AirlockWireStatus.TimingIndicator, timingStatus);
wires.SetStatus(AirlockWireStatus.SafetyIndicator, safetyStatus);
/*
_wires.SetStatus(6, powerLight);
_wires.SetStatus(7, powerLight);
@@ -131,26 +136,45 @@ namespace Content.Server.GameObjects.Components.Doors
private void UpdatePowerCutStatus()
{
_powerReceiver.PowerDisabled = PowerWiresPulsed ||
_wires.IsWireCut(Wires.MainPower) ||
_wires.IsWireCut(Wires.BackupPower);
if (!Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
return;
}
if (PowerWiresPulsed)
{
receiver.PowerDisabled = true;
return;
}
if (!Owner.TryGetComponent(out WiresComponent? wires))
{
return;
}
receiver.PowerDisabled =
wires.IsWireCut(Wires.MainPower) ||
wires.IsWireCut(Wires.BackupPower);
}
private void UpdateBoltLightStatus()
{
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(DoorVisuals.BoltLights, BoltLightsVisible);
}
}
protected override DoorState State
public override DoorState State
{
set
protected set
{
base.State = value;
// Only show the maintenance panel if the airlock is closed
_wires.IsPanelVisible = value != DoorState.Open;
if (Owner.TryGetComponent(out WiresComponent? wires))
{
wires.IsPanelVisible = value != DoorState.Open;
}
// If the door is closed, we should look if the bolt was locked while closing
UpdateBoltLightStatus();
}
@@ -159,25 +183,32 @@ namespace Content.Server.GameObjects.Components.Doors
public override void Initialize()
{
base.Initialize();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_wires = Owner.GetComponent<WiresComponent>();
_powerReceiver.OnPowerStateChanged += PowerDeviceOnOnPowerStateChanged;
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
appearance.SetData(DoorVisuals.Powered, _powerReceiver.Powered);
receiver.OnPowerStateChanged += PowerDeviceOnOnPowerStateChanged;
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(DoorVisuals.Powered, receiver.Powered);
}
}
}
public override void OnRemove()
{
_powerReceiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged;
if (Owner.TryGetComponent(out PowerReceiverComponent? receiver))
{
receiver.OnPowerStateChanged -= PowerDeviceOnOnPowerStateChanged;
}
base.OnRemove();
}
private void PowerDeviceOnOnPowerStateChanged(object sender, PowerStateEventArgs e)
private void PowerDeviceOnOnPowerStateChanged(object? sender, PowerStateEventArgs e)
{
if (Owner.TryGetComponent(out AppearanceComponent appearance))
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(DoorVisuals.Powered, e.Powered);
}
@@ -188,11 +219,12 @@ namespace Content.Server.GameObjects.Components.Doors
protected override void ActivateImpl(ActivateEventArgs args)
{
if (_wires.IsPanelOpen)
if (Owner.TryGetComponent(out WiresComponent? wires) &&
wires.IsPanelOpen)
{
if (args.User.TryGetComponent(out IActorComponent actor))
if (args.User.TryGetComponent(out IActorComponent? actor))
{
_wires.OpenInterface(actor.playerSession);
wires.OpenInterface(actor.playerSession);
}
}
else
@@ -272,8 +304,7 @@ namespace Content.Server.GameObjects.Components.Doors
case Wires.MainPower:
case Wires.BackupPower:
PowerWiresPulsed = true;
_powerWiresPulsedTimerCancel?.Cancel();
_powerWiresPulsedTimerCancel = new CancellationTokenSource();
_powerWiresPulsedTimerCancel.Cancel();
Timer.Spawn(PowerWiresTimeout,
() => PowerWiresPulsed = false,
_powerWiresPulsedTimerCancel.Token);
@@ -377,7 +408,8 @@ namespace Content.Server.GameObjects.Components.Doors
private bool IsPowered()
{
return _powerReceiver.Powered;
return !Owner.TryGetComponent(out PowerReceiverComponent? receiver)
|| receiver.Powered;
}
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
@@ -388,11 +420,12 @@ namespace Content.Server.GameObjects.Components.Doors
if (tool.HasQuality(ToolQuality.Cutting)
|| tool.HasQuality(ToolQuality.Multitool))
{
if (_wires.IsPanelOpen)
if (Owner.TryGetComponent(out WiresComponent? wires)
&& wires.IsPanelOpen)
{
if (eventArgs.User.TryGetComponent(out IActorComponent actor))
if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
_wires.OpenInterface(actor.playerSession);
wires.OpenInterface(actor.playerSession);
return true;
}
}

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Linq;
using System.Threading;
using Content.Server.Atmos;
@@ -35,10 +36,10 @@ namespace Content.Server.GameObjects.Components.Doors
private DoorState _state = DoorState.Closed;
protected virtual DoorState State
public virtual DoorState State
{
get => _state;
set => _state = value;
protected set => _state = value;
}
protected float OpenTimeCounter;
@@ -46,10 +47,7 @@ namespace Content.Server.GameObjects.Components.Doors
protected const float AutoCloseDelay = 5;
protected float CloseSpeed = AutoCloseDelay;
private AirtightComponent airtightComponent;
private ICollidableComponent _collidableComponent;
private AppearanceComponent _appearance;
private CancellationTokenSource _cancellationTokenSource;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
protected virtual TimeSpan CloseTimeOne => TimeSpan.FromSeconds(0.3f);
protected virtual TimeSpan CloseTimeTwo => TimeSpan.FromSeconds(0.9f);
@@ -72,21 +70,9 @@ namespace Content.Server.GameObjects.Components.Doors
serializer.DataField(ref _occludes, "occludes", true);
}
public override void Initialize()
{
base.Initialize();
airtightComponent = Owner.GetComponent<AirtightComponent>();
_collidableComponent = Owner.GetComponent<ICollidableComponent>();
_appearance = Owner.GetComponent<AppearanceComponent>();
_cancellationTokenSource = new CancellationTokenSource();
}
public override void OnRemove()
{
_cancellationTokenSource.Cancel();
_collidableComponent = null;
_appearance = null;
_cancellationTokenSource?.Cancel();
base.OnRemove();
}
@@ -108,7 +94,6 @@ namespace Content.Server.GameObjects.Components.Doors
ActivateImpl(eventArgs);
}
void ICollideBehavior.CollideWith(IEntity entity)
{
if (State != DoorState.Closed)
@@ -139,8 +124,10 @@ namespace Content.Server.GameObjects.Components.Doors
protected void SetAppearance(DoorVisualState state)
{
if (_appearance != null || Owner.TryGetComponent(out _appearance))
_appearance.SetData(DoorVisuals.VisualState, state);
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(DoorVisuals.VisualState, state);
}
}
public virtual bool CanOpen()
@@ -151,28 +138,53 @@ namespace Content.Server.GameObjects.Components.Doors
public virtual bool CanOpen(IEntity user)
{
if (!CanOpen()) return false;
if (!Owner.TryGetComponent(out AccessReader accessReader))
if (!Owner.TryGetComponent<AccessReader>(out var accessReader))
{
return true;
}
return accessReader.IsAllowed(user);
var doorSystem = EntitySystem.Get<DoorSystem>();
var isAirlockExternal = HasAccessType("External");
return doorSystem.AccessType switch
{
DoorSystem.AccessTypes.AllowAll => true,
DoorSystem.AccessTypes.AllowAllIdExternal => isAirlockExternal ? accessReader.IsAllowed(user) : true,
DoorSystem.AccessTypes.AllowAllNoExternal => !isAirlockExternal,
_ => accessReader.IsAllowed(user)
};
}
/// <summary>
/// Returns whether a door has a certain access type. For example, maintenance doors will have access type
/// "Maintenance" in their AccessReader.
/// </summary>
private bool HasAccessType(string accesType)
{
if(Owner.TryGetComponent<AccessReader>(out var accessReader))
{
return accessReader.AccessLists.Any(list => list.Contains(accesType));
}
return true;
}
public void TryOpen(IEntity user)
{
if (!CanOpen(user))
if (CanOpen(user))
{
Open();
if (user.TryGetComponent(out HandsComponent? hands) && hands.Count == 0)
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/bang.ogg", Owner,
AudioParams.Default.WithVolume(-2));
}
}
else
{
Deny();
return;
}
Open();
if (user.TryGetComponent(out HandsComponent hands) && hands.Count == 0)
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/bang.ogg", Owner,
AudioParams.Default.WithVolume(-2));
}
}
@@ -185,15 +197,22 @@ namespace Content.Server.GameObjects.Components.Doors
State = DoorState.Opening;
SetAppearance(DoorVisualState.Opening);
if (_occludes && Owner.TryGetComponent(out OccluderComponent occluder))
if (_occludes && Owner.TryGetComponent(out OccluderComponent? occluder))
{
occluder.Enabled = false;
}
Timer.Spawn(OpenTimeOne, async () =>
{
airtightComponent.AirBlocked = false;
_collidableComponent.Hard = false;
if (Owner.TryGetComponent(out AirtightComponent? airtight))
{
airtight.AirBlocked = false;
}
if (Owner.TryGetComponent(out ICollidableComponent? collidable))
{
collidable.Hard = false;
}
await Timer.Delay(OpenTimeTwo, _cancellationTokenSource.Token);
@@ -212,7 +231,7 @@ namespace Content.Server.GameObjects.Components.Doors
public virtual bool CanClose(IEntity user)
{
if (!CanClose()) return false;
if (!Owner.TryGetComponent(out AccessReader accessReader))
if (!Owner.TryGetComponent(out AccessReader? accessReader))
{
return true;
}
@@ -233,18 +252,22 @@ namespace Content.Server.GameObjects.Components.Doors
private void CheckCrush()
{
if (!Owner.TryGetComponent(out ICollidableComponent? body))
{
return;
}
// Check if collides with something
var collidesWith = _collidableComponent.GetCollidingEntities(Vector2.Zero, false);
var collidesWith = body.GetCollidingEntities(Vector2.Zero, false);
if (collidesWith.Count() != 0)
{
// Crush
bool hitSomeone = false;
foreach (var e in collidesWith)
{
if (!e.TryGetComponent(out StunnableComponent stun)
|| !e.TryGetComponent(out IDamageableComponent damage)
|| !e.TryGetComponent(out ICollidableComponent otherBody)
|| !Owner.TryGetComponent(out ICollidableComponent body))
if (!e.TryGetComponent(out StunnableComponent? stun)
|| !e.TryGetComponent(out IDamageableComponent? damage)
|| !e.TryGetComponent(out ICollidableComponent? otherBody))
continue;
var percentage = otherBody.WorldAABB.IntersectPercentage(body.WorldAABB);
@@ -319,7 +342,8 @@ namespace Content.Server.GameObjects.Components.Doors
public bool Close()
{
bool shouldCheckCrush = false;
if (_collidableComponent.IsColliding(Vector2.Zero, false))
if (Owner.TryGetComponent(out ICollidableComponent? collidable) && collidable.IsColliding(Vector2.Zero, false))
{
if (Safety)
return false;
@@ -331,7 +355,7 @@ namespace Content.Server.GameObjects.Components.Doors
State = DoorState.Closing;
OpenTimeCounter = 0;
SetAppearance(DoorVisualState.Closing);
if (_occludes && Owner.TryGetComponent(out OccluderComponent occluder))
if (_occludes && Owner.TryGetComponent(out OccluderComponent? occluder))
{
occluder.Enabled = true;
}
@@ -343,8 +367,15 @@ namespace Content.Server.GameObjects.Components.Doors
CheckCrush();
}
airtightComponent.AirBlocked = true;
_collidableComponent.Hard = true;
if (Owner.TryGetComponent(out AirtightComponent? airtight))
{
airtight.AirBlocked = true;
}
if (Owner.TryGetComponent(out ICollidableComponent? body))
{
body.Hard = true;
}
await Timer.Delay(CloseTimeTwo, _cancellationTokenSource.Token);
@@ -391,7 +422,7 @@ namespace Content.Server.GameObjects.Components.Doors
}
}
protected enum DoorState
public enum DoorState
{
Closed,
Open,

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.Chemistry;
using Content.Shared.Chemistry;
@@ -19,23 +20,25 @@ namespace Content.Server.GameObjects.Components.Fluids
[RegisterComponent]
public class BucketComponent : Component, IInteractUsing
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
public override string Name => "Bucket";
public ReagentUnit MaxVolume
{
get => _contents.MaxVolume;
set => _contents.MaxVolume = value;
get => Owner.TryGetComponent(out SolutionComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero;
set
{
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution.MaxVolume = value;
}
}
}
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
public ReagentUnit CurrentVolume => Owner.TryGetComponent(out SolutionComponent? solution)
? solution.CurrentVolume
: ReagentUnit.Zero;
private SolutionComponent _contents;
private string _sound;
private string? _sound;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
@@ -47,16 +50,28 @@ namespace Content.Server.GameObjects.Components.Fluids
public override void Initialize()
{
base.Initialize();
_contents = Owner.GetComponent<SolutionComponent>();
Owner.EnsureComponent<SolutionComponent>();
}
private bool TryGiveToMop(MopComponent mopComponent)
{
if (!Owner.TryGetComponent(out SolutionComponent? contents))
{
return false;
}
var mopContents = mopComponent.Contents;
if (mopContents == null)
{
return false;
}
// Let's fill 'er up
// If this is called the mop should be empty but just in case we'll do Max - Current
var transferAmount = ReagentUnit.Min(mopComponent.MaxVolume - mopComponent.CurrentVolume, CurrentVolume);
var solution = _contents.SplitSolution(transferAmount);
if (!mopComponent.Contents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0)
var solution = contents.SplitSolution(transferAmount);
if (!mopContents.TryAddSolution(solution) || mopComponent.CurrentVolume == 0)
{
return false;
}
@@ -73,7 +88,12 @@ namespace Content.Server.GameObjects.Components.Fluids
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.TryGetComponent(out MopComponent mopComponent))
if (!Owner.TryGetComponent(out SolutionComponent? contents))
{
return false;
}
if (!eventArgs.Using.TryGetComponent(out MopComponent? mopComponent))
{
return false;
}
@@ -86,7 +106,7 @@ namespace Content.Server.GameObjects.Components.Fluids
return false;
}
Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Splish"));
Owner.PopupMessage(eventArgs.User, Loc.GetString("Splish"));
return true;
}
@@ -96,15 +116,22 @@ namespace Content.Server.GameObjects.Components.Fluids
return false;
}
var solution = mopComponent.Contents.SplitSolution(transferAmount);
if (!_contents.TryAddSolution(solution))
var mopContents = mopComponent.Contents;
if (mopContents == null)
{
return false;
}
var solution = mopContents.SplitSolution(transferAmount);
if (!contents.TryAddSolution(solution))
{
//This really shouldn't happen
throw new InvalidOperationException();
}
// Give some visual feedback shit's happening (for anyone who can't hear sound)
Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Sploosh"));
Owner.PopupMessage(eventArgs.User, Loc.GetString("Sploosh"));
if (_sound == null)
{
@@ -114,7 +141,6 @@ namespace Content.Server.GameObjects.Components.Fluids
EntitySystem.Get<AudioSystem>().PlayFromEntity(_sound, Owner);
return true;
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Server.GameObjects.Components.Chemistry;
#nullable enable
using Content.Server.GameObjects.Components.Chemistry;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.Interfaces;
@@ -6,8 +7,8 @@ using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Fluids
@@ -18,21 +19,24 @@ namespace Content.Server.GameObjects.Components.Fluids
[RegisterComponent]
public class MopComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
public override string Name => "Mop";
internal SolutionComponent Contents => _contents;
private SolutionComponent _contents;
public SolutionComponent? Contents => Owner.GetComponentOrNull<SolutionComponent>();
public ReagentUnit MaxVolume
{
get => _contents.MaxVolume;
set => _contents.MaxVolume = value;
get => Owner.GetComponentOrNull<SolutionComponent>()?.MaxVolume ?? ReagentUnit.Zero;
set
{
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution.MaxVolume = value;
}
}
}
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
public ReagentUnit CurrentVolume =>
Owner.GetComponentOrNull<SolutionComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
// Currently there's a separate amount for pickup and dropoff so
// Picking up a puddle requires multiple clicks
@@ -41,7 +45,7 @@ namespace Content.Server.GameObjects.Components.Fluids
public ReagentUnit PickupAmount => _pickupAmount;
private ReagentUnit _pickupAmount;
private string _pickupSound;
private string _pickupSound = "";
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
@@ -54,12 +58,16 @@ namespace Content.Server.GameObjects.Components.Fluids
public override void Initialize()
{
base.Initialize();
_contents = Owner.GetComponent<SolutionComponent>();
if (!Owner.EnsureComponent(out SolutionComponent _))
{
Logger.Warning($"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
}
void IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!Owner.TryGetComponent(out SolutionComponent? contents)) return;
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
if (CurrentVolume <= 0)
@@ -71,12 +79,12 @@ namespace Content.Server.GameObjects.Components.Fluids
if (eventArgs.Target == null)
{
// Drop the liquid on the mop on to the ground
SpillHelper.SpillAt(eventArgs.ClickLocation, _contents.SplitSolution(CurrentVolume), "PuddleSmear");
SpillHelper.SpillAt(eventArgs.ClickLocation, contents.SplitSolution(CurrentVolume), "PuddleSmear");
return;
}
if (!eventArgs.Target.TryGetComponent(out PuddleComponent puddleComponent))
if (!eventArgs.Target.TryGetComponent(out PuddleComponent? puddleComponent))
{
return;
}
@@ -107,23 +115,22 @@ namespace Content.Server.GameObjects.Components.Fluids
if (puddleCleaned) //After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly.
{
SpillHelper.SpillAt(eventArgs.ClickLocation, _contents.SplitSolution(transferAmount), "PuddleSmear");
SpillHelper.SpillAt(eventArgs.ClickLocation, contents.SplitSolution(transferAmount), "PuddleSmear");
}
else
{
_contents.SplitSolution(transferAmount);
contents.SplitSolution(transferAmount);
}
// Give some visual feedback shit's happening (for anyone who can't hear sound)
Owner.PopupMessage(eventArgs.User, _localizationManager.GetString("Swish"));
Owner.PopupMessage(eventArgs.User, Loc.GetString("Swish"));
if (_pickupSound == null)
if (string.IsNullOrWhiteSpace(_pickupSound))
{
return;
}
EntitySystem.Get<AudioSystem>().PlayFromEntity(_pickupSound, Owner);
}
}
}

View File

@@ -46,10 +46,7 @@ namespace Content.Server.GameObjects.Components.Fluids
// based on behaviour (e.g. someone being punched vs slashed with a sword would have different blood sprite)
// to check for low volumes for evaporation or whatever
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly ILocalizationManager _loc;
#pragma warning restore 649
[Dependency] private readonly IMapManager _mapManager = default!;
public override string Name => "Puddle";
@@ -116,6 +113,7 @@ namespace Content.Server.GameObjects.Components.Fluids
public override void Initialize()
{
base.Initialize();
if (Owner.TryGetComponent(out SolutionComponent solutionComponent))
{
_contents = solutionComponent;
@@ -123,21 +121,27 @@ namespace Content.Server.GameObjects.Components.Fluids
else
{
_contents = Owner.AddComponent<SolutionComponent>();
_contents.Initialize();
}
_snapGrid = Owner.GetComponent<SnapGridComponent>();
_snapGrid = Owner.EnsureComponent<SnapGridComponent>();
// Smaller than 1m^3 for now but realistically this shouldn't be hit
MaxVolume = ReagentUnit.New(1000);
// Random sprite state set server-side so it's consistent across all clients
_spriteComponent = Owner.GetComponent<SpriteComponent>();
_spriteComponent = Owner.EnsureComponent<SpriteComponent>();
var robustRandom = IoCManager.Resolve<IRobustRandom>();
var randomVariant = robustRandom.Next(0, _spriteVariants - 1);
var baseName = new ResourcePath(_spriteComponent.BaseRSIPath).FilenameWithoutExtension;
_spriteComponent.LayerSetState(0, $"{baseName}-{randomVariant}"); // TODO: Remove hardcode
if (_spriteComponent.BaseRSIPath != null)
{
var baseName = new ResourcePath(_spriteComponent.BaseRSIPath).FilenameWithoutExtension;
_spriteComponent.LayerSetState(0, $"{baseName}-{randomVariant}"); // TODO: Remove hardcode
}
// UpdateAppearance should get called soon after this so shouldn't need to call Dirty() here
UpdateStatus();
@@ -153,7 +157,7 @@ namespace Content.Server.GameObjects.Components.Fluids
{
if(_slippery)
{
message.AddText(_loc.GetString("It looks slippery."));
message.AddText(Loc.GetString("It looks slippery."));
}
}

View File

@@ -8,6 +8,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -16,10 +17,9 @@ namespace Content.Server.GameObjects.Components.Fluids
[RegisterComponent]
class SprayComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
#pragma warning restore 649
public override string Name => "Spray";
private ReagentUnit _transferAmount;
@@ -46,13 +46,17 @@ namespace Content.Server.GameObjects.Components.Fluids
set => _sprayVelocity = value;
}
private SolutionComponent _contents;
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
public ReagentUnit CurrentVolume => Owner.GetComponentOrNull<SolutionComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
public override void Initialize()
{
base.Initialize();
_contents = Owner.GetComponent<SolutionComponent>();
if (!Owner.EnsureComponent(out SolutionComponent _))
{
Logger.Warning(
$"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(SolutionComponent)}");
}
}
public override void ExposeData(ObjectSerializer serializer)
@@ -75,8 +79,11 @@ namespace Content.Server.GameObjects.Components.Fluids
if (eventArgs.ClickLocation.GridID != playerPos.GridID)
return;
if (!Owner.TryGetComponent(out SolutionComponent contents))
return;
var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized;
var solution = _contents.SplitSolution(_transferAmount);
var solution = contents.SplitSolution(_transferAmount);
playerPos = playerPos.Offset(direction); // Move a bit so we don't hit the player
//TODO: check for wall?

View File

@@ -29,6 +29,7 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
using Content.Server.GameObjects.Components.ActionBlocking;
namespace Content.Server.GameObjects.Components.GUI
{
@@ -37,9 +38,7 @@ namespace Content.Server.GameObjects.Components.GUI
[ComponentReference(typeof(ISharedHandsComponent))]
public class HandsComponent : SharedHandsComponent, IHandsComponent, IBodyPartAdded, IBodyPartRemoved
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
#pragma warning restore 649
private string? _activeHand;
private uint _nextHand;
@@ -446,6 +445,7 @@ namespace Content.Server.GameObjects.Components.GUI
ActiveHand ??= name;
OnItemChanged?.Invoke();
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner));
Dirty();
}
@@ -468,6 +468,7 @@ namespace Content.Server.GameObjects.Components.GUI
}
OnItemChanged?.Invoke();
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new HandCountChangedEvent(Owner));
Dirty();
}
@@ -793,4 +794,14 @@ namespace Content.Server.GameObjects.Components.GUI
return new SharedHand(index, Name, Entity?.Uid, location);
}
}
public class HandCountChangedEvent : EntitySystemMessage
{
public HandCountChangedEvent(IEntity sender)
{
Sender = sender;
}
public IEntity Sender { get; }
}
}

View File

@@ -21,7 +21,7 @@ namespace Content.Server.GameObjects.Components.GUI
{
base.Initialize();
_inventory = Owner.GetComponent<InventoryComponent>();
_inventory = Owner.EnsureComponent<InventoryComponent>();
}
bool IInventoryController.CanEquip(Slots slot, IEntity entity, bool flagsCheck, out string reason)

View File

@@ -28,10 +28,8 @@ namespace Content.Server.GameObjects.Components.GUI
[RegisterComponent]
public class InventoryComponent : SharedInventoryComponent, IExAct, IEffectBlocker, IPressureProtection
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly IServerNotifyManager _serverNotifyManager;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IServerNotifyManager _serverNotifyManager = default!;
[ViewVariables]
private readonly Dictionary<Slots, ContainerSlot> _slotContainers = new Dictionary<Slots, ContainerSlot>();

View File

@@ -1,12 +1,15 @@
using System;
#nullable enable
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Content.Server.GameObjects.Components.ActionBlocking;
using Content.Server.GameObjects.Components.Items.Storage;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Server.Interfaces;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.GUI;
using Content.Shared.GameObjects.Components.Inventory;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.GameObjects.Components.UserInterface;
using Robust.Server.Interfaces.GameObjects;
@@ -16,7 +19,6 @@ using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.ViewVariables;
using static Content.Shared.GameObjects.Components.Inventory.EquipmentSlotDefines;
@@ -25,27 +27,39 @@ namespace Content.Server.GameObjects.Components.GUI
[RegisterComponent]
public sealed class StrippableComponent : SharedStrippableComponent, IDragDrop
{
[Dependency] private IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
public const float StripDelay = 2f;
[ViewVariables]
private BoundUserInterface _userInterface;
private InventoryComponent _inventoryComponent;
private HandsComponent _handsComponent;
[ViewVariables]
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(StrippingUiKey.Key);
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(StrippingUiKey.Key);
_userInterface.OnReceiveMessage += HandleUserInterfaceMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += HandleUserInterfaceMessage;
}
_inventoryComponent = Owner.GetComponent<InventoryComponent>();
_handsComponent = Owner.GetComponent<HandsComponent>();
Owner.EnsureComponent<InventoryComponent>();
Owner.EnsureComponent<HandsComponent>();
Owner.EnsureComponent<CuffableComponent>();
if (Owner.TryGetComponent(out CuffableComponent? cuffed))
{
cuffed.OnCuffedStateChanged += UpdateSubscribed;
}
if (Owner.TryGetComponent(out InventoryComponent? inventory))
{
inventory.OnItemChanged += UpdateSubscribed;
}
_inventoryComponent.OnItemChanged += UpdateSubscribed;
if (Owner.TryGetComponent(out HandsComponent? hands))
{
hands.OnItemChanged += UpdateSubscribed;
}
// Initial update.
UpdateSubscribed();
@@ -53,33 +67,69 @@ namespace Content.Server.GameObjects.Components.GUI
private void UpdateSubscribed()
{
if (UserInterface == null)
{
return;
}
var inventory = GetInventorySlots();
var hands = GetHandSlots();
var cuffs = GetHandcuffs();
_userInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands));
UserInterface.SetState(new StrippingBoundUserInterfaceState(inventory, hands, cuffs));
}
public bool CanBeStripped(IEntity by)
{
return by != Owner
&& by.HasComponent<HandsComponent>()
&& ActionBlockerSystem.CanInteract(by);
}
public bool CanDragDrop(DragDropEventArgs eventArgs)
{
return eventArgs.User.HasComponent<HandsComponent>()
&& eventArgs.Target != eventArgs.Dropped && eventArgs.Target == eventArgs.User;
return eventArgs.Target != eventArgs.Dropped
&& eventArgs.Target == eventArgs.User
&& CanBeStripped(eventArgs.User);
}
public bool DragDrop(DragDropEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return false;
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false;
OpenUserInterface(actor.playerSession);
return true;
}
private Dictionary<EntityUid, string> GetHandcuffs()
{
var dictionary = new Dictionary<EntityUid, string>();
if (!Owner.TryGetComponent(out CuffableComponent? cuffed))
{
return dictionary;
}
foreach (IEntity entity in cuffed.StoredEntities)
{
dictionary.Add(entity.Uid, entity.Name);
}
return dictionary;
}
private Dictionary<Slots, string> GetInventorySlots()
{
var dictionary = new Dictionary<Slots, string>();
foreach (var slot in _inventoryComponent.Slots)
if (!Owner.TryGetComponent(out InventoryComponent? inventory))
{
dictionary[slot] = _inventoryComponent.GetSlotItem(slot)?.Owner.Name ?? "None";
return dictionary;
}
foreach (var slot in inventory.Slots)
{
dictionary[slot] = inventory.GetSlotItem(slot)?.Owner.Name ?? "None";
}
return dictionary;
@@ -89,9 +139,14 @@ namespace Content.Server.GameObjects.Components.GUI
{
var dictionary = new Dictionary<string, string>();
foreach (var hand in _handsComponent.Hands)
if (!Owner.TryGetComponent(out HandsComponent? hands))
{
dictionary[hand] = _handsComponent.GetItem(hand)?.Owner.Name ?? "None";
return dictionary;
}
foreach (var hand in hands.Hands)
{
dictionary[hand] = hands.GetItem(hand)?.Owner.Name ?? "None";
}
return dictionary;
@@ -99,7 +154,7 @@ namespace Content.Server.GameObjects.Components.GUI
private void OpenUserInterface(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
/// <summary>
@@ -336,34 +391,80 @@ namespace Content.Server.GameObjects.Components.GUI
private void HandleUserInterfaceMessage(ServerBoundUserInterfaceMessage obj)
{
var user = obj.Session.AttachedEntity;
if (user == null || !(user.TryGetComponent(out HandsComponent userHands))) return;
if (user == null || !(user.TryGetComponent(out HandsComponent? userHands))) return;
var placingItem = userHands.GetActiveHand != null;
switch (obj.Message)
{
case StrippingInventoryButtonPressed inventoryMessage:
var inventory = Owner.GetComponent<InventoryComponent>();
if (inventory.TryGetSlotItem(inventoryMessage.Slot, out ItemComponent _))
placingItem = false;
if (Owner.TryGetComponent<InventoryComponent>(out var inventory))
{
if (inventory.TryGetSlotItem(inventoryMessage.Slot, out ItemComponent _))
placingItem = false;
if(placingItem)
PlaceActiveHandItemInInventory(user, inventoryMessage.Slot);
else
TakeItemFromInventory(user, inventoryMessage.Slot);
if (placingItem)
PlaceActiveHandItemInInventory(user, inventoryMessage.Slot);
else
TakeItemFromInventory(user, inventoryMessage.Slot);
}
break;
case StrippingHandButtonPressed handMessage:
var hands = Owner.GetComponent<HandsComponent>();
if (hands.TryGetItem(handMessage.Hand, out _))
placingItem = false;
if (Owner.TryGetComponent<HandsComponent>(out var hands))
{
if (hands.TryGetItem(handMessage.Hand, out _))
placingItem = false;
if(placingItem)
PlaceActiveHandItemInHands(user, handMessage.Hand);
else
TakeItemFromHands(user, handMessage.Hand);
if (placingItem)
PlaceActiveHandItemInHands(user, handMessage.Hand);
else
TakeItemFromHands(user, handMessage.Hand);
}
break;
case StrippingHandcuffButtonPressed handcuffMessage:
if (Owner.TryGetComponent<CuffableComponent>(out var cuffed))
{
foreach (var entity in cuffed.StoredEntities)
{
if (entity.Uid == handcuffMessage.Handcuff)
{
cuffed.TryUncuff(user, entity);
return;
}
}
}
break;
}
}
[Verb]
private sealed class StripVerb : Verb<StrippableComponent>
{
protected override void GetData(IEntity user, StrippableComponent component, VerbData data)
{
if (!component.CanBeStripped(user))
{
data.Visibility = VerbVisibility.Invisible;
return;
}
data.Visibility = VerbVisibility.Visible;
data.Text = Loc.GetString("Strip");
}
protected override void Activate(IEntity user, StrippableComponent component)
{
if (!user.TryGetComponent(out IActorComponent? actor))
{
return;
}
component.OpenUserInterface(actor.playerSession);
}
}
}

View File

@@ -1,9 +1,10 @@
using System.Threading.Tasks;
#nullable enable
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.Server.Utility;
using Content.Shared.GameObjects.Components.Gravity;
using Content.Shared.GameObjects.Components.Interactable;
using Content.Shared.GameObjects.EntitySystems;
@@ -16,17 +17,13 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Gravity
{
[RegisterComponent]
public class GravityGeneratorComponent : SharedGravityGeneratorComponent, IInteractUsing, IBreakAct, IInteractHand
{
private BoundUserInterface _userInterface;
private PowerReceiverComponent _powerReceiver;
private SpriteComponent _sprite;
private bool _switchedOn;
@@ -34,7 +31,7 @@ namespace Content.Server.GameObjects.Components.Gravity
private GravityGeneratorStatus _status;
public bool Powered => _powerReceiver.Powered;
public bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
public bool SwitchedOn => _switchedOn;
@@ -64,15 +61,17 @@ namespace Content.Server.GameObjects.Components.Gravity
public override string Name => "GravityGenerator";
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(GravityGeneratorUiKey.Key);
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(GravityGeneratorUiKey.Key);
_userInterface.OnReceiveMessage += HandleUIMessage;
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_sprite = Owner.GetComponent<SpriteComponent>();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += HandleUIMessage;
}
_switchedOn = true;
_intact = true;
_status = GravityGeneratorStatus.On;
@@ -101,7 +100,7 @@ namespace Content.Server.GameObjects.Components.Gravity
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.TryGetComponent(out WelderComponent tool))
if (!eventArgs.Using.TryGetComponent(out WelderComponent? tool))
return false;
if (!await tool.UseTool(eventArgs.User, Owner, 2f, ToolQuality.Welding, 5f))
@@ -150,7 +149,7 @@ namespace Content.Server.GameObjects.Components.Gravity
switch (message.Message)
{
case GeneratorStatusRequestMessage _:
_userInterface.SetState(new GeneratorState(Status == GravityGeneratorStatus.On));
UserInterface?.SetState(new GeneratorState(Status == GravityGeneratorStatus.On));
break;
case SwitchGeneratorMessage msg:
_switchedOn = msg.On;
@@ -163,35 +162,51 @@ namespace Content.Server.GameObjects.Components.Gravity
private void OpenUserInterface(IPlayerSession playerSession)
{
_userInterface.Open(playerSession);
UserInterface?.Open(playerSession);
}
private void MakeBroken()
{
_status = GravityGeneratorStatus.Broken;
_sprite.LayerSetState(0, "broken");
_sprite.LayerSetVisible(1, false);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.LayerSetState(0, "broken");
sprite.LayerSetVisible(1, false);
}
}
private void MakeUnpowered()
{
_status = GravityGeneratorStatus.Unpowered;
_sprite.LayerSetState(0, "off");
_sprite.LayerSetVisible(1, false);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.LayerSetState(0, "off");
sprite.LayerSetVisible(1, false);
}
}
private void MakeOff()
{
_status = GravityGeneratorStatus.Off;
_sprite.LayerSetState(0, "off");
_sprite.LayerSetVisible(1, false);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.LayerSetState(0, "off");
sprite.LayerSetVisible(1, false);
}
}
private void MakeOn()
{
_status = GravityGeneratorStatus.On;
_sprite.LayerSetState(0, "on");
_sprite.LayerSetVisible(1, true);
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite.LayerSetState(0, "on");
sprite.LayerSetVisible(1, true);
}
}
}

View File

@@ -1,8 +1,10 @@
using System;
#nullable enable
using System;
using System.Linq;
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.Interfaces;
using Content.Server.Mobs;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Instruments;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -34,12 +36,8 @@ namespace Content.Server.GameObjects.Components.Instruments
IUse,
IThrown
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _notifyManager;
[Dependency] private readonly IGameTiming _gameTiming;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private static readonly TimeSpan OneSecAgo = TimeSpan.FromSeconds(-1);
@@ -47,7 +45,7 @@ namespace Content.Server.GameObjects.Components.Instruments
/// The client channel currently playing the instrument, or null if there's none.
/// </summary>
[ViewVariables]
private IPlayerSession _instrumentPlayer;
private IPlayerSession? _instrumentPlayer;
private bool _handheld;
@@ -72,9 +70,6 @@ namespace Content.Server.GameObjects.Components.Instruments
[ViewVariables]
private int _midiEventCount = 0;
[ViewVariables]
private BoundUserInterface _userInterface;
/// <summary>
/// Whether the instrument is an item which can be held or not.
/// </summary>
@@ -95,7 +90,7 @@ namespace Content.Server.GameObjects.Components.Instruments
}
}
public IPlayerSession InstrumentPlayer
public IPlayerSession? InstrumentPlayer
{
get => _instrumentPlayer;
private set
@@ -108,11 +103,13 @@ namespace Content.Server.GameObjects.Components.Instruments
_instrumentPlayer = value;
if (value != null)
_instrumentPlayer.PlayerStatusChanged += OnPlayerStatusChanged;
_instrumentPlayer!.PlayerStatusChanged += OnPlayerStatusChanged;
}
}
private void OnPlayerStatusChanged(object sender, SessionStatusEventArgs e)
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(InstrumentUiKey.Key);
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
{
if (e.Session != _instrumentPlayer || e.NewStatus != SessionStatus.Disconnected) return;
InstrumentPlayer = null;
@@ -122,8 +119,11 @@ namespace Content.Server.GameObjects.Components.Instruments
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>().GetBoundUserInterface(InstrumentUiKey.Key);
_userInterface.OnClosed += UserInterfaceOnClosed;
if (UserInterface != null)
{
UserInterface.OnClosed += UserInterfaceOnClosed;
}
}
public override void ExposeData(ObjectSerializer serializer)
@@ -137,14 +137,14 @@ namespace Content.Server.GameObjects.Components.Instruments
return new InstrumentState(Playing, _lastSequencerTick);
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession session = null)
public override void HandleNetworkMessage(ComponentMessage message, INetChannel channel, ICommonSession? session = null)
{
base.HandleNetworkMessage(message, channel, session);
switch (message)
{
case InstrumentMidiEventMessage midiEventMsg:
if (!Playing || session != _instrumentPlayer) return;
if (!Playing || session != _instrumentPlayer || InstrumentPlayer == null) return;
var send = true;
@@ -231,7 +231,7 @@ namespace Content.Server.GameObjects.Components.Instruments
Clean();
SendNetworkMessage(new InstrumentStopMidiMessage());
InstrumentPlayer = null;
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
public void Thrown(ThrownEventArgs eventArgs)
@@ -239,7 +239,7 @@ namespace Content.Server.GameObjects.Components.Instruments
Clean();
SendNetworkMessage(new InstrumentStopMidiMessage());
InstrumentPlayer = null;
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
public void HandSelected(HandSelectedEventArgs eventArgs)
@@ -255,12 +255,12 @@ namespace Content.Server.GameObjects.Components.Instruments
{
Clean();
SendNetworkMessage(new InstrumentStopMidiMessage());
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
public void Activate(ActivateEventArgs eventArgs)
{
if (Handheld || !eventArgs.User.TryGetComponent(out IActorComponent actor)) return;
if (Handheld || !eventArgs.User.TryGetComponent(out IActorComponent? actor)) return;
if (InstrumentPlayer != null) return;
@@ -270,7 +270,7 @@ namespace Content.Server.GameObjects.Components.Instruments
public bool UseEntity(UseEntityEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor)) return false;
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor)) return false;
if (InstrumentPlayer == actor.playerSession)
{
@@ -291,7 +291,7 @@ namespace Content.Server.GameObjects.Components.Instruments
private void OpenUserInterface(IPlayerSession session)
{
_userInterface.Open(session);
UserInterface?.Open(session);
}
public override void Update(float delta)
@@ -302,7 +302,7 @@ namespace Content.Server.GameObjects.Components.Instruments
{
InstrumentPlayer = null;
Clean();
_userInterface.CloseAll();
UserInterface?.CloseAll();
}
if ((_batchesDropped >= MaxMidiBatchDropped
@@ -314,9 +314,9 @@ namespace Content.Server.GameObjects.Components.Instruments
SendNetworkMessage(new InstrumentStopMidiMessage());
Playing = false;
_userInterface.CloseAll();
UserInterface?.CloseAll();
if (mob.TryGetComponent(out StunnableComponent stun))
if (mob != null && mob.TryGetComponent(out StunnableComponent? stun))
{
stun.Stun(1);
Clean();

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
#nullable enable
using System.Threading.Tasks;
using Content.Server.GameObjects.Components.GUI;
using Content.Server.GameObjects.Components.Items.Clothing;
using Content.Server.GameObjects.Components.Items.Storage;
@@ -27,28 +28,26 @@ namespace Content.Server.GameObjects.Components.Interactable
/// Component that represents a handheld lightsource which can be toggled on and off.
/// </summary>
[RegisterComponent]
internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing, IMapInit
internal sealed class HandheldLightComponent : SharedHandheldLightComponent, IUse, IExamine, IInteractUsing,
IMapInit
{
#pragma warning disable 649
[Dependency] private readonly ISharedNotifyManager _notifyManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
#pragma warning restore 649
[Dependency] private readonly ISharedNotifyManager _notifyManager = default!;
[ViewVariables(VVAccess.ReadWrite)] public float Wattage { get; set; } = 10;
[ViewVariables] private ContainerSlot _cellContainer;
private PointLightComponent _pointLight;
private SpriteComponent _spriteComponent;
private ClothingComponent _clothingComponent;
[ViewVariables] private ContainerSlot _cellContainer = default!;
[ViewVariables]
private BatteryComponent Cell
private BatteryComponent? Cell
{
get
{
if (_cellContainer.ContainedEntity == null) return null;
if (_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent? cell))
{
return cell;
}
_cellContainer.ContainedEntity.TryGetComponent(out BatteryComponent cell);
return cell;
return null;
}
}
@@ -58,6 +57,8 @@ namespace Content.Server.GameObjects.Components.Interactable
[ViewVariables]
public bool Activated { get; private set; }
[ViewVariables] protected override bool HasCell => Cell != null;
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!eventArgs.Using.HasComponent<BatteryComponent>()) return false;
@@ -81,11 +82,9 @@ namespace Content.Server.GameObjects.Components.Interactable
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
var loc = IoCManager.Resolve<ILocalizationManager>();
if (Activated)
{
message.AddMarkup(loc.GetString("The light is currently [color=darkgreen]on[/color]."));
message.AddMarkup(Loc.GetString("The light is currently [color=darkgreen]on[/color]."));
}
}
@@ -98,11 +97,10 @@ namespace Content.Server.GameObjects.Components.Interactable
{
base.Initialize();
_pointLight = Owner.GetComponent<PointLightComponent>();
_spriteComponent = Owner.GetComponent<SpriteComponent>();
Owner.TryGetComponent(out _clothingComponent);
Owner.EnsureComponent<PointLightComponent>();
_cellContainer =
ContainerManagerComponent.Ensure<ContainerSlot>("flashlight_cell_container", Owner, out _);
Dirty();
}
@@ -140,7 +138,6 @@ namespace Content.Server.GameObjects.Components.Interactable
Activated = false;
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner);
}
private void TurnOn(IEntity user)
@@ -153,10 +150,9 @@ namespace Content.Server.GameObjects.Components.Interactable
var cell = Cell;
if (cell == null)
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/button.ogg", Owner);
_notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Cell missing..."));
_notifyManager.PopupMessage(Owner, user, Loc.GetString("Cell missing..."));
return;
}
@@ -166,7 +162,7 @@ namespace Content.Server.GameObjects.Components.Interactable
if (Wattage > cell.CurrentCharge)
{
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Machines/button.ogg", Owner);
_notifyManager.PopupMessage(Owner, user, _localizationManager.GetString("Dead cell..."));
_notifyManager.PopupMessage(Owner, user, Loc.GetString("Dead cell..."));
return;
}
@@ -174,25 +170,46 @@ namespace Content.Server.GameObjects.Components.Interactable
SetState(true);
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/flashlight_toggle.ogg", Owner);
}
private void SetState(bool on)
{
_spriteComponent.LayerSetVisible(1, on);
_pointLight.Enabled = on;
if (_clothingComponent != null)
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
_clothingComponent.ClothingEquippedPrefix = on ? "On" : "Off";
sprite.LayerSetVisible(1, on);
}
if (Owner.TryGetComponent(out PointLightComponent? light))
{
light.Enabled = on;
}
if (Owner.TryGetComponent(out ClothingComponent? clothing))
{
clothing.ClothingEquippedPrefix = on ? "On" : "Off";
}
}
public void OnUpdate(float frameTime)
{
if (!Activated) return;
if (!Activated || Cell == null) return;
var cell = Cell;
if (cell == null || !cell.TryUseCharge(Wattage * frameTime)) TurnOff();
var appearanceComponent = Owner.GetComponent<AppearanceComponent>();
if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.70)
{
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.FullPower);
}
else if (Cell.MaxCharge - Cell.CurrentCharge < Cell.MaxCharge * 0.90)
{
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.LowPower);
}
else
{
appearanceComponent.SetData(HandheldLightVisuals.Power, HandheldLightPowerStates.Dying);
}
if (Cell == null || !Cell.TryUseCharge(Wattage * frameTime)) TurnOff();
Dirty();
}
@@ -211,7 +228,9 @@ namespace Content.Server.GameObjects.Components.Interactable
return;
}
if (!user.TryGetComponent(out HandsComponent hands))
Dirty();
if (!user.TryGetComponent(out HandsComponent? hands))
{
return;
}
@@ -222,24 +241,23 @@ namespace Content.Server.GameObjects.Components.Interactable
}
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Items/pistol_magout.ogg", Owner);
}
public override ComponentState GetComponentState()
{
if (Cell == null)
{
return new HandheldLightComponentState(null);
return new HandheldLightComponentState(null, false);
}
if (Wattage > Cell.CurrentCharge)
{
// Practically zero.
// This is so the item status works correctly.
return new HandheldLightComponentState(0);
return new HandheldLightComponentState(0, HasCell);
}
return new HandheldLightComponentState(Cell.CurrentCharge / Cell.MaxCharge);
return new HandheldLightComponentState(Cell.CurrentCharge / Cell.MaxCharge, HasCell);
}
[Verb]

View File

@@ -14,11 +14,9 @@ namespace Content.Server.GameObjects.Components.Interactable
[RegisterComponent]
public class TilePryingComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager;
[Dependency] private readonly IMapManager _mapManager;
#pragma warning restore 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public override string Name => "TilePrying";
private bool _toolComponentNeeded = true;

View File

@@ -29,10 +29,8 @@ namespace Content.Server.GameObjects.Components.Interactable
[ComponentReference(typeof(IToolComponent))]
public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct, ISolutionChange
{
#pragma warning disable 649
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
#pragma warning restore 649
public override string Name => "Welder";
public override uint? NetID => ContentNetIDs.WELDER;

View File

@@ -20,9 +20,7 @@ namespace Content.Server.GameObjects.Components.Items.Clothing
[ComponentReference(typeof(IItemComponent))]
public class ClothingComponent : ItemComponent, IUse
{
#pragma warning disable 649
[Dependency] private readonly IServerNotifyManager _serverNotifyManager;
#pragma warning restore 649
[Dependency] private readonly IServerNotifyManager _serverNotifyManager = default!;
public override string Name => "Clothing";
public override uint? NetID => ContentNetIDs.CLOTHING;
@@ -63,7 +61,6 @@ namespace Content.Server.GameObjects.Components.Items.Clothing
});
serializer.DataField(ref _quickEquipEnabled, "QuickEquip", true);
serializer.DataFieldCached(ref _heatResistance, "HeatResistance", 323);
}

View File

@@ -20,10 +20,8 @@ namespace Content.Server.GameObjects.Components.Items
[RegisterComponent]
public class DiceComponent : Component, IActivate, IUse, ILand, IExamine
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override string Name => "Dice";
@@ -85,9 +83,9 @@ namespace Content.Server.GameObjects.Components.Items
void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{
//No details check, since the sprite updates to show the side.
var loc = IoCManager.Resolve<ILocalizationManager>();
message.AddMarkup(loc.GetString("A dice with [color=lightgray]{0}[/color] sides.\n" +
"It has landed on a [color=white]{1}[/color].", _sides, _currentSide));
message.AddMarkup(Loc.GetString(
"A dice with [color=lightgray]{0}[/color] sides.\n" + "It has landed on a [color=white]{1}[/color].",
_sides, _currentSide));
}
}
}

View File

@@ -15,16 +15,12 @@ namespace Content.Server.GameObjects.Components.Items
[RegisterComponent]
public class FloorTileItemComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager;
[Dependency] private readonly IMapManager _mapManager;
#pragma warning restore 649
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public override string Name => "FloorTile";
private StackComponent _stack;
private string _outputTile;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
@@ -34,18 +30,20 @@ namespace Content.Server.GameObjects.Components.Items
public override void Initialize()
{
base.Initialize();
_stack = Owner.GetComponent<StackComponent>();
Owner.EnsureComponent<StackComponent>();
}
public void AfterInteract(AfterInteractEventArgs eventArgs)
{
if (!InteractionChecks.InRangeUnobstructed(eventArgs)) return;
if (!Owner.TryGetComponent(out StackComponent stack)) return;
var attacked = eventArgs.Target;
var mapGrid = _mapManager.GetGrid(eventArgs.ClickLocation.GridID);
var tile = mapGrid.GetTileRef(eventArgs.ClickLocation);
var tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.Tile.TypeId];
if (tileDef.IsSubFloor && attacked == null && _stack.Use(1))
if (tileDef.IsSubFloor && attacked == null && stack.Use(1))
{
var desiredTile = _tileDefinitionManager[_outputTile];
mapGrid.SetTile(eventArgs.ClickLocation, new Tile(desiredTile.TileId));

View File

@@ -15,10 +15,8 @@ namespace Content.Server.GameObjects.Components.Items.RCD
[RegisterComponent]
public class RCDAmmoComponent : Component, IAfterInteract, IExamine
{
[Dependency] private IServerNotifyManager _serverNotifyManager = default!;
#pragma warning disable 649
[Dependency] private IServerNotifyManager _serverNotifyManager;
#pragma warning restore 649
public override string Name => "RCDAmmo";
//How much ammo we refill

View File

@@ -25,14 +25,12 @@ namespace Content.Server.GameObjects.Components.Items.RCD
[RegisterComponent]
public class RCDComponent : Component, IAfterInteract, IUse, IExamine
{
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
[Dependency] private readonly IServerNotifyManager _serverNotifyManager = default!;
#pragma warning disable 649
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager;
[Dependency] private readonly IEntitySystemManager _entitySystemManager;
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IServerEntityManager _serverEntityManager;
[Dependency] private IServerNotifyManager _serverNotifyManager;
#pragma warning restore 649
public override string Name => "RCD";
private RcdMode _mode = 0; //What mode are we on? Can be floors, walls, deconstruct.
private readonly RcdMode[] _modes = (RcdMode[]) Enum.GetValues(typeof(RcdMode));

View File

@@ -33,7 +33,7 @@ namespace Content.Server.GameObjects.Components.Items.Storage
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IStorageComponent))]
public class EntityStorageComponent : Component, IActivate, IStorageComponent, IInteractUsing, IDestroyAct, IActionBlocker
public class EntityStorageComponent : Component, IActivate, IStorageComponent, IInteractUsing, IDestroyAct, IActionBlocker, IExAct
{
public override string Name => "EntityStorage";
@@ -430,5 +430,22 @@ namespace Content.Server.GameObjects.Components.Items.Storage
data.Text = component.Open ? "Close" : "Open";
}
void IExAct.OnExplosion(ExplosionEventArgs eventArgs)
{
if (eventArgs.Severity < ExplosionSeverity.Heavy)
{
return;
}
foreach (var entity in Contents.ContainedEntities)
{
var exActs = entity.GetAllComponents<IExAct>().ToArray();
foreach (var exAct in exActs)
{
exAct.OnExplosion(eventArgs);
}
}
}
}
}

View File

@@ -8,11 +8,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill
[RegisterComponent]
internal sealed class MedkitFillComponent : Component, IMapInit
{
public override string Name => "MedkitFill";
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager;
#pragma warning restore 649
public override string Name => "MedkitFill";
void IMapInit.MapInit()
{

View File

@@ -10,14 +10,12 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill
[RegisterComponent]
internal sealed class StorageFillComponent : Component, IMapInit
{
[Dependency] private readonly IEntityManager _entityManager = default!;
public override string Name => "StorageFill";
private List<string> _contents = new List<string>();
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager;
#pragma warning restore 649
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);

View File

@@ -10,11 +10,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill
[RegisterComponent]
internal sealed class ToolboxElectricalFillComponent : Component, IMapInit
{
public override string Name => "ToolboxElectricalFill";
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager;
#pragma warning restore 649
public override string Name => "ToolboxElectricalFill";
void IMapInit.MapInit()
{

View File

@@ -10,11 +10,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill
[RegisterComponent]
internal sealed class ToolboxEmergencyFillComponent : Component, IMapInit
{
public override string Name => "ToolboxEmergencyFill";
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager;
#pragma warning restore 649
public override string Name => "ToolboxEmergencyFill";
void IMapInit.MapInit()
{

View File

@@ -10,11 +10,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill
[RegisterComponent]
internal sealed class ToolboxGoldFillComponent : Component, IMapInit
{
public override string Name => "ToolboxGoldFill";
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager;
#pragma warning restore 649
public override string Name => "ToolboxGoldFill";
void IMapInit.MapInit()
{

View File

@@ -8,11 +8,9 @@ namespace Content.Server.GameObjects.Components.Items.Storage.Fill
[RegisterComponent]
internal sealed class UtilityBeltClothingFillComponent : Component, IMapInit
{
public override string Name => "UtilityBeltClothingFill";
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager;
#pragma warning restore 649
public override string Name => "UtilityBeltClothingFill";
void IMapInit.MapInit()
{

View File

@@ -24,13 +24,11 @@ namespace Content.Server.GameObjects.Components.Items.Storage
[ComponentReference(typeof(IItemComponent))]
public class ItemComponent : StorableComponent, IInteractHand, IExAct, IEquipped, IUnequipped, IItemComponent
{
[Dependency] private readonly IMapManager _mapManager = default!;
public override string Name => "Item";
public override uint? NetID => ContentNetIDs.ITEM;
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager;
#pragma warning restore 649
private string _equippedPrefix;
public string EquippedPrefix

View File

@@ -37,10 +37,8 @@ namespace Content.Server.GameObjects.Components.Items.Storage
public class ServerStorageComponent : SharedStorageComponent, IInteractUsing, IUse, IActivate, IStorageComponent, IDestroyAct, IExAct,
IDragDrop
{
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning restore 649
private const string LoggerName = "Storage";

View File

@@ -16,10 +16,8 @@ namespace Content.Server.GameObjects.Components.Items
[RegisterComponent]
public class ToysComponent : Component, IActivate, IUse, ILand
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public override string Name => "Toys";

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -14,6 +15,7 @@ using Content.Server.GameObjects.EntitySystems;
using Content.Server.Interfaces;
using Content.Server.Interfaces.Chat;
using Content.Server.Interfaces.GameObjects;
using Content.Server.Utility;
using Content.Shared.Chemistry;
using Content.Shared.GameObjects.Components.Power;
using Content.Shared.Interfaces;
@@ -39,25 +41,19 @@ namespace Content.Server.GameObjects.Components.Kitchen
[ComponentReference(typeof(IActivate))]
public class MicrowaveComponent : SharedMicrowaveComponent, IActivate, IInteractUsing, ISolutionChange, ISuicideAct
{
#pragma warning disable 649
[Dependency] private readonly IEntityManager _entityManager;
[Dependency] private readonly RecipeManager _recipeManager;
[Dependency] private readonly IServerNotifyManager _notifyManager;
#pragma warning restore 649
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly RecipeManager _recipeManager = default!;
[Dependency] private readonly IServerNotifyManager _notifyManager = default!;
#region YAMLSERIALIZE
private int _cookTimeDefault;
private int _cookTimeMultiplier; //For upgrades and stuff I guess?
private string _badRecipeName;
private string _startCookingSound;
private string _cookingCompleteSound;
private string _badRecipeName = "";
private string _startCookingSound = "";
private string _cookingCompleteSound = "";
#endregion
#region VIEWVARIABLES
[ViewVariables]
private SolutionComponent _solution;
[ViewVariables]
[ViewVariables]
private bool _busy = false;
/// <summary>
@@ -67,20 +63,18 @@ namespace Content.Server.GameObjects.Components.Kitchen
/// </summary>
[ViewVariables]
private uint _currentCookTimerTime = 1;
#endregion
private bool _powered => _powerReceiver.Powered;
private bool _hasContents => _solution.ReagentList.Count > 0 || _storage.ContainedEntities.Count > 0;
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
private bool _hasContents => Owner.TryGetComponent(out SolutionComponent? solution) && (solution.ReagentList.Count > 0 || _storage.ContainedEntities.Count > 0);
private bool _uiDirty = true;
private bool _lostPower = false;
private int _currentCookTimeButtonIndex = 0;
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => _uiDirty = true;
private AudioSystem _audioSystem;
private AppearanceComponent _appearance;
private PowerReceiverComponent _powerReceiver;
private BoundUserInterface _userInterface;
private Container _storage;
private AudioSystem _audioSystem = default!;
private Container _storage = default!;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(MicrowaveUiKey.Key);
public override void ExposeData(ObjectSerializer serializer)
{
@@ -95,22 +89,21 @@ namespace Content.Server.GameObjects.Components.Kitchen
public override void Initialize()
{
base.Initialize();
_solution ??= Owner.TryGetComponent(out SolutionComponent solutionComponent)
? solutionComponent
: Owner.AddComponent<SolutionComponent>();
Owner.EnsureComponent<SolutionComponent>();
_storage = ContainerManagerComponent.Ensure<Container>("microwave_entity_container", Owner, out var existed);
_appearance = Owner.GetComponent<AppearanceComponent>();
_powerReceiver = Owner.GetComponent<PowerReceiverComponent>();
_audioSystem = EntitySystem.Get<AudioSystem>();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(MicrowaveUiKey.Key);
_userInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += UserInterfaceOnReceiveMessage;
}
}
private void UserInterfaceOnReceiveMessage(ServerBoundUserInterfaceMessage message)
{
if (!_powered || _busy)
if (!Powered || _busy)
{
return;
}
@@ -158,13 +151,13 @@ namespace Content.Server.GameObjects.Components.Kitchen
public void OnUpdate()
{
if (!_powered)
if (!Powered)
{
//TODO:If someone cuts power currently, microwave magically keeps going. FIX IT!
SetAppearance(MicrowaveVisualState.Idle);
}
if (_busy && !_powered)
if (_busy && !Powered)
{
//we lost power while we were cooking/busy!
_lostPower = true;
@@ -174,11 +167,11 @@ namespace Content.Server.GameObjects.Components.Kitchen
_uiDirty = true;
}
if (_uiDirty)
if (_uiDirty && Owner.TryGetComponent(out SolutionComponent? solution))
{
_userInterface.SetState(new MicrowaveUpdateUserInterfaceState
UserInterface?.SetState(new MicrowaveUpdateUserInterfaceState
(
_solution.Solution.Contents.ToArray(),
solution.Solution.Contents.ToArray(),
_storage.ContainedEntities.Select(item => item.Uid).ToArray(),
_busy,
_currentCookTimeButtonIndex,
@@ -190,26 +183,26 @@ namespace Content.Server.GameObjects.Components.Kitchen
private void SetAppearance(MicrowaveVisualState state)
{
if (_appearance != null || Owner.TryGetComponent(out _appearance))
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
_appearance.SetData(PowerDeviceVisuals.VisualState, state);
appearance.SetData(PowerDeviceVisuals.VisualState, state);
}
}
void IActivate.Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor) || !_powered)
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor) || !Powered)
{
return;
}
_uiDirty = true;
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
public async Task<bool> InteractUsing(InteractUsingEventArgs eventArgs)
{
if (!_powered)
if (!Powered)
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
Loc.GetString("It has no power!"));
@@ -232,8 +225,13 @@ namespace Content.Server.GameObjects.Components.Kitchen
return false;
}
if (!Owner.TryGetComponent(out SolutionComponent? solution))
{
return false;
}
//Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = ReagentUnit.Min(attackPourable.TransferAmount, _solution.EmptyVolume);
var realTransferAmount = ReagentUnit.Min(attackPourable.TransferAmount, solution.EmptyVolume);
if (realTransferAmount <= 0) //Special message if container is full
{
_notifyManager.PopupMessage(Owner.Transform.GridPosition, eventArgs.User,
@@ -243,7 +241,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
//Move units from attackSolution to targetSolution
var removedSolution = attackSolution.SplitSolution(realTransferAmount);
if (!_solution.TryAddSolution(removedSolution))
if (!solution.TryAddSolution(removedSolution))
{
return false;
}
@@ -280,6 +278,11 @@ namespace Content.Server.GameObjects.Components.Kitchen
var solidsDict = new Dictionary<string, int>();
foreach(var item in _storage.ContainedEntities)
{
if (item.Prototype == null)
{
continue;
}
if(solidsDict.ContainsKey(item.Prototype.ID))
{
solidsDict[item.Prototype.ID]++;
@@ -303,7 +306,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
}
// Check recipes
FoodRecipePrototype recipeToCook = null;
FoodRecipePrototype? recipeToCook = null;
foreach (var r in _recipeManager.Recipes.Where(r => CanSatisfyRecipe(r, solidsDict) == MicrowaveSuccessState.RecipePass))
{
recipeToCook = r;
@@ -330,7 +333,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
{
if (goodMeal)
{
SubtractContents(recipeToCook);
SubtractContents(recipeToCook!);
}
else
{
@@ -357,12 +360,18 @@ namespace Content.Server.GameObjects.Components.Kitchen
private void VaporizeReagents()
{
_solution.RemoveAllSolution();
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution.RemoveAllSolution();
}
}
private void VaporizeReagentQuantity(Solution.ReagentQuantity reagentQuantity)
{
_solution.TryRemoveReagent(reagentQuantity.ReagentId, reagentQuantity.Quantity);
if (Owner.TryGetComponent(out SolutionComponent? solution))
{
solution?.TryRemoveReagent(reagentQuantity.ReagentId, reagentQuantity.Quantity);
}
}
private void VaporizeSolids()
@@ -395,9 +404,14 @@ namespace Content.Server.GameObjects.Components.Kitchen
private void SubtractContents(FoodRecipePrototype recipe)
{
if (!Owner.TryGetComponent(out SolutionComponent? solution))
{
return;
}
foreach(var recipeReagent in recipe.IngredientsReagents)
{
_solution.TryRemoveReagent(recipeReagent.Key, ReagentUnit.New(recipeReagent.Value));
solution?.TryRemoveReagent(recipeReagent.Key, ReagentUnit.New(recipeReagent.Value));
}
foreach (var recipeSolid in recipe.IngredientsSolids)
@@ -406,6 +420,11 @@ namespace Content.Server.GameObjects.Components.Kitchen
{
foreach (var item in _storage.ContainedEntities)
{
if (item.Prototype == null)
{
continue;
}
if (item.Prototype.ID == recipeSolid.Key)
{
_storage.Remove(item);
@@ -420,9 +439,14 @@ namespace Content.Server.GameObjects.Components.Kitchen
private MicrowaveSuccessState CanSatisfyRecipe(FoodRecipePrototype recipe, Dictionary<string,int> solids)
{
if (!Owner.TryGetComponent(out SolutionComponent? solution))
{
return MicrowaveSuccessState.RecipeFail;
}
foreach (var reagent in recipe.IngredientsReagents)
{
if (!_solution.ContainsReagent(reagent.Key, out var amount))
if (!solution.ContainsReagent(reagent.Key, out var amount))
{
return MicrowaveSuccessState.RecipeFail;
}

View File

@@ -1,4 +1,6 @@
using Content.Server.GameObjects.Components.Mobs;
#nullable enable
using Content.Server.GameObjects.Components.Mobs;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -8,6 +10,7 @@ using Robust.Server.Interfaces.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components
{
@@ -15,19 +18,36 @@ namespace Content.Server.GameObjects.Components
[ComponentReference(typeof(IActivate))]
public class MagicMirrorComponent : SharedMagicMirrorComponent, IActivate
{
private BoundUserInterface _userInterface;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(MagicMirrorUiKey.Key);
public override void Initialize()
{
base.Initialize();
_userInterface = Owner.GetComponent<ServerUserInterfaceComponent>()
.GetBoundUserInterface(MagicMirrorUiKey.Key);
_userInterface.OnReceiveMessage += OnUiReceiveMessage;
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
}
public override void OnRemove()
{
if (UserInterface != null)
{
UserInterface.OnReceiveMessage -= OnUiReceiveMessage;
}
base.OnRemove();
}
private static void OnUiReceiveMessage(ServerBoundUserInterfaceMessage obj)
{
if (!obj.Session.AttachedEntity.TryGetComponent(out HumanoidAppearanceComponent looks))
if (obj.Session.AttachedEntity == null)
{
return;
}
if (!obj.Session.AttachedEntity.TryGetComponent(out HumanoidAppearanceComponent? looks))
{
return;
}
@@ -70,23 +90,23 @@ namespace Content.Server.GameObjects.Components
public void Activate(ActivateEventArgs eventArgs)
{
if (!eventArgs.User.TryGetComponent(out IActorComponent actor))
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
if (!eventArgs.User.TryGetComponent(out HumanoidAppearanceComponent looks))
if (!eventArgs.User.TryGetComponent(out HumanoidAppearanceComponent? looks))
{
Owner.PopupMessage(eventArgs.User, Loc.GetString("You can't have any hair!"));
return;
}
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
var msg = new MagicMirrorInitialDataMessage(looks.Appearance.HairColor, looks.Appearance.FacialHairColor, looks.Appearance.HairStyleName,
looks.Appearance.FacialHairStyleName);
_userInterface.SendMessage(msg, actor.playerSession);
UserInterface?.SendMessage(msg, actor.playerSession);
}
}
}

View File

@@ -18,14 +18,12 @@ namespace Content.Server.GameObjects.Components.Markers
[RegisterComponent]
public class ConditionalSpawnerComponent : Component, IMapInit
{
public override string Name => "ConditionalSpawner";
[Dependency] private readonly IGameTicker _gameTicker = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
#pragma warning disable 649
[Dependency] private IGameTicker _gameTicker;
[Dependency] private IReflectionManager _reflectionManager;
[Dependency] private IEntityManager _entityManager;
[Dependency] private IRobustRandom _robustRandom;
#pragma warning restore 649
public override string Name => "ConditionalSpawner";
[ViewVariables(VVAccess.ReadWrite)]
public List<string> Prototypes { get; set; } = new List<string>();

View File

@@ -12,9 +12,7 @@ namespace Content.Server.GameObjects.Components.Markers
[ComponentReference(typeof(SharedSpawnPointComponent))]
public sealed class SpawnPointComponent : SharedSpawnPointComponent
{
#pragma warning disable 649
[Dependency] private IPrototypeManager _prototypeManager;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[ViewVariables(VVAccess.ReadWrite)]
private SpawnPointType _spawnType;

View File

@@ -15,10 +15,8 @@ namespace Content.Server.GameObjects.Components.Markers
[RegisterComponent]
public class TimedSpawnerComponent : Component
{
#pragma warning disable 649
[Dependency] private IEntityManager _entityManager;
[Dependency] private IRobustRandom _robustRandom;
#pragma warning restore 649
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
public override string Name => "TimedSpawner";

View File

@@ -14,12 +14,10 @@ namespace Content.Server.GameObjects.Components.Markers
[RegisterComponent]
public class TrashSpawnerComponent : ConditionalSpawnerComponent
{
public override string Name => "TrashSpawner";
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
#pragma warning disable 649
[Dependency] private IEntityManager _entityManager;
[Dependency] private IRobustRandom _robustRandom;
#pragma warning restore 649
public override string Name => "TrashSpawner";
[ViewVariables(VVAccess.ReadWrite)]
public List<string> RarePrototypes { get; set; } = new List<string>();

View File

@@ -0,0 +1,71 @@
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Stack;
using Content.Shared.Damage;
using Content.Shared.GameObjects.Components.Body;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Serialization;
namespace Content.Server.GameObjects.Components.Medical
{
[RegisterComponent]
public class HealingComponent : Component, IAfterInteract
{
public override string Name => "Healing";
public Dictionary<DamageType, int> Heal { get; private set; }
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(this, h => h.Heal, "heal", new Dictionary<DamageType, int>());
}
public void AfterInteract(AfterInteractEventArgs eventArgs)
{
if (eventArgs.Target == null)
{
return;
}
if (!eventArgs.Target.TryGetComponent(out IBodyManagerComponent body))
{
return;
}
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
{
return;
}
if (eventArgs.User != eventArgs.Target)
{
var interactionSystem = EntitySystem.Get<SharedInteractionSystem>();
var from = eventArgs.User.Transform.MapPosition;
var to = eventArgs.Target.Transform.MapPosition;
bool Ignored(IEntity entity) => entity == eventArgs.User || entity == eventArgs.Target;
var inRange = interactionSystem.InRangeUnobstructed(from, to, predicate: Ignored);
if (!inRange)
{
return;
}
}
if (Owner.TryGetComponent(out StackComponent stack) &&
!stack.Use(1))
{
return;
}
foreach (var (type, amount) in Heal)
{
body.ChangeDamage(type, -amount, true);
}
}
}
}

View File

@@ -1,7 +1,9 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using Content.Server.GameObjects.Components.Power.ApcNetComponents;
using Content.Server.GameObjects.EntitySystems;
using Content.Server.Utility;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Medical;
using Content.Shared.GameObjects.EntitySystems;
@@ -16,6 +18,7 @@ using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Maths;
using Content.Shared.Damage;
using Robust.Shared.Localization;
using Robust.Shared.ViewVariables;
namespace Content.Server.GameObjects.Components.Medical
{
@@ -23,29 +26,29 @@ namespace Content.Server.GameObjects.Components.Medical
[ComponentReference(typeof(IActivate))]
public class MedicalScannerComponent : SharedMedicalScannerComponent, IActivate
{
private AppearanceComponent _appearance;
private BoundUserInterface _userInterface;
private ContainerSlot _bodyContainer;
private ContainerSlot _bodyContainer = default!;
private readonly Vector2 _ejectOffset = new Vector2(-0.5f, 0f);
public bool IsOccupied => _bodyContainer.ContainedEntity != null;
private PowerReceiverComponent _powerReceiver;
private bool Powered => _powerReceiver.Powered;
[ViewVariables]
private bool Powered => !Owner.TryGetComponent(out PowerReceiverComponent? receiver) || receiver.Powered;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(MedicalScannerUiKey.Key);
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>();
if (UserInterface != null)
{
UserInterface.OnReceiveMessage += OnUiReceiveMessage;
}
//TODO: write this so that it checks for a change in power events and acts accordingly.
_bodyContainer = ContainerManagerComponent.Ensure<ContainerSlot>($"{Name}-bodyContainer", Owner);
// TODO: write this so that it checks for a change in power events and acts accordingly.
var newState = GetUserInterfaceState();
_userInterface.SetState(newState);
UserInterface?.SetState(newState);
UpdateUserInterface();
}
@@ -62,11 +65,15 @@ namespace Content.Server.GameObjects.Components.Medical
var body = _bodyContainer.ContainedEntity;
if (body == null)
{
_appearance.SetData(MedicalScannerVisuals.Status, MedicalScannerStatus.Open);
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance?.SetData(MedicalScannerVisuals.Status, MedicalScannerStatus.Open);
};
return EmptyUIState;
}
if (!body.TryGetComponent(out IDamageableComponent damageable) ||
if (!body.TryGetComponent(out IDamageableComponent? damageable) ||
damageable.CurrentDamageState == DamageState.Dead)
{
return EmptyUIState;
@@ -86,7 +93,7 @@ namespace Content.Server.GameObjects.Components.Medical
}
var newState = GetUserInterfaceState();
_userInterface.SetState(newState);
UserInterface?.SetState(newState);
}
private MedicalScannerStatus GetStatusFromDamageState(DamageState damageState)
@@ -115,12 +122,15 @@ namespace Content.Server.GameObjects.Components.Medical
private void UpdateAppearance()
{
_appearance.SetData(MedicalScannerVisuals.Status, GetStatus());
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
appearance.SetData(MedicalScannerVisuals.Status, GetStatus());
}
}
public void Activate(ActivateEventArgs args)
{
if (!args.User.TryGetComponent(out IActorComponent actor))
if (!args.User.TryGetComponent(out IActorComponent? actor))
{
return;
}
@@ -128,7 +138,7 @@ namespace Content.Server.GameObjects.Components.Medical
if (!Powered)
return;
_userInterface.Open(actor.playerSession);
UserInterface?.Open(actor.playerSession);
}
[Verb]

View File

@@ -20,9 +20,7 @@ namespace Content.Server.GameObjects.Components.Metabolism
[RegisterComponent]
public class MetabolismComponent : Component
{
#pragma warning disable 649
[Dependency] private readonly IPrototypeManager _prototypeManager;
#pragma warning restore 649
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override string Name => "Metabolism";

View File

@@ -17,17 +17,16 @@ namespace Content.Server.GameObjects.Components.Mining
[RegisterComponent]
public class AsteroidRockComponent : Component, IInteractUsing
{
[Dependency] private readonly IRobustRandom _random = default!;
public override string Name => "AsteroidRock";
private static readonly string[] SpriteStates = {"0", "1", "2", "3", "4"};
#pragma warning disable 649
[Dependency] private readonly IRobustRandom _random;
#pragma warning restore 649
public override void Initialize()
{
base.Initialize();
var spriteComponent = Owner.GetComponent<SpriteComponent>();
var spriteComponent = Owner.EnsureComponent<SpriteComponent>();
spriteComponent.LayerSetState(0, _random.Pick(SpriteStates));
}

View File

@@ -5,6 +5,7 @@ using Content.Server.Mobs;
using Content.Shared.GameObjects.Components.Damage;
using Content.Shared.GameObjects.Components.Mobs;
using Content.Shared.GameObjects.EntitySystems;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.Interfaces.GameObjects;
@@ -163,6 +164,11 @@ namespace Content.Server.GameObjects.Components.Mobs
{
public void EnterState(IEntity entity)
{
if (entity.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(DamageStateVisuals.State, DamageState.Alive);
}
UpdateState(entity);
}
@@ -187,7 +193,12 @@ namespace Content.Server.GameObjects.Components.Mobs
{
case RuinableComponent ruinable:
{
var modifier = (int) (ruinable.TotalDamage / (ruinable.MaxHp / 7f));
if (ruinable.DeadThreshold == null)
{
break;
}
var modifier = (int) (ruinable.TotalDamage / (ruinable.DeadThreshold / 7f));
status.ChangeStatusEffectIcon(StatusEffect.Health,
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
@@ -196,8 +207,12 @@ namespace Content.Server.GameObjects.Components.Mobs
}
case BodyManagerComponent body:
{
// TODO: Declare body max normal damage (currently 100)
var modifier = (int) (body.TotalDamage / (100f / 7f));
if (body.CriticalThreshold == null)
{
return;
}
var modifier = (int) (body.TotalDamage / (body.CriticalThreshold / 7f));
status.ChangeStatusEffectIcon(StatusEffect.Health,
"/Textures/Interface/StatusEffects/Human/human" + modifier + ".png");
@@ -281,6 +296,11 @@ namespace Content.Server.GameObjects.Components.Mobs
{
public void EnterState(IEntity entity)
{
if (entity.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(DamageStateVisuals.State, DamageState.Critical);
}
if (entity.TryGetComponent(out ServerStatusEffectsComponent status))
{
status.ChangeStatusEffectIcon(StatusEffect.Health,
@@ -382,6 +402,11 @@ namespace Content.Server.GameObjects.Components.Mobs
{
public void EnterState(IEntity entity)
{
if (entity.TryGetComponent(out AppearanceComponent appearance))
{
appearance.SetData(DamageStateVisuals.State, DamageState.Dead);
}
if (entity.TryGetComponent(out ServerStatusEffectsComponent status))
{
status.ChangeStatusEffectIcon(StatusEffect.Health,

View File

@@ -0,0 +1,41 @@
using Content.Server.Interfaces.Chat;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Content.Server.GameObjects.Components.Mobs.Speech
{
public interface IAccentManager
{
public void Initialize();
}
public class AccentManager : IAccentManager
{
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IComponentManager _componentManager = default!;
public static readonly Regex SentenceRegex = new Regex(@"(?<=[\.!\?])");
public void Initialize()
{
IoCManager.InjectDependencies(this);
_chatManager.RegisterChatTransform(AccentHandler);
}
public string AccentHandler(IEntity player, string message)
{
//TODO: give accents a prio?
var accents = _componentManager.GetComponents<IAccentComponent>(player.Uid);
foreach (var accent in accents)
{
message = accent.Accentuate(message);
}
return message;
}
}
}

View File

@@ -0,0 +1,18 @@
using Robust.Shared.GameObjects;
using System;
namespace Content.Server.GameObjects.Components.Mobs.Speech
{
[RegisterComponent]
public class BackwardsAccentComponent : Component, IAccentComponent
{
public override string Name => "BackwardsAccent";
public string Accentuate(string message)
{
var arr = message.ToCharArray();
Array.Reverse(arr);
return new string(arr);
}
}
}

View File

@@ -0,0 +1,36 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.Random;
using Robust.Shared.IoC;
using Robust.Shared.Random;
using System.Collections.Generic;
namespace Content.Server.GameObjects.Components.Mobs.Speech
{
[RegisterComponent]
public class OwOAccentComponent : Component, IAccentComponent
{
public override string Name => "OwOAccent";
private static readonly IReadOnlyList<string> Faces = new List<string>{
" (・`ω´・)", " ;;w;;", " owo", " UwU", " >w<", " ^w^"
}.AsReadOnly();
private string RandomFace => IoCManager.Resolve<IRobustRandom>().Pick(Faces);
private static readonly Dictionary<string, string> SpecialWords = new Dictionary<string, string>
{
{ "you", "wu" },
};
public string Accentuate(string message)
{
foreach ((var word,var repl) in SpecialWords)
{
message = message.Replace(word, repl);
}
return message.Replace("!", RandomFace)
.Replace("r", "w").Replace("R", "W")
.Replace("l", "w").Replace("L", "W");
}
}
}

View File

@@ -0,0 +1,64 @@
using Microsoft.CodeAnalysis.Operations;
using Microsoft.EntityFrameworkCore.Internal;
using Robust.Shared.GameObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace Content.Server.GameObjects.Components.Mobs.Speech
{
[RegisterComponent]
public class SpanishAccentComponent : Component, IAccentComponent
{
public override string Name => "SpanishAccent";
public string Accentuate(string message)
{
// Insert E before every S
message = InsertS(message);
// If a sentence ends with ?, insert a reverse ? at the beginning of the sentence
message = ReplaceQuestionMark(message);
return message;
}
private string InsertS(string message)
{
// Replace every new Word that starts with s/S
var msg = message.Replace(" s", " es").Replace(" S", "Es");
// Still need to check if the beginning of the message starts
if (msg.StartsWith("s"))
{
return msg.Remove(0, 1).Insert(0, "es");
}
else if (msg.StartsWith("S"))
{
return msg.Remove(0, 1).Insert(0, "Es");
}
return msg;
}
private string ReplaceQuestionMark(string message)
{
var sentences = AccentManager.SentenceRegex.Split(message);
var msg = "";
foreach (var s in sentences)
{
if (s.EndsWith("?")) // We've got a question => add ¿ to the beginning
{
// Because we don't split by whitespace, we may have some spaces in front of the sentence.
// So we add the symbol before the first non space char
msg += s.Insert(s.Length - s.TrimStart().Length, "¿");
}
else
{
msg += s;
}
}
return msg;
}
}
}

View File

@@ -0,0 +1,94 @@
using Robust.Server.Interfaces.Console;
using Robust.Server.Interfaces.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.IoC;
using System;
using System.Linq;
namespace Content.Server.GameObjects.Components.Mobs.Speech
{
internal interface IAccentComponent
{
/// <summary>
/// Transforms a message with the given Accent
/// </summary>
/// <param name="message">The spoken message</param>
/// <returns>The message after the transformation</returns>
public string Accentuate(string message);
}
public class AddAccent : IClientCommand
{
public string Command => "addaccent";
public string Description => "Add a speech component to the current player";
public string Help => $"{Command} <component>/?";
public void Execute(IConsoleShell shell, IPlayerSession player, string[] args)
{
if (args.Length == 0)
{
shell.SendText(player, Help);
return;
}
if (player.AttachedEntity == null)
{
shell.SendText(player, "You don't have a player!");
return;
}
var compFactory = IoCManager.Resolve<IComponentFactory>();
if (args[0] == "?")
{
// Get all components that implement the ISpeechComponent except
var speeches = compFactory.GetAllRefTypes()
.Where(c => typeof(IAccentComponent).IsAssignableFrom(c) && c.IsClass);
var msg = "";
foreach(var s in speeches)
{
msg += $"{compFactory.GetRegistration(s).Name}\n";
}
shell.SendText(player, msg);
}
else
{
var name = args[0];
// Try to get the Component
Type type;
try
{
var comp = compFactory.GetComponent(name);
type = comp.GetType();
}
catch (Exception)
{
shell.SendText(player, $"Accent {name} not found. Try {Command} ? to get a list of all appliable accents.");
return;
}
// Check if that already exists
try
{
var comp = player.AttachedEntity.GetComponent(type);
shell.SendText(player, "You already have this accent!");
return;
}
catch (Exception)
{
// Accent not found
}
// Generic fuckery
var ensure = typeof(IEntity).GetMethod("AddComponent");
if (ensure == null)
return;
var method = ensure.MakeGenericMethod(type);
method.Invoke(player.AttachedEntity, null);
}
}
}
}

View File

@@ -12,9 +12,7 @@ namespace Content.Server.GameObjects.Components.Mobs
[ComponentReference(typeof(SharedStunnableComponent))]
public class StunnableComponent : SharedStunnableComponent
{
#pragma warning disable 649
[Dependency] private IGameTiming _gameTiming;
#pragma warning restore 649
[Dependency] private readonly IGameTiming _gameTiming = default!;
protected override void OnKnockdown()
{

View File

@@ -0,0 +1,20 @@
using Content.Server.Mobs;
using Robust.Shared.GameObjects;
namespace Content.Server.GameObjects.Components.Mobs
{
[RegisterComponent]
public sealed class VisitingMindComponent : Component
{
public override string Name => "VisitingMind";
public Mind Mind { get; set; }
public override void OnRemove()
{
base.OnRemove();
Mind?.UnVisit();
}
}
}

View File

@@ -1,11 +1,16 @@
using Content.Server.GameObjects.EntitySystems.AI;
#nullable enable
using Content.Server.GameObjects.EntitySystems.AI;
using Content.Server.Interfaces.GameTicking;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.Roles;
using Robust.Server.AI;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -14,23 +19,26 @@ namespace Content.Server.GameObjects.Components.Movement
[RegisterComponent, ComponentReference(typeof(IMoverComponent))]
public class AiControllerComponent : Component, IMoverComponent
{
private string _logicName;
private string? _logicName;
private float _visionRadius;
public override string Name => "AiController";
[ViewVariables(VVAccess.ReadWrite)]
public string LogicName
public string? LogicName
{
get => _logicName;
set
{
_logicName = value;
Processor = null;
Processor = null!;
}
}
public AiLogicProcessor Processor { get; set; }
public AiLogicProcessor? Processor { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public string? StartingGearPrototype { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public float VisionRadius
@@ -45,18 +53,34 @@ namespace Content.Server.GameObjects.Components.Movement
base.Initialize();
// This component requires a collidable component.
if (!Owner.HasComponent<ICollidableComponent>())
Owner.AddComponent<CollidableComponent>();
Owner.EnsureComponent<CollidableComponent>();
EntitySystem.Get<AiSystem>().ProcessorInitialize(this);
}
protected override void Startup()
{
base.Startup();
if (StartingGearPrototype != null)
{
var startingGear = IoCManager.Resolve<IPrototypeManager>().Index<StartingGearPrototype>(StartingGearPrototype);
IoCManager.Resolve<IGameTicker>().EquipStartingGear(Owner, startingGear);
}
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _logicName, "logic", null);
serializer.DataReadWriteFunction(
"startingGear",
null,
startingGear => StartingGearPrototype = startingGear,
() => StartingGearPrototype);
serializer.DataField(ref _visionRadius, "vision", 8.0f);
}
@@ -74,7 +98,7 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? component))
{
return component.CurrentWalkSpeed;
}
@@ -91,7 +115,7 @@ namespace Content.Server.GameObjects.Components.Movement
{
get
{
if (Owner.TryGetComponent(out MovementSpeedModifierComponent component))
if (Owner.TryGetComponent(out MovementSpeedModifierComponent? component))
{
return component.CurrentSprintSpeed;
}

View File

@@ -1,21 +1,21 @@

using Content.Server.GameObjects.Components.Body;
using Content.Server.GameObjects.EntitySystems.DoAfter;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.GameObjects.EntitySystems;
using Content.Shared.GameObjects.Verbs;
using Content.Shared.Interfaces;
using Content.Shared.Interfaces.GameObjects.Components;
using Robust.Server.Interfaces.Player;
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.Log;
using Robust.Shared.Maths;
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
@@ -24,10 +24,7 @@ namespace Content.Server.GameObjects.Components.Movement
[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.
@@ -41,14 +38,17 @@ namespace Content.Server.GameObjects.Components.Movement
[ViewVariables]
private float _climbDelay;
private ICollidableComponent _collidableComponent;
private DoAfterSystem _doAfterSystem;
public override void Initialize()
{
base.Initialize();
_collidableComponent = Owner.GetComponent<ICollidableComponent>();
if (!Owner.EnsureComponent(out CollidableComponent _))
{
Logger.Warning($"Entity {Owner.Name} at {Owner.Transform.MapPosition} didn't have a {nameof(CollidableComponent)}");
}
_doAfterSystem = EntitySystem.Get<DoAfterSystem>();
}
@@ -62,68 +62,101 @@ namespace Content.Server.GameObjects.Components.Movement
bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs)
{
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
{
_notifyManager.PopupMessage(eventArgs.User, eventArgs.User, Loc.GetString("You can't do that!"));
string reason;
bool canVault;
if (eventArgs.User == eventArgs.Dropped)
canVault = CanVault(eventArgs.User, eventArgs.Target, out reason);
else
canVault = CanVault(eventArgs.User, eventArgs.Dropped, eventArgs.Target, out reason);
if (!canVault)
eventArgs.User.PopupMessage(eventArgs.User, reason);
return canVault;
}
/// <summary>
/// Checks if the user can vault the target
/// </summary>
/// <param name="user">The entity that wants to vault</param>
/// <param name="target">The object that is being vaulted</param>
/// <param name="reason">The reason why it cant be dropped</param>
/// <returns></returns>
private bool CanVault(IEntity user, IEntity target, out string reason)
{
if (!ActionBlockerSystem.CanInteract(user))
{
reason = Loc.GetString("You can't do that!");
return false;
}
if (eventArgs.User == eventArgs.Dropped) // user is dragging themselves onto a climbable
if (!user.HasComponent<ClimbingComponent>())
{
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;
}
reason = Loc.GetString("You are incapable of climbing!");
return false;
}
var bodyManager = user.GetComponent<BodyManagerComponent>();
if (bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Leg).Count == 0 ||
bodyManager.GetBodyPartsOfType(Shared.GameObjects.Components.Body.BodyPartType.Foot).Count == 0)
{
reason = Loc.GetString("You are unable to climb!");
return false;
}
var userPosition = user.Transform.MapPosition;
var climbablePosition = target.Transform.MapPosition;
var interaction = EntitySystem.Get<SharedInteractionSystem>();
bool Ignored(IEntity entity) => (entity == target || entity == user);
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored))
{
reason = Loc.GetString("You can't reach there!");
return false;
}
reason = string.Empty;
return true;
}
/// <summary>
/// Checks if the user can vault the dragged entity onto the the target
/// </summary>
/// <param name="user">The user that wants to vault the entity</param>
/// <param name="dragged">The entity that is being vaulted</param>
/// <param name="target">The object that is being vaulted onto</param>
/// <param name="reason">The reason why it cant be dropped</param>
/// <returns></returns>
private bool CanVault(IEntity user, IEntity dragged, IEntity target, out string reason)
{
if (!ActionBlockerSystem.CanInteract(user))
{
reason = Loc.GetString("You can't do that!");
return false;
}
if (target == null || !dragged.HasComponent<ClimbingComponent>())
{
reason = Loc.GetString("You can't do that!");
return false;
}
var userPosition = user.Transform.MapPosition;
var otherUserPosition = dragged.Transform.MapPosition;
var climbablePosition = target.Transform.MapPosition;
var interaction = EntitySystem.Get<SharedInteractionSystem>();
bool Ignored(IEntity entity) => (entity == target || entity == user || entity == dragged);
if (!interaction.InRangeUnobstructed(userPosition, climbablePosition, _range, predicate: Ignored) ||
!interaction.InRangeUnobstructed(userPosition, otherUserPosition, _range, predicate: Ignored))
{
reason = Loc.GetString("You can't reach there!");
return false;
}
reason = string.Empty;
return true;
}
@@ -175,7 +208,7 @@ namespace Content.Server.GameObjects.Components.Movement
// 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:theName} forces {1:theName} onto {2:theName}!", user, entityToMove, Owner), 15);
_notifyManager.PopupMessage(user, user, Loc.GetString("You force {0:theName} onto {1:theName}!", entityToMove, Owner));
user.PopupMessage(user, Loc.GetString("You force {0:theName} onto {1:theName}!", entityToMove, Owner));
}
}
@@ -211,7 +244,7 @@ namespace Content.Server.GameObjects.Components.Movement
climbMode.TryMoveTo(user.Transform.WorldPosition, endPoint);
PopupMessageOtherClientsInRange(user, Loc.GetString("{0:theName} jumps onto {1:theName}!", user, Owner), 15);
_notifyManager.PopupMessage(user, user, Loc.GetString("You jump onto {0:theName}!", Owner));
user.PopupMessage(user, Loc.GetString("You jump onto {0:theName}!", Owner));
}
}
@@ -231,5 +264,27 @@ namespace Content.Server.GameObjects.Components.Movement
source.PopupMessage(viewer.AttachedEntity, message);
}
}
/// <summary>
/// Allows you to vault an object with the ClimbableComponent through right click
/// </summary>
[Verb]
private sealed class ClimbVerb : Verb<ClimbableComponent>
{
protected override void GetData(IEntity user, ClimbableComponent component, VerbData data)
{
if (!component.CanVault(user, component.Owner, out var _))
{
data.Visibility = VerbVisibility.Invisible;
}
data.Text = Loc.GetString("Vault");
}
protected override void Activate(IEntity user, ClimbableComponent component)
{
component.TryClimb(user);
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Collections.Generic;
using Content.Shared.GameObjects.Components.Movement;
using Robust.Server.GameObjects;
@@ -18,9 +19,7 @@ namespace Content.Server.GameObjects.Components.Movement
[RegisterComponent]
public class ServerPortalComponent : SharedPortalComponent
{
#pragma warning disable 649
[Dependency] private readonly IServerEntityManager _serverEntityManager;
#pragma warning restore 649
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
// Potential improvements: Different sounds,
// Add Gateways
@@ -28,15 +27,14 @@ namespace Content.Server.GameObjects.Components.Movement
// Put portal above most other things layer-wise
// Add telefragging (get entities on connecting portal and force brute damage)
private AppearanceComponent _appearanceComponent;
private IEntity _connectingTeleporter;
private IEntity? _connectingTeleporter;
private PortalState _state = PortalState.Pending;
[ViewVariables(VVAccess.ReadWrite)] private float _individualPortalCooldown;
[ViewVariables] private float _overallPortalCooldown;
[ViewVariables] private bool _onCooldown;
[ViewVariables] private string _departureSound;
[ViewVariables] private string _arrivalSound;
public List<IEntity> immuneEntities = new List<IEntity>(); // K
[ViewVariables] private string _departureSound = "";
[ViewVariables] private string _arrivalSound = "";
public readonly List<IEntity> ImmuneEntities = new List<IEntity>(); // K
[ViewVariables(VVAccess.ReadWrite)] private float _aliveTime;
public override void ExposeData(ObjectSerializer serializer)
@@ -52,12 +50,6 @@ namespace Content.Server.GameObjects.Components.Movement
serializer.DataField(ref _arrivalSound, "arrival_sound", "/Audio/Effects/teleport_arrival.ogg");
}
public override void Initialize()
{
base.Initialize();
_appearanceComponent = Owner.GetComponent<AppearanceComponent>();
}
public override void OnAdd()
{
// This will blow up an entity it's attached to
@@ -74,13 +66,6 @@ namespace Content.Server.GameObjects.Components.Movement
}
}
public override void OnRemove()
{
_appearanceComponent = null;
base.OnRemove();
}
public bool CanBeConnected()
{
if (_connectingTeleporter == null)
@@ -108,23 +93,24 @@ namespace Content.Server.GameObjects.Components.Movement
}
_state = targetState;
if (_appearanceComponent != null)
if (Owner.TryGetComponent(out AppearanceComponent? appearance))
{
_appearanceComponent.SetData(PortalVisuals.State, _state);
appearance.SetData(PortalVisuals.State, _state);
}
}
private void releaseCooldown(IEntity entity)
private void ReleaseCooldown(IEntity entity)
{
if (immuneEntities.Contains(entity))
if (ImmuneEntities.Contains(entity))
{
immuneEntities.Remove(entity);
ImmuneEntities.Remove(entity);
}
if (_connectingTeleporter != null &&
_connectingTeleporter.TryGetComponent<ServerPortalComponent>(out var otherPortal))
{
otherPortal.immuneEntities.Remove(entity);
otherPortal.ImmuneEntities.Remove(entity);
}
}
@@ -142,7 +128,7 @@ namespace Content.Server.GameObjects.Components.Movement
private bool IsEntityPortable(IEntity entity)
{
// TODO: Check if it's slotted etc. Otherwise the slot item itself gets ported.
if (!immuneEntities.Contains(entity) && entity.HasComponent<TeleportableComponent>())
if (!ImmuneEntities.Contains(entity) && entity.HasComponent<TeleportableComponent>())
{
return true;
}
@@ -192,7 +178,7 @@ namespace Content.Server.GameObjects.Components.Movement
public void TryPortalEntity(IEntity entity)
{
if (immuneEntities.Contains(entity) || _connectingTeleporter == null)
if (ImmuneEntities.Contains(entity) || _connectingTeleporter == null)
{
return;
}
@@ -208,9 +194,9 @@ namespace Content.Server.GameObjects.Components.Movement
soundPlayer.PlayAtCoords(_arrivalSound, entity.Transform.GridPosition);
TryChangeState(PortalState.RecentlyTeleported);
// To stop spam teleporting. Could potentially look at adding a timer to flush this from the portal
immuneEntities.Add(entity);
_connectingTeleporter.GetComponent<ServerPortalComponent>().immuneEntities.Add(entity);
Timer.Spawn(TimeSpan.FromSeconds(_individualPortalCooldown), () => releaseCooldown(entity));
ImmuneEntities.Add(entity);
_connectingTeleporter.GetComponent<ServerPortalComponent>().ImmuneEntities.Add(entity);
Timer.Spawn(TimeSpan.FromSeconds(_individualPortalCooldown), () => ReleaseCooldown(entity));
StartCooldown();
}
}

View File

@@ -1,4 +1,5 @@
using System;
#nullable enable
using System;
using System.Linq;
using Content.Shared.GameObjects.Components.Movement;
using Content.Shared.Interfaces.GameObjects.Components;
@@ -24,11 +25,10 @@ namespace Content.Server.GameObjects.Components.Movement
[RegisterComponent]
public class ServerTeleporterComponent : Component, IAfterInteract
{
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager;
[Dependency] private readonly IServerEntityManager _serverEntityManager;
[Dependency] private readonly IRobustRandom _spreadRandom;
#pragma warning restore 649
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
[Dependency] private readonly IRobustRandom _spreadRandom = default!;
// TODO: Look at MapManager.Map for Beacons to get all entities on grid
public ItemTeleporterState State => _state;
@@ -39,15 +39,13 @@ namespace Content.Server.GameObjects.Components.Movement
[ViewVariables] private int _range;
[ViewVariables] private ItemTeleporterState _state;
[ViewVariables] private TeleporterType _teleporterType;
[ViewVariables] private string _departureSound;
[ViewVariables] private string _arrivalSound;
[ViewVariables] private string _cooldownSound;
[ViewVariables] private string _departureSound = "";
[ViewVariables] private string _arrivalSound = "";
[ViewVariables] private string? _cooldownSound;
// If the direct OR random teleport will try to avoid hitting collidables
[ViewVariables] private bool _avoidCollidable;
[ViewVariables] private float _portalAliveTime;
private AppearanceComponent _appearanceComponent;
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
@@ -63,22 +61,20 @@ namespace Content.Server.GameObjects.Components.Movement
serializer.DataField(ref _portalAliveTime, "portal_alive_time", 5.0f); // TODO: Change this to 0 before PR?
}
public override void OnRemove()
{
_appearanceComponent = null;
base.OnRemove();
}
private void SetState(ItemTeleporterState newState)
{
if (!Owner.TryGetComponent(out AppearanceComponent? appearance))
{
return;
}
if (newState == ItemTeleporterState.Cooldown)
{
_appearanceComponent.SetData(TeleporterVisuals.VisualState, TeleporterVisualState.Charging);
appearance.SetData(TeleporterVisuals.VisualState, TeleporterVisualState.Charging);
}
else
{
_appearanceComponent.SetData(TeleporterVisuals.VisualState, TeleporterVisualState.Ready);
appearance.SetData(TeleporterVisuals.VisualState, TeleporterVisualState.Ready);
}
_state = newState;
}
@@ -149,12 +145,11 @@ namespace Content.Server.GameObjects.Components.Movement
public override void Initialize()
{
_appearanceComponent = Owner.GetComponent<AppearanceComponent>();
_state = ItemTeleporterState.Off;
base.Initialize();
_state = ItemTeleporterState.Off;
}
private bool emptySpace(IEntity user, Vector2 target)
private bool EmptySpace(IEntity user, Vector2 target)
{
// TODO: Check the user's spot? Upside is no stacking TPs but downside is they can't unstuck themselves from walls.
foreach (var entity in _serverEntityManager.GetEntitiesIntersecting(user.Transform.MapID, target))
@@ -167,7 +162,7 @@ namespace Content.Server.GameObjects.Components.Movement
return true;
}
private Vector2 randomEmptySpot(IEntity user, int range)
private Vector2 RandomEmptySpot(IEntity user, int range)
{
Vector2 targetVector = user.Transform.GridPosition.Position;
// Definitely a better way to do this
@@ -176,7 +171,7 @@ namespace Content.Server.GameObjects.Components.Movement
var randomRange = _spreadRandom.Next(0, range);
var angle = Angle.FromDegrees(_spreadRandom.Next(0, 359));
targetVector = user.Transform.GridPosition.Position + angle.ToVec() * randomRange;
if (emptySpace(user, targetVector))
if (EmptySpace(user, targetVector))
{
return targetVector;
}
@@ -200,7 +195,7 @@ namespace Content.Server.GameObjects.Components.Movement
Vector2 targetVector;
if (_avoidCollidable)
{
targetVector = randomEmptySpot(user, _range);
targetVector = RandomEmptySpot(user, _range);
}
else
{
@@ -227,7 +222,7 @@ namespace Content.Server.GameObjects.Components.Movement
public void Teleport(IEntity user, Vector2 vector)
{
// Messy maybe?
GridCoordinates targetGrid = new GridCoordinates(vector, user.Transform.GridID);
var targetGrid = new GridCoordinates(vector, user.Transform.GridID);
var soundPlayer = EntitySystem.Get<AudioSystem>();
// If portals use those, otherwise just move em over
@@ -240,10 +235,11 @@ namespace Content.Server.GameObjects.Components.Movement
// Arrival portal
var arrivalPortal = _serverEntityManager.SpawnEntity("Portal", targetGrid);
arrivalPortal.TryGetComponent<ServerPortalComponent>(out var arrivalComponent);
// Connect. TODO: If the OnUpdate in ServerPortalComponent is changed this may need to change as well.
arrivalComponent.TryConnectPortal(departurePortal);
if (arrivalPortal.TryGetComponent<ServerPortalComponent>(out var arrivalComponent))
{
// Connect. TODO: If the OnUpdate in ServerPortalComponent is changed this may need to change as well.
arrivalComponent.TryConnectPortal(departurePortal);
}
}
else
{

View File

@@ -22,10 +22,8 @@ namespace Content.Server.GameObjects.Components.Movement
[ComponentReference(typeof(IMoverComponent))]
internal class ShuttleControllerComponent : Component, IMoverComponent
{
#pragma warning disable 649
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
#pragma warning restore 649
private bool _movingUp;
private bool _movingDown;

Some files were not shown because too many files have changed in this diff Show More