Merge remote-tracking branch 'space-wizards/master'
# Conflicts: # Content.Server/Access/Systems/AgentIDCardSystem.cs # Content.Server/GameTicking/Rules/ChangelingRuleSystem.cs # Content.Server/GameTicking/Rules/ThiefRuleSystem.cs # Content.Server/Medical/SuitSensors/SuitSensorSystem.cs # Content.Server/Zombies/ZombieSystem.cs # Content.Shared/Clothing/EntitySystems/SharedChameleonClothingSystem.cs # Content.Shared/Standing/StandingStateComponent.cs # Content.Shared/Standing/StandingStateSystem.cs # Content.Shared/Stunnable/SharedStunSystem.Knockdown.cs # Content.Shared/Stunnable/SharedStunSystem.cs # Resources/Locale/en-US/_strings/game-ticking/game-presets/preset-changeling.ftl # Resources/Locale/en-US/_strings/ghost/observer-role.ftl # Resources/Locale/en-US/_strings/robotics/borg_modules.ftl # Resources/Prototypes/Entities/Clothing/Ears/specific.yml # Resources/Prototypes/Entities/Clothing/Eyes/specific.yml # Resources/Prototypes/Entities/Clothing/Hands/specific.yml # Resources/Prototypes/Entities/Clothing/Head/specific.yml # Resources/Prototypes/Entities/Clothing/Neck/specific.yml # Resources/Prototypes/Entities/Clothing/OuterClothing/specific.yml # Resources/Prototypes/Entities/Clothing/Shoes/specific.yml # Resources/Prototypes/Entities/Clothing/Uniforms/specific.yml # Resources/Prototypes/Entities/Mobs/NPCs/animals.yml # Resources/Prototypes/Entities/Objects/Materials/Sheets/glass.yml # Resources/Prototypes/Entities/Objects/Materials/Sheets/metal.yml # Resources/Prototypes/Entities/Objects/Materials/Sheets/other.yml # Resources/Prototypes/Entities/Objects/Materials/parts.yml # Resources/Prototypes/Entities/Objects/Specific/Robotics/borg_modules.yml
This commit is contained in:
commit
3cf56cd5b3
217 changed files with 3422 additions and 2454 deletions
|
|
@ -21,7 +21,7 @@ namespace Content.Client.Actions.UI
|
|||
/// </summary>
|
||||
public (TimeSpan Start, TimeSpan End)? Cooldown { get; set; }
|
||||
|
||||
public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null, FormattedMessage? charges = null)
|
||||
public ActionAlertTooltip(FormattedMessage name, FormattedMessage? desc, string? requires = null)
|
||||
{
|
||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
||||
|
||||
|
|
@ -52,17 +52,6 @@ namespace Content.Client.Actions.UI
|
|||
vbox.AddChild(description);
|
||||
}
|
||||
|
||||
if (charges != null && !string.IsNullOrWhiteSpace(charges.ToString()))
|
||||
{
|
||||
var chargesLabel = new RichTextLabel
|
||||
{
|
||||
MaxWidth = TooltipTextMaxWidth,
|
||||
StyleClasses = { StyleNano.StyleClassTooltipActionCharges }
|
||||
};
|
||||
chargesLabel.SetMessage(charges);
|
||||
vbox.AddChild(chargesLabel);
|
||||
}
|
||||
|
||||
vbox.AddChild(_cooldownLabel = new RichTextLabel
|
||||
{
|
||||
MaxWidth = TooltipTextMaxWidth,
|
||||
|
|
|
|||
|
|
@ -1,36 +1,46 @@
|
|||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Light.Visualizers;
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
public sealed class LightBulbSystem : VisualizerSystem<LightBulbComponent>
|
||||
public sealed class LightBulbSystem : SharedLightBulbSystem
|
||||
{
|
||||
protected override void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args)
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<LightBulbComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, LightBulbComponent comp, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
// update sprite state
|
||||
if (AppearanceSystem.TryGetData<LightBulbState>(uid, LightBulbVisuals.State, out var state, args.Component))
|
||||
if (_appearance.TryGetData<LightBulbState>(uid, LightBulbVisuals.State, out var state, args.Component))
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case LightBulbState.Normal:
|
||||
SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState);
|
||||
_sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.NormalSpriteState);
|
||||
break;
|
||||
case LightBulbState.Broken:
|
||||
SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState);
|
||||
_sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BrokenSpriteState);
|
||||
break;
|
||||
case LightBulbState.Burned:
|
||||
SpriteSystem.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState);
|
||||
_sprite.LayerSetRsiState((uid, args.Sprite), LightBulbVisualLayers.Base, comp.BurnedSpriteState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// also update sprites color
|
||||
if (AppearanceSystem.TryGetData<Color>(uid, LightBulbVisuals.Color, out var color, args.Component))
|
||||
if (_appearance.TryGetData<Color>(uid, LightBulbVisuals.Color, out var color, args.Component))
|
||||
{
|
||||
SpriteSystem.SetColor((uid, args.Sprite), color);
|
||||
_sprite.SetColor((uid, args.Sprite), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
Content.Client/Light/EntitySystems/PoweredLightSystem.cs
Normal file
5
Content.Client/Light/EntitySystems/PoweredLightSystem.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
using Content.Shared.Light.EntitySystems;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
public sealed class PoweredLightSystem : SharedPoweredLightSystem;
|
||||
5
Content.Client/Medical/SuitSensors/SuitSensorSystem.cs
Normal file
5
Content.Client/Medical/SuitSensors/SuitSensorSystem.cs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
using Content.Shared.Medical.SuitSensors;
|
||||
|
||||
namespace Content.Client.Medical.SuitSensors;
|
||||
|
||||
public sealed class SuitSensorSystem : SharedSuitSensorSystem;
|
||||
|
|
@ -5,4 +5,5 @@ namespace Content.Client.Power.Components;
|
|||
[RegisterComponent]
|
||||
public sealed partial class ApcPowerReceiverComponent : SharedApcPowerReceiverComponent
|
||||
{
|
||||
public override float Load { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
|
|||
|
||||
public EntityUid Entity;
|
||||
|
||||
private bool _allowBorgControl = true;
|
||||
|
||||
public RoboticsConsoleWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
|
@ -72,6 +74,7 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
|
|||
public void UpdateState(RoboticsConsoleState state)
|
||||
{
|
||||
_cyborgs = state.Cyborgs;
|
||||
_allowBorgControl = state.AllowBorgControl;
|
||||
|
||||
// clear invalid selection
|
||||
if (_selected is {} selected && !_cyborgs.ContainsKey(selected))
|
||||
|
|
@ -85,8 +88,8 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
|
|||
PopulateData();
|
||||
|
||||
var locked = _lock.IsLocked(Entity);
|
||||
DangerZone.Visible = !locked;
|
||||
LockedMessage.Visible = locked;
|
||||
DangerZone.Visible = !locked && _allowBorgControl;
|
||||
LockedMessage.Visible = locked && _allowBorgControl; // Only show if locked AND control is allowed
|
||||
}
|
||||
|
||||
private void PopulateCyborgs()
|
||||
|
|
@ -120,11 +123,19 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
|
|||
BorgSprite.Texture = _sprite.Frame0(data.ChassisSprite!);
|
||||
|
||||
var batteryColor = data.Charge switch {
|
||||
< 0.2f => "red",
|
||||
< 0.4f => "orange",
|
||||
< 0.6f => "yellow",
|
||||
< 0.8f => "green",
|
||||
_ => "blue"
|
||||
< 0.2f => "#FF6C7F", // red
|
||||
< 0.4f => "#EF973C", // orange
|
||||
< 0.6f => "#E8CB2D", // yellow
|
||||
< 0.8f => "#30CC19", // green
|
||||
_ => "#00D3B8" // cyan
|
||||
};
|
||||
|
||||
var hpPercentColor = data.HpPercent switch {
|
||||
< 0.2f => "#FF6C7F", // red
|
||||
< 0.4f => "#EF973C", // orange
|
||||
< 0.6f => "#E8CB2D", // yellow
|
||||
< 0.8f => "#30CC19", // green
|
||||
_ => "#00D3B8" // cyan
|
||||
};
|
||||
|
||||
var text = new FormattedMessage();
|
||||
|
|
@ -132,12 +143,14 @@ public sealed partial class RoboticsConsoleWindow : FancyWindow
|
|||
text.AddMarkupOrThrow(Loc.GetString("robotics-console-designation"));
|
||||
text.AddText($" {data.Name}\n"); // prevent players trolling by naming borg [color=red]satan[/color]
|
||||
text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-battery", ("charge", (int)(data.Charge * 100f)), ("color", batteryColor))}\n");
|
||||
text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-hp", ("hp", (int)(data.HpPercent * 100f)), ("color", hpPercentColor))}\n");
|
||||
text.AddMarkupOrThrow($"{Loc.GetString("robotics-console-brain", ("brain", data.HasBrain))}\n");
|
||||
text.AddMarkupOrThrow(Loc.GetString("robotics-console-modules", ("count", data.ModuleCount)));
|
||||
BorgInfo.SetMessage(text);
|
||||
|
||||
// how the turntables
|
||||
DisableButton.Disabled = !(data.HasBrain && data.CanDisable);
|
||||
DisableButton.Disabled = !_allowBorgControl || !(data.HasBrain && data.CanDisable);
|
||||
DestroyButton.Disabled = !_allowBorgControl;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
|||
private readonly HashSet<DockingPortState> _drawnDocks = new();
|
||||
private readonly Dictionary<DockingPortState, Button> _dockButtons = new();
|
||||
|
||||
private readonly Color _fallbackHighlightedColor = Color.Magenta;
|
||||
|
||||
/// <summary>
|
||||
/// Store buttons for every other dock
|
||||
/// </summary>
|
||||
|
|
@ -213,11 +215,11 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
|||
|
||||
if (HighlightedDock == dock.Entity)
|
||||
{
|
||||
otherDockColor = Color.ToSrgb(Color.Magenta);
|
||||
otherDockColor = Color.ToSrgb(dock.HighlightedColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
otherDockColor = Color.ToSrgb(Color.Purple);
|
||||
otherDockColor = Color.ToSrgb(dock.Color);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -311,7 +313,7 @@ public sealed partial class ShuttleDockControl : BaseShuttleControl
|
|||
ScalePosition(Vector2.Transform(new Vector2(-0.5f, 0.5f), rotation)),
|
||||
ScalePosition(Vector2.Transform(new Vector2(0.5f, -0.5f), rotation)));
|
||||
|
||||
var dockColor = Color.Magenta;
|
||||
var dockColor = _viewedState?.HighlightedColor ?? _fallbackHighlightedColor;
|
||||
var connectionColor = Color.Pink;
|
||||
|
||||
handle.DrawRect(ourDockConnection, connectionColor.WithAlpha(0.2f));
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
-dockRadius * UIScale,
|
||||
(Size.X + dockRadius) * UIScale,
|
||||
(Size.Y + dockRadius) * UIScale);
|
||||
|
||||
|
||||
if (_docks.TryGetValue(nent, out var docks))
|
||||
{
|
||||
foreach (var state in docks)
|
||||
|
|
@ -321,7 +321,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
|||
continue;
|
||||
}
|
||||
|
||||
var color = Color.ToSrgb(Color.Magenta);
|
||||
var color = Color.ToSrgb(state.HighlightedColor);
|
||||
|
||||
var verts = new[]
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,22 +28,8 @@ namespace Content.Client.Stack
|
|||
|
||||
base.SetCount(uid, amount, component);
|
||||
|
||||
if (component.Lingering &&
|
||||
TryComp<SpriteComponent>(uid, out var sprite))
|
||||
{
|
||||
// tint the stack gray and make it transparent if it's lingering.
|
||||
var color = component.Count == 0 && component.Lingering
|
||||
? Color.DarkGray.WithAlpha(0.65f)
|
||||
: Color.White;
|
||||
|
||||
for (var i = 0; i < sprite.AllLayers.Count(); i++)
|
||||
{
|
||||
_sprite.LayerSetColor((uid, sprite), i, color);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO PREDICT ENTITY DELETION: This should really just be a normal entity deletion call.
|
||||
if (component.Count <= 0 && !component.Lingering)
|
||||
if (component.Count <= 0)
|
||||
{
|
||||
Xform.DetachEntity(uid, Transform(uid));
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using System.Numerics;
|
||||
using Content.Client.Cooldown;
|
||||
using Content.Client.UserInterface.Systems.Inventory.Controls;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.UserInterface.Controls
|
||||
{
|
||||
|
|
@ -20,6 +22,7 @@ namespace Content.Client.UserInterface.Controls
|
|||
public CooldownGraphic CooldownDisplay { get; }
|
||||
|
||||
private SpriteView SpriteView { get; }
|
||||
private EntityPrototypeView ProtoView { get; }
|
||||
|
||||
public EntityUid? Entity => SpriteView.Entity;
|
||||
|
||||
|
|
@ -141,6 +144,13 @@ namespace Content.Client.UserInterface.Controls
|
|||
SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize),
|
||||
OverrideDirection = Direction.South
|
||||
});
|
||||
AddChild(ProtoView = new EntityPrototypeView
|
||||
{
|
||||
Visible = false,
|
||||
Scale = new Vector2(2, 2),
|
||||
SetSize = new Vector2(DefaultButtonSize, DefaultButtonSize),
|
||||
OverrideDirection = Direction.South
|
||||
});
|
||||
|
||||
AddChild(HoverSpriteView = new SpriteView
|
||||
{
|
||||
|
|
@ -209,12 +219,35 @@ namespace Content.Client.UserInterface.Controls
|
|||
HoverSpriteView.SetEntity(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes the control to display a placeholder prototype, optionally faded
|
||||
/// </summary>
|
||||
public void SetEntity(EntityUid? ent)
|
||||
{
|
||||
SpriteView.SetEntity(ent);
|
||||
SpriteView.Visible = true;
|
||||
ProtoView.Visible = false;
|
||||
UpdateButtonTexture();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes the control to display a placeholder prototype, optionally faded
|
||||
/// </summary>
|
||||
public void SetPrototype(EntProtoId? proto, bool fade)
|
||||
{
|
||||
ProtoView.SetPrototype(proto);
|
||||
SpriteView.Visible = false;
|
||||
ProtoView.Visible = true;
|
||||
|
||||
UpdateButtonTexture();
|
||||
|
||||
if (ProtoView.Entity is not { } ent || !fade)
|
||||
return;
|
||||
|
||||
var sprites = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
||||
sprites.SetColor((ent.Owner, ent.Comp1), Color.DarkGray.WithAlpha(0.65f));
|
||||
}
|
||||
|
||||
private void UpdateButtonTexture()
|
||||
{
|
||||
var fullTexture = Theme.ResolveTextureOrNull(_fullButtonTexturePath);
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ using Content.Client.Actions;
|
|||
using Content.Client.Actions.UI;
|
||||
using Content.Client.Cooldown;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Actions.Components;
|
||||
using Content.Shared.Charges.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
|
|
@ -23,9 +23,9 @@ namespace Content.Client.UserInterface.Systems.Actions.Controls;
|
|||
public sealed class ActionButton : Control, IEntityControl
|
||||
{
|
||||
private IEntityManager _entities;
|
||||
private IPlayerManager _player;
|
||||
private SpriteSystem? _spriteSys;
|
||||
private ActionUIController? _controller;
|
||||
private SharedChargesSystem _sharedChargesSys;
|
||||
private bool _beingHovered;
|
||||
private bool _depressed;
|
||||
private bool _toggled;
|
||||
|
|
@ -67,8 +67,8 @@ public sealed class ActionButton : Control, IEntityControl
|
|||
// TODO why is this constructor so slooooow. The rest of the code is fine
|
||||
|
||||
_entities = entities;
|
||||
_player = IoCManager.Resolve<IPlayerManager>();
|
||||
_spriteSys = spriteSys;
|
||||
_sharedChargesSys = _entities.System<SharedChargesSystem>();
|
||||
_controller = controller;
|
||||
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
|
@ -197,23 +197,17 @@ public sealed class ActionButton : Control, IEntityControl
|
|||
return null;
|
||||
|
||||
var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
|
||||
var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription));
|
||||
FormattedMessage? chargesText = null;
|
||||
var desc = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription));
|
||||
|
||||
// TODO: Don't touch this use an event make callers able to add their own shit for actions or I kill you.
|
||||
if (_entities.TryGetComponent(Action, out LimitedChargesComponent? actionCharges))
|
||||
{
|
||||
var charges = _sharedChargesSys.GetCurrentCharges((Action.Value, actionCharges, null));
|
||||
chargesText = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {charges.ToString()}/{actionCharges.MaxCharges}"));
|
||||
if (_player.LocalEntity is null)
|
||||
return null;
|
||||
|
||||
if (_entities.TryGetComponent(Action, out AutoRechargeComponent? autoRecharge))
|
||||
{
|
||||
var chargeTimeRemaining = _sharedChargesSys.GetNextRechargeTime((Action.Value, actionCharges, autoRecharge));
|
||||
chargesText.AddText(Loc.GetString($"{Environment.NewLine}Time Til Recharge: {chargeTimeRemaining}"));
|
||||
}
|
||||
}
|
||||
var ev = new ExaminedEvent(desc, Action.Value, _player.LocalEntity.Value, true, !desc.IsEmpty);
|
||||
_entities.EventBus.RaiseLocalEvent(Action.Value.Owner, ev);
|
||||
|
||||
return new ActionAlertTooltip(name, decr, charges: chargesText);
|
||||
var newDesc = ev.GetTotalMessage();
|
||||
|
||||
return new ActionAlertTooltip(name, newDesc);
|
||||
}
|
||||
|
||||
protected override void ControlFocusExited()
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using Robust.Client.Player;
|
|||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
|
|
@ -73,7 +74,8 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
{
|
||||
if (entity.Owner != _player.LocalEntity)
|
||||
return;
|
||||
AddHand(name, location);
|
||||
if (_handsSystem.TryGetHand((entity.Owner, entity.Comp), name, out var hand))
|
||||
AddHand(name, hand.Value);
|
||||
}
|
||||
|
||||
private void OnRemoveHand(Entity<HandsComponent> entity, string name)
|
||||
|
|
@ -139,7 +141,7 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
_playerHandsComponent = handsComp;
|
||||
foreach (var (name, hand) in handsComp.Comp.Hands)
|
||||
{
|
||||
var handButton = AddHand(name, hand.Location);
|
||||
var handButton = AddHand(name, hand);
|
||||
|
||||
if (_handsSystem.TryGetHeldItem(handsComp.AsNullable(), name, out var held) &&
|
||||
_entities.TryGetComponent(held, out VirtualItemComponent? virt))
|
||||
|
|
@ -147,11 +149,25 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
handButton.SetEntity(virt.BlockingEntity);
|
||||
handButton.Blocked = true;
|
||||
}
|
||||
else
|
||||
else if (held != null)
|
||||
{
|
||||
handButton.SetEntity(held);
|
||||
handButton.Blocked = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hand.EmptyRepresentative is { } representative)
|
||||
{
|
||||
// placeholder, view it
|
||||
SetRepresentative(handButton, representative);
|
||||
}
|
||||
else
|
||||
{
|
||||
// otherwise empty
|
||||
handButton.SetEntity(null);
|
||||
}
|
||||
handButton.Blocked = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (handsComp.Comp.ActiveHandId == null)
|
||||
|
|
@ -159,6 +175,11 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
SetActiveHand(handsComp.Comp.ActiveHandId);
|
||||
}
|
||||
|
||||
private void SetRepresentative(HandButton handButton, EntProtoId prototype)
|
||||
{
|
||||
handButton.SetPrototype(prototype, true);
|
||||
}
|
||||
|
||||
private void HandBlocked(string handName)
|
||||
{
|
||||
if (!_handLookup.TryGetValue(handName, out var hand))
|
||||
|
|
@ -203,7 +224,12 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
hand.Blocked = false;
|
||||
}
|
||||
|
||||
UpdateHandStatus(hand, entity);
|
||||
if (_playerHandsComponent != null &&
|
||||
_player.LocalSession?.AttachedEntity is { } playerEntity &&
|
||||
_handsSystem.TryGetHand((playerEntity, _playerHandsComponent), name, out var handData))
|
||||
{
|
||||
UpdateHandStatus(hand, entity, handData);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnItemRemoved(string name, EntityUid entity)
|
||||
|
|
@ -212,8 +238,19 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
if (hand == null)
|
||||
return;
|
||||
|
||||
if (_playerHandsComponent != null &&
|
||||
_player.LocalSession?.AttachedEntity is { } playerEntity &&
|
||||
_handsSystem.TryGetHand((playerEntity, _playerHandsComponent), name, out var handData))
|
||||
{
|
||||
UpdateHandStatus(hand, null, handData);
|
||||
if (handData?.EmptyRepresentative is { } representative)
|
||||
{
|
||||
SetRepresentative(hand, representative);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
hand.SetEntity(null);
|
||||
UpdateHandStatus(hand, null);
|
||||
}
|
||||
|
||||
private HandsContainer GetFirstAvailableContainer()
|
||||
|
|
@ -276,13 +313,13 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
if (foldedLocation == HandUILocation.Left)
|
||||
{
|
||||
_statusHandLeft = handControl;
|
||||
HandsGui.UpdatePanelEntityLeft(heldEnt);
|
||||
HandsGui.UpdatePanelEntityLeft(heldEnt, hand.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Middle or right
|
||||
_statusHandRight = handControl;
|
||||
HandsGui.UpdatePanelEntityRight(heldEnt);
|
||||
HandsGui.UpdatePanelEntityRight(heldEnt, hand.Value);
|
||||
}
|
||||
|
||||
HandsGui.SetHighlightHand(foldedLocation);
|
||||
|
|
@ -295,9 +332,9 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
return handControl;
|
||||
}
|
||||
|
||||
private HandButton AddHand(string handName, HandLocation location)
|
||||
private HandButton AddHand(string handName, Hand hand)
|
||||
{
|
||||
var button = new HandButton(handName, location);
|
||||
var button = new HandButton(handName, hand.Location);
|
||||
button.StoragePressed += StorageActivate;
|
||||
button.Pressed += HandPressed;
|
||||
var uiLocation = location.GetUILocation(); // 🌟Starlight🌟
|
||||
|
|
@ -319,10 +356,16 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
GetFirstAvailableContainer().AddButton(button);
|
||||
}
|
||||
|
||||
if (hand.EmptyRepresentative is { } representative)
|
||||
{
|
||||
SetRepresentative(button, representative);
|
||||
}
|
||||
UpdateHandStatus(button, null, hand);
|
||||
|
||||
// If we don't have a status for this hand type yet, set it.
|
||||
// This means we have status filled by default in most scenarios,
|
||||
// otherwise the user'd need to switch hands to "activate" the hands the first time.
|
||||
if (location.GetUILocation() == HandUILocation.Left)
|
||||
if (hand.Location.GetUILocation() == HandUILocation.Left)
|
||||
_statusHandLeft ??= button;
|
||||
else
|
||||
_statusHandRight ??= button;
|
||||
|
|
@ -486,12 +529,12 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
|||
}
|
||||
}
|
||||
|
||||
private void UpdateHandStatus(HandButton hand, EntityUid? entity)
|
||||
private void UpdateHandStatus(HandButton hand, EntityUid? entity, Hand? handData)
|
||||
{
|
||||
if (hand == _statusHandLeft)
|
||||
HandsGui?.UpdatePanelEntityLeft(entity);
|
||||
HandsGui?.UpdatePanelEntityLeft(entity, handData);
|
||||
|
||||
if (hand == _statusHandRight)
|
||||
HandsGui?.UpdatePanelEntityRight(entity);
|
||||
HandsGui?.UpdatePanelEntityRight(entity, handData);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ public sealed partial class HotbarGui : UIWidget
|
|||
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
|
||||
}
|
||||
|
||||
public void UpdatePanelEntityLeft(EntityUid? entity)
|
||||
public void UpdatePanelEntityLeft(EntityUid? entity, Hand? hand)
|
||||
{
|
||||
StatusPanelLeft.Update(entity);
|
||||
StatusPanelLeft.Update(entity, hand);
|
||||
}
|
||||
|
||||
public void UpdatePanelEntityRight(EntityUid? entity)
|
||||
public void UpdatePanelEntityRight(EntityUid? entity, Hand? hand)
|
||||
{
|
||||
StatusPanelRight.Update(entity);
|
||||
StatusPanelRight.Update(entity, hand);
|
||||
}
|
||||
|
||||
public void SetHighlightHand(HandUILocation? hand)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public sealed partial class ItemStatusPanel : Control
|
|||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
[ViewVariables] private EntityUid? _entity;
|
||||
[ViewVariables] private Hand? _hand;
|
||||
|
||||
// Tracked so we can re-run SetSide() if the theme changes.
|
||||
private HandUILocation _side;
|
||||
|
|
@ -101,29 +102,45 @@ public sealed partial class ItemStatusPanel : Control
|
|||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
UpdateItemName();
|
||||
UpdateItemName(_hand);
|
||||
}
|
||||
|
||||
public void Update(EntityUid? entity)
|
||||
public void Update(EntityUid? entity, Hand? hand)
|
||||
{
|
||||
ItemNameLabel.Visible = entity != null;
|
||||
NoItemLabel.Visible = entity == null;
|
||||
if (entity == _entity && hand == _hand)
|
||||
return;
|
||||
|
||||
_hand = hand;
|
||||
if (entity == null)
|
||||
{
|
||||
ItemNameLabel.Text = "";
|
||||
ClearOldStatus();
|
||||
_entity = null;
|
||||
|
||||
if (hand?.EmptyLabel is { } label)
|
||||
{
|
||||
ItemNameLabel.Visible = true;
|
||||
NoItemLabel.Visible = false;
|
||||
|
||||
ItemNameLabel.Text = Loc.GetString(label);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemNameLabel.Visible = false;
|
||||
NoItemLabel.Visible = true;
|
||||
|
||||
ItemNameLabel.Text = "";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity != _entity)
|
||||
{
|
||||
_entity = entity.Value;
|
||||
BuildNewEntityStatus();
|
||||
ItemNameLabel.Visible = true;
|
||||
NoItemLabel.Visible = false;
|
||||
|
||||
UpdateItemName();
|
||||
}
|
||||
_entity = entity.Value;
|
||||
BuildNewEntityStatus();
|
||||
|
||||
UpdateItemName(hand);
|
||||
}
|
||||
|
||||
public void UpdateHighlight(bool highlight)
|
||||
|
|
@ -131,14 +148,14 @@ public sealed partial class ItemStatusPanel : Control
|
|||
HighlightPanel.Visible = highlight;
|
||||
}
|
||||
|
||||
private void UpdateItemName()
|
||||
private void UpdateItemName(Hand? hand)
|
||||
{
|
||||
if (_entity == null)
|
||||
return;
|
||||
|
||||
if (!_entityManager.TryGetComponent<MetaDataComponent>(_entity, out var meta) || meta.Deleted)
|
||||
{
|
||||
Update(null);
|
||||
Update(null, hand);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@
|
|||
// https://github.com/dotnet/runtime/issues/107197
|
||||
// So we can't really parallelize integration tests harder either until the runtime fixes that,
|
||||
// *or* we fix serv3 to not spam expression trees.
|
||||
[assembly: LevelOfParallelism(3)]
|
||||
[assembly: LevelOfParallelism(2)]
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ using Content.Server.Mind;
|
|||
using Content.Server.Roles;
|
||||
using Content.Server.RoundEnd;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
|
@ -22,6 +21,7 @@ using Content.Shared.NPC.Prototypes;
|
|||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.NukeOps;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Station.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Linq;
|
|||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
|
|
@ -11,7 +10,7 @@ using Content.Shared.Mind;
|
|||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using Content.Shared._Sunrise.Biocode;
|
|||
using Content.Server.Implants;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Lock;
|
||||
using Content.Shared.PDA;
|
||||
|
||||
namespace Content.Server.Access.Systems
|
||||
|
|
@ -26,6 +27,7 @@ namespace Content.Server.Access.Systems
|
|||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ChameleonClothingSystem _chameleon = default!;
|
||||
[Dependency] private readonly ChameleonControllerSystem _chamController = default!;
|
||||
[Dependency] private readonly LockSystem _lock = default!;
|
||||
[Dependency] private readonly BiocodeSystem _biocodeSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
|
|
@ -81,7 +83,8 @@ namespace Content.Server.Access.Systems
|
|||
|
||||
private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (args.Target == null || !args.CanReach || !TryComp<AccessComponent>(args.Target, out var targetAccess) || !HasComp<IdCardComponent>(args.Target))
|
||||
if (args.Target == null || !args.CanReach || _lock.IsLocked(uid) ||
|
||||
!TryComp<AccessComponent>(args.Target, out var targetAccess) || !HasComp<IdCardComponent>(args.Target))
|
||||
return;
|
||||
|
||||
// Sunrise-Start
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System.Linq;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Forensics;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Hands.Systems;
|
||||
using Content.Server.Mind;
|
||||
|
|
@ -21,6 +20,7 @@ using Content.Shared.PDA;
|
|||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.StationRecords;
|
||||
using Content.Shared.Throwing;
|
||||
|
|
|
|||
|
|
@ -276,8 +276,8 @@ namespace Content.Server.Construction
|
|||
if(!insertStep.EntityValid(insert, EntityManager, Factory))
|
||||
return HandleResult.False;
|
||||
|
||||
// Unremovable items can't be inserted, unless they are a lingering stack
|
||||
if(HasComp<UnremoveableComponent>(insert) && (!TryComp<StackComponent>(insert, out var comp) || !comp.Lingering))
|
||||
// Unremovable items can't be inserted
|
||||
if(HasComp<UnremoveableComponent>(insert))
|
||||
return HandleResult.False;
|
||||
|
||||
// If we're only testing whether this step would be handled by the given event, then we're done.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
|
|
@ -16,6 +15,7 @@ using Content.Shared.IdentityManagement;
|
|||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Jittering;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.NodeContainer;
|
||||
using Content.Shared.NodeContainer.NodeGroups;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Numerics;
|
|||
using Content.Server.Announcements;
|
||||
using Content.Server.Discord;
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Maps;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Shuttles.Components;
|
||||
|
|
@ -13,6 +12,7 @@ using Content.Shared.GameTicking;
|
|||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Asynchronous;
|
||||
|
|
@ -20,7 +20,6 @@ using Robust.Shared.Audio;
|
|||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Content.Server.Mind;
|
|||
using Content.Server.Roles;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Localizations;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ using Content.Shared.NPC.Components;
|
|||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Nuke;
|
||||
using Content.Shared.NukeOps;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Store;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Zombies;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ using Content.Shared.Mobs.Systems;
|
|||
using Content.Shared.NPC.Prototypes;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Revolutionary.Components;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
|
@ -204,7 +205,10 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem<RevolutionaryRuleCo
|
|||
if (_mind.TryGetMind(ev.User.Value, out var revMindId, out _))
|
||||
{
|
||||
if (_role.MindHasRole<RevolutionaryRoleComponent>(revMindId, out var role))
|
||||
{
|
||||
role.Value.Comp2.ConvertedCount++;
|
||||
Dirty(role.Value.Owner, role.Value.Comp2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using Content.Server.Shuttles.Systems;
|
|||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Survivor.Components;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using Content.Server.Antag;
|
|||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.NPC.Systems;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Antag;
|
||||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Objectives;
|
||||
using Content.Server.PDA.Ringer;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Roles.RoleCodeword;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ using Content.Shared.Mobs;
|
|||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Zombies;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Ghost;
|
||||
|
||||
/// <summary>
|
||||
/// This is used to mark Observers properly, as they get Minds
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ObserverRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
public string Name => Loc.GetString("observer-role-name");
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Ghost.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a ghostrole.
|
||||
/// It also holds the name for the round end display
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class GhostRoleMarkerRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
//TODO does anything still use this? It gets populated by GhostRolesystem but I don't see anything ever reading it
|
||||
[DataField] public string? Name;
|
||||
|
||||
}
|
||||
|
|
@ -565,9 +565,6 @@ public sealed class GhostRoleSystem : EntitySystem
|
|||
_mindSystem.TransferTo(newMind, mob);
|
||||
|
||||
_roleSystem.MindAddRoles(newMind.Owner, role.MindRoles, newMind.Comp);
|
||||
|
||||
if (_roleSystem.MindHasRole<GhostRoleMarkerRoleComponent>(newMind!, out var markerRole))
|
||||
markerRole.Value.Comp2.Name = role.RoleName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,87 +1,5 @@
|
|||
using Content.Server.Light.Components;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems
|
||||
{
|
||||
public sealed class LightBulbSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
namespace Content.Server.Light.EntitySystems;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LightBulbComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<LightBulbComponent, LandEvent>(HandleLand);
|
||||
SubscribeLocalEvent<LightBulbComponent, BreakageEventArgs>(OnBreak);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, LightBulbComponent bulb, ComponentInit args)
|
||||
{
|
||||
// update default state of bulbs
|
||||
SetColor(uid, bulb.Color, bulb);
|
||||
SetState(uid, bulb.State, bulb);
|
||||
}
|
||||
|
||||
private void HandleLand(EntityUid uid, LightBulbComponent bulb, ref LandEvent args)
|
||||
{
|
||||
PlayBreakSound(uid, bulb);
|
||||
SetState(uid, LightBulbState.Broken, bulb);
|
||||
}
|
||||
|
||||
private void OnBreak(EntityUid uid, LightBulbComponent component, BreakageEventArgs args)
|
||||
{
|
||||
SetState(uid, LightBulbState.Broken, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new color for a light bulb and raise event about change
|
||||
/// </summary>
|
||||
public void SetColor(EntityUid uid, Color color, LightBulbComponent? bulb = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bulb))
|
||||
return;
|
||||
|
||||
bulb.Color = color;
|
||||
UpdateAppearance(uid, bulb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new state for a light bulb (broken, burned) and raise event about change
|
||||
/// </summary>
|
||||
public void SetState(EntityUid uid, LightBulbState state, LightBulbComponent? bulb = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bulb))
|
||||
return;
|
||||
|
||||
bulb.State = state;
|
||||
UpdateAppearance(uid, bulb);
|
||||
}
|
||||
|
||||
public void PlayBreakSound(EntityUid uid, LightBulbComponent? bulb = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bulb))
|
||||
return;
|
||||
|
||||
_audio.PlayPvs(bulb.BreakSound, uid);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, LightBulbComponent? bulb = null,
|
||||
AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bulb, ref appearance, logMissing: false))
|
||||
return;
|
||||
|
||||
// try to update appearance and color
|
||||
_appearance.SetData(uid, LightBulbVisuals.State, bulb.State, appearance);
|
||||
_appearance.SetData(uid, LightBulbVisuals.Color, bulb.Color, appearance);
|
||||
}
|
||||
}
|
||||
}
|
||||
public sealed class LightBulbSystem : SharedLightBulbSystem;
|
||||
|
|
|
|||
|
|
@ -1,440 +1,64 @@
|
|||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Emp;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light;
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
|
||||
namespace Content.Server.Light.EntitySystems
|
||||
namespace Content.Server.Light.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// System for the PoweredLightComponents
|
||||
/// </summary>
|
||||
public sealed class PoweredLightSystem : SharedPoweredLightSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// System for the PoweredLightComponents
|
||||
/// </summary>
|
||||
public sealed class PoweredLightSystem : EntitySystem
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!;
|
||||
[Dependency] private readonly LightBulbSystem _bulbSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly DeviceLinkSystem _signalSystem = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly PointLightSystem _pointLight = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!;
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PoweredLightComponent, MapInitEvent>(OnMapInit);
|
||||
|
||||
private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2);
|
||||
public const string LightBulbContainer = "light_bulb";
|
||||
SubscribeLocalEvent<PoweredLightComponent, GhostBooEvent>(OnGhostBoo);
|
||||
|
||||
public override void Initialize()
|
||||
SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
}
|
||||
|
||||
private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args)
|
||||
{
|
||||
if (light.IgnoreGhostsBoo)
|
||||
return;
|
||||
|
||||
// check cooldown first to prevent abuse
|
||||
var time = GameTiming.CurTime;
|
||||
if (light.LastGhostBlink != null)
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PoweredLightComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PoweredLightComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<PoweredLightComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<PoweredLightComponent, InteractHandEvent>(OnInteractHand);
|
||||
|
||||
SubscribeLocalEvent<PoweredLightComponent, GhostBooEvent>(OnGhostBoo);
|
||||
SubscribeLocalEvent<PoweredLightComponent, DamageChangedEvent>(HandleLightDamaged);
|
||||
|
||||
SubscribeLocalEvent<PoweredLightComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
SubscribeLocalEvent<PoweredLightComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
|
||||
SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
|
||||
SubscribeLocalEvent<PoweredLightComponent, PoweredLightDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<PoweredLightComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
|
||||
{
|
||||
light.LightBulbContainer = _containerSystem.EnsureContainer<ContainerSlot>(uid, LightBulbContainer);
|
||||
_signalSystem.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort);
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args)
|
||||
{
|
||||
// TODO: Use ContainerFill dog
|
||||
if (light.HasLampOnSpawn != null)
|
||||
{
|
||||
var entity = Spawn(light.HasLampOnSpawn, Comp<TransformComponent>(uid).Coordinates);
|
||||
_containerSystem.Insert(entity, light.LightBulbContainer);
|
||||
}
|
||||
// need this to update visualizers
|
||||
UpdateLight(uid, light);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
if (time <= light.LastGhostBlink + light.GhostBlinkingCooldown)
|
||||
return;
|
||||
|
||||
args.Handled = InsertBulb(uid, args.Used, component);
|
||||
}
|
||||
|
||||
private void OnInteractHand(EntityUid uid, PoweredLightComponent light, InteractHandEvent args)
|
||||
light.LastGhostBlink = time;
|
||||
|
||||
ToggleBlinkingLight(uid, light, true);
|
||||
uid.SpawnTimer(light.GhostBlinkingTime, () =>
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
ToggleBlinkingLight(uid, light, false);
|
||||
});
|
||||
|
||||
// check if light has bulb to eject
|
||||
var bulbUid = GetBulb(uid, light);
|
||||
if (bulbUid == null)
|
||||
return;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
var userUid = args.User;
|
||||
//removing a broken/burned bulb, so allow instant removal
|
||||
if(TryComp<LightBulbComponent>(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal)
|
||||
{
|
||||
args.Handled = EjectBulb(uid, userUid, light) != null;
|
||||
return;
|
||||
}
|
||||
|
||||
// removing a working bulb, so require a delay
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true,
|
||||
});
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
#region Bulb Logic API
|
||||
/// <summary>
|
||||
/// Inserts the bulb if possible.
|
||||
/// </summary>
|
||||
/// <returns>True if it could insert it, false if it couldn't.</returns>
|
||||
public bool InsertBulb(EntityUid uid, EntityUid bulbUid, PoweredLightComponent? light = null)
|
||||
private void OnMapInit(EntityUid uid, PoweredLightComponent light, MapInitEvent args)
|
||||
{
|
||||
// TODO: Use ContainerFill dog
|
||||
if (light.HasLampOnSpawn != null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return false;
|
||||
|
||||
// check if light already has bulb
|
||||
if (GetBulb(uid, light) != null)
|
||||
return false;
|
||||
|
||||
// check if bulb fits
|
||||
if (!TryComp(bulbUid, out LightBulbComponent? lightBulb))
|
||||
return false;
|
||||
if (lightBulb.Type != light.BulbType)
|
||||
return false;
|
||||
|
||||
// try to insert bulb in container
|
||||
if (!_containerSystem.Insert(bulbUid, light.LightBulbContainer))
|
||||
return false;
|
||||
|
||||
UpdateLight(uid, light);
|
||||
return true;
|
||||
var entity = EntityManager.SpawnEntity(light.HasLampOnSpawn, EntityManager.GetComponent<TransformComponent>(uid).Coordinates);
|
||||
ContainerSystem.Insert(entity, light.LightBulbContainer);
|
||||
}
|
||||
// need this to update visualizers
|
||||
UpdateLight(uid, light);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the bulb to a mob's hand if possible.
|
||||
/// </summary>
|
||||
/// <returns>Bulb uid if it was successfully ejected, null otherwise</returns>
|
||||
public EntityUid? EjectBulb(EntityUid uid, EntityUid? userUid = null, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return null;
|
||||
|
||||
// check if light has bulb
|
||||
if (GetBulb(uid, light) is not { Valid: true } bulb)
|
||||
return null;
|
||||
|
||||
// try to remove bulb from container
|
||||
if (!_containerSystem.Remove(bulb, light.LightBulbContainer))
|
||||
return null;
|
||||
|
||||
// try to place bulb in hands
|
||||
_handsSystem.PickupOrDrop(userUid, bulb);
|
||||
|
||||
UpdateLight(uid, light);
|
||||
return bulb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the spawned prototype of a pre-mapinit powered light with a different variant.
|
||||
/// </summary>
|
||||
public bool ReplaceSpawnedPrototype(Entity<PoweredLightComponent> light, string bulb)
|
||||
{
|
||||
if (light.Comp.LightBulbContainer.ContainedEntity != null)
|
||||
return false;
|
||||
|
||||
if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized)
|
||||
return false;
|
||||
|
||||
light.Comp.HasLampOnSpawn = bulb;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to replace current bulb with a new one
|
||||
/// If succeed old bulb just drops on floor
|
||||
/// </summary>
|
||||
public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null)
|
||||
{
|
||||
EjectBulb(uid, null, light);
|
||||
return InsertBulb(uid, bulb, light);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get light bulb inserted in powered light
|
||||
/// </summary>
|
||||
/// <returns>Bulb uid if it exist, null otherwise</returns>
|
||||
public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return null;
|
||||
|
||||
return light.LightBulbContainer.ContainedEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to break bulb inside light fixture
|
||||
/// </summary>
|
||||
public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light, false))
|
||||
return false;
|
||||
|
||||
// if we aren't mapinited,
|
||||
// just null the spawned bulb
|
||||
if (LifeStage(uid) < EntityLifeStage.MapInitialized)
|
||||
{
|
||||
light.HasLampOnSpawn = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
// check bulb state
|
||||
var bulbUid = GetBulb(uid, light);
|
||||
if (bulbUid == null || !TryComp(bulbUid.Value, out LightBulbComponent? lightBulb))
|
||||
return false;
|
||||
if (lightBulb.State == LightBulbState.Broken)
|
||||
return false;
|
||||
|
||||
// break it
|
||||
_bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb);
|
||||
_bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb);
|
||||
UpdateLight(uid, light);
|
||||
return true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void UpdateLight(EntityUid uid,
|
||||
PoweredLightComponent? light = null,
|
||||
ApcPowerReceiverComponent? powerReceiver = null,
|
||||
AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light, ref powerReceiver, false))
|
||||
return;
|
||||
|
||||
// Optional component.
|
||||
Resolve(uid, ref appearance, false);
|
||||
|
||||
// check if light has bulb
|
||||
var bulbUid = GetBulb(uid, light);
|
||||
if (bulbUid == null || !TryComp(bulbUid.Value, out LightBulbComponent? lightBulb))
|
||||
{
|
||||
SetLight(uid, false, light: light);
|
||||
powerReceiver.Load = 0;
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Empty, appearance);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (lightBulb.State)
|
||||
{
|
||||
case LightBulbState.Normal:
|
||||
if (powerReceiver.Powered && light.On)
|
||||
{
|
||||
SetLight(uid, true, lightBulb.Color, light, lightBulb.LightRadius, lightBulb.LightEnergy, lightBulb.LightSoftness);
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.On, appearance);
|
||||
var time = _gameTiming.CurTime;
|
||||
if (time > light.LastThunk + ThunkDelay)
|
||||
{
|
||||
light.LastThunk = time;
|
||||
_audio.PlayPvs(light.TurnOnSound, uid, light.TurnOnSound.Params.AddVolume(-10f));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetLight(uid, false, light: light);
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Off, appearance);
|
||||
}
|
||||
break;
|
||||
case LightBulbState.Broken:
|
||||
SetLight(uid, false, light: light);
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Broken, appearance);
|
||||
break;
|
||||
case LightBulbState.Burned:
|
||||
SetLight(uid, false, light: light);
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Burned, appearance);
|
||||
break;
|
||||
}
|
||||
|
||||
powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy the light bulb if the light took any damage.
|
||||
/// </summary>
|
||||
public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args)
|
||||
{
|
||||
// Was it being repaired, or did it take damage?
|
||||
if (args.DamageIncreased)
|
||||
{
|
||||
// Eventually, this logic should all be done by this (or some other) system, not a component.
|
||||
TryDestroyBulb(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGhostBoo(EntityUid uid, PoweredLightComponent light, GhostBooEvent args)
|
||||
{
|
||||
if (light.IgnoreGhostsBoo)
|
||||
return;
|
||||
|
||||
// check cooldown first to prevent abuse
|
||||
var time = _gameTiming.CurTime;
|
||||
if (light.LastGhostBlink != null)
|
||||
{
|
||||
if (time <= light.LastGhostBlink + light.GhostBlinkingCooldown)
|
||||
return;
|
||||
}
|
||||
|
||||
light.LastGhostBlink = time;
|
||||
|
||||
ToggleBlinkingLight(uid, light, true);
|
||||
uid.SpawnTimer(light.GhostBlinkingTime, () =>
|
||||
{
|
||||
ToggleBlinkingLight(uid, light, false);
|
||||
});
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
// TODO: Power moment
|
||||
var metadata = MetaData(uid);
|
||||
|
||||
if (metadata.EntityPaused || TerminatingOrDeleted(uid, metadata))
|
||||
return;
|
||||
|
||||
UpdateLight(uid, component);
|
||||
}
|
||||
|
||||
public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking)
|
||||
{
|
||||
if (light.IsBlinking == isNowBlinking)
|
||||
return;
|
||||
|
||||
light.IsBlinking = isNowBlinking;
|
||||
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance);
|
||||
}
|
||||
|
||||
private void OnSignalReceived(EntityUid uid, PoweredLightComponent component, ref SignalReceivedEvent args)
|
||||
{
|
||||
if (args.Port == component.OffPort)
|
||||
SetState(uid, false, component);
|
||||
else if (args.Port == component.OnPort)
|
||||
SetState(uid, true, component);
|
||||
else if (args.Port == component.TogglePort)
|
||||
ToggleLight(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns the light on or of when receiving a <see cref="DeviceNetworkConstants.CmdSetState"/> command.
|
||||
/// The light is turned on or of according to the <see cref="DeviceNetworkConstants.StateEnabled"/> value
|
||||
/// </summary>
|
||||
private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return;
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return;
|
||||
|
||||
SetState(uid, enabled, component);
|
||||
}
|
||||
|
||||
private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return;
|
||||
|
||||
light.CurrentLit = value;
|
||||
_ambientSystem.SetAmbience(uid, value);
|
||||
|
||||
if (TryComp(uid, out PointLightComponent? pointLight))
|
||||
{
|
||||
_pointLight.SetEnabled(uid, value, pointLight);
|
||||
|
||||
if (color != null)
|
||||
_pointLight.SetColor(uid, color.Value, pointLight);
|
||||
if (radius != null)
|
||||
_pointLight.SetRadius(uid, (float) radius, pointLight);
|
||||
if (energy != null)
|
||||
_pointLight.SetEnergy(uid, (float) energy, pointLight);
|
||||
if (softness != null)
|
||||
_pointLight.SetSoftness(uid, (float) softness, pointLight);
|
||||
}
|
||||
|
||||
// light bulbs burn your hands!
|
||||
if (TryComp<DamageOnInteractComponent>(uid, out var damageOnInteractComp))
|
||||
_damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value);
|
||||
}
|
||||
|
||||
public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return;
|
||||
|
||||
light.On = !light.On;
|
||||
UpdateLight(uid, light);
|
||||
}
|
||||
|
||||
public void SetState(EntityUid uid, bool state, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return;
|
||||
|
||||
light.On = state;
|
||||
UpdateLight(uid, light);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
EjectBulb(args.Args.Target.Value, args.Args.User, component);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args)
|
||||
{
|
||||
if (TryDestroyBulb(uid, component))
|
||||
args.Affected = true;
|
||||
}
|
||||
private void OnEmpPulse(EntityUid uid, PoweredLightComponent component, ref EmpPulseEvent args)
|
||||
{
|
||||
if (TryDestroyBulb(uid, component))
|
||||
args.Affected = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +1,25 @@
|
|||
using System.Numerics;
|
||||
using Content.Server.Access.Systems;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Emp;
|
||||
using Content.Server.Medical.CrewMonitoring;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Content.Shared.DeviceNetwork.Components;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Content.Shared.Medical.SuitSensors;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Medical.SuitSensors;
|
||||
|
||||
public sealed class SuitSensorSystem : EntitySystem
|
||||
public sealed class SuitSensorSystem : SharedSuitSensorSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetworkSystem = default!;
|
||||
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly SingletonDeviceNetServerSystem _singletonServerSystem = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
|
||||
SubscribeLocalEvent<SuitSensorComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<SuitSensorComponent, ClothingGotEquippedEvent>(OnEquipped);
|
||||
SubscribeLocalEvent<SuitSensorComponent, ClothingGotUnequippedEvent>(OnUnequipped);
|
||||
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<SuitSensorComponent, GetVerbsEvent<Verb>>(OnVerb);
|
||||
SubscribeLocalEvent<SuitSensorComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
|
||||
SubscribeLocalEvent<SuitSensorComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
||||
|
||||
SubscribeLocalEvent<SuitSensorComponent, EmpPulseEvent>(OnEmpPulse);
|
||||
SubscribeLocalEvent<SuitSensorComponent, EmpDisabledRemoved>(OnEmpFinished);
|
||||
SubscribeLocalEvent<SuitSensorComponent, SuitSensorChangeDoAfterEvent>(OnSuitSensorDoAfter);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
|
@ -78,14 +38,13 @@ public sealed class SuitSensorSystem : EntitySystem
|
|||
if (curTime < sensor.NextUpdate)
|
||||
continue;
|
||||
|
||||
if (!CheckSensorAssignedStation(uid, sensor))
|
||||
if (!CheckSensorAssignedStation((uid, sensor)))
|
||||
continue;
|
||||
|
||||
// TODO: This would cause imprecision at different tick rates.
|
||||
sensor.NextUpdate = curTime + sensor.UpdateRate;
|
||||
sensor.NextUpdate += sensor.UpdateRate;
|
||||
|
||||
// get sensor status
|
||||
var status = GetSensorState(uid, sensor);
|
||||
var status = GetSensorState((uid, sensor));
|
||||
if (status == null)
|
||||
continue;
|
||||
|
||||
|
|
@ -112,406 +71,21 @@ public sealed class SuitSensorSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the sensor is assigned to a station or not
|
||||
/// and tries to assign an unassigned sensor to a station if it's currently on a grid
|
||||
/// </summary>
|
||||
/// <returns>True if the sensor is assigned to a station or assigning it was successful. False otherwise.</returns>
|
||||
private bool CheckSensorAssignedStation(EntityUid uid, SuitSensorComponent sensor)
|
||||
{
|
||||
if (!sensor.StationId.HasValue && Transform(uid).GridUid == null)
|
||||
return false;
|
||||
|
||||
sensor.StationId = _stationSystem.GetOwningStation(uid);
|
||||
return sensor.StationId.HasValue;
|
||||
}
|
||||
|
||||
private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev)
|
||||
{
|
||||
// If the player spawns in arrivals then the grid underneath them may not be appropriate.
|
||||
// in which case we'll just use the station spawn code told us they are attached to and set all of their
|
||||
// sensors.
|
||||
var sensorQuery = GetEntityQuery<SuitSensorComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
RecursiveSensor(ev.Mob, ev.Station, sensorQuery, xformQuery);
|
||||
}
|
||||
|
||||
private void RecursiveSensor(EntityUid uid, EntityUid stationUid, EntityQuery<SuitSensorComponent> sensorQuery, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
var xform = xformQuery.GetComponent(uid);
|
||||
var enumerator = xform.ChildEnumerator;
|
||||
|
||||
while (enumerator.MoveNext(out var child))
|
||||
{
|
||||
if (sensorQuery.TryGetComponent(child, out var sensor))
|
||||
{
|
||||
sensor.StationId = stationUid;
|
||||
}
|
||||
|
||||
RecursiveSensor(child, stationUid, sensorQuery, xformQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapInit(EntityUid uid, SuitSensorComponent component, MapInitEvent args)
|
||||
{
|
||||
// Fallback
|
||||
component.StationId ??= _stationSystem.GetOwningStation(uid);
|
||||
|
||||
// generate random mode
|
||||
if (component.RandomMode)
|
||||
{
|
||||
//make the sensor mode favor higher levels, except coords.
|
||||
var modesDist = new[]
|
||||
{
|
||||
SuitSensorMode.SensorOff,
|
||||
SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary,
|
||||
SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals,
|
||||
SuitSensorMode.SensorCords, SuitSensorMode.SensorCords
|
||||
};
|
||||
component.Mode = _random.Pick(modesDist);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEquipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotEquippedEvent args)
|
||||
{
|
||||
component.User = args.Wearer;
|
||||
}
|
||||
|
||||
private void OnUnequipped(EntityUid uid, SuitSensorComponent component, ref ClothingGotUnequippedEvent args)
|
||||
{
|
||||
component.User = null;
|
||||
}
|
||||
|
||||
private void OnExamine(EntityUid uid, SuitSensorComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
string msg;
|
||||
switch (component.Mode)
|
||||
{
|
||||
case SuitSensorMode.SensorOff:
|
||||
msg = "suit-sensor-examine-off";
|
||||
break;
|
||||
case SuitSensorMode.SensorBinary:
|
||||
msg = "suit-sensor-examine-binary";
|
||||
break;
|
||||
case SuitSensorMode.SensorVitals:
|
||||
msg = "suit-sensor-examine-vitals";
|
||||
break;
|
||||
case SuitSensorMode.SensorCords:
|
||||
msg = "suit-sensor-examine-cords";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
args.PushMarkup(Loc.GetString(msg));
|
||||
}
|
||||
|
||||
private void OnVerb(EntityUid uid, SuitSensorComponent component, GetVerbsEvent<Verb> args)
|
||||
{
|
||||
// check if user can change sensor
|
||||
if (component.ControlsLocked)
|
||||
return;
|
||||
|
||||
// standard interaction checks
|
||||
if (!args.CanInteract || args.Hands == null)
|
||||
return;
|
||||
|
||||
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target))
|
||||
return;
|
||||
|
||||
// check if target is incapacitated (cuffed, dead, etc)
|
||||
if (component.User != null && args.User != component.User && _actionBlocker.CanInteract(component.User.Value, null))
|
||||
return;
|
||||
|
||||
args.Verbs.UnionWith(new[]
|
||||
{
|
||||
CreateVerb(uid, component, args.User, SuitSensorMode.SensorOff),
|
||||
CreateVerb(uid, component, args.User, SuitSensorMode.SensorBinary),
|
||||
CreateVerb(uid, component, args.User, SuitSensorMode.SensorVitals),
|
||||
CreateVerb(uid, component, args.User, SuitSensorMode.SensorCords)
|
||||
});
|
||||
}
|
||||
|
||||
private void OnInsert(EntityUid uid, SuitSensorComponent component, EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.ActivationContainer)
|
||||
return;
|
||||
|
||||
component.User = args.Container.Owner;
|
||||
}
|
||||
|
||||
private void OnRemove(EntityUid uid, SuitSensorComponent component, EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != component.ActivationContainer)
|
||||
return;
|
||||
|
||||
component.User = null;
|
||||
}
|
||||
|
||||
private void OnEmpPulse(EntityUid uid, SuitSensorComponent component, ref EmpPulseEvent args)
|
||||
private void OnEmpPulse(Entity<SuitSensorComponent> ent, ref EmpPulseEvent args)
|
||||
{
|
||||
args.Affected = true;
|
||||
args.Disabled = true;
|
||||
|
||||
component.PreviousMode = component.Mode;
|
||||
SetSensor((uid, component), SuitSensorMode.SensorOff, null);
|
||||
ent.Comp.PreviousMode = ent.Comp.Mode;
|
||||
SetSensor(ent.AsNullable(), SuitSensorMode.SensorOff, null);
|
||||
|
||||
component.PreviousControlsLocked = component.ControlsLocked;
|
||||
component.ControlsLocked = true;
|
||||
ent.Comp.PreviousControlsLocked = ent.Comp.ControlsLocked;
|
||||
ent.Comp.ControlsLocked = true;
|
||||
}
|
||||
|
||||
private void OnEmpFinished(EntityUid uid, SuitSensorComponent component, ref EmpDisabledRemoved args)
|
||||
private void OnEmpFinished(Entity<SuitSensorComponent> ent, ref EmpDisabledRemoved args)
|
||||
{
|
||||
SetSensor((uid, component), component.PreviousMode, null);
|
||||
component.ControlsLocked = component.PreviousControlsLocked;
|
||||
}
|
||||
|
||||
private Verb CreateVerb(EntityUid uid, SuitSensorComponent component, EntityUid userUid, SuitSensorMode mode)
|
||||
{
|
||||
return new Verb()
|
||||
{
|
||||
Text = GetModeName(mode),
|
||||
Disabled = component.Mode == mode,
|
||||
Priority = -(int) mode, // sort them in descending order
|
||||
Category = VerbCategory.SetSensor,
|
||||
Act = () => TrySetSensor((uid, component), mode, userUid)
|
||||
};
|
||||
}
|
||||
|
||||
private string GetModeName(SuitSensorMode mode)
|
||||
{
|
||||
string name;
|
||||
switch (mode)
|
||||
{
|
||||
case SuitSensorMode.SensorOff:
|
||||
name = "suit-sensor-mode-off";
|
||||
break;
|
||||
case SuitSensorMode.SensorBinary:
|
||||
name = "suit-sensor-mode-binary";
|
||||
break;
|
||||
case SuitSensorMode.SensorVitals:
|
||||
name = "suit-sensor-mode-vitals";
|
||||
break;
|
||||
case SuitSensorMode.SensorCords:
|
||||
name = "suit-sensor-mode-cords";
|
||||
break;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
return Loc.GetString(name);
|
||||
}
|
||||
|
||||
public void TrySetSensor(Entity<SuitSensorComponent> sensors, SuitSensorMode mode, EntityUid userUid)
|
||||
{
|
||||
var comp = sensors.Comp;
|
||||
|
||||
if (!Resolve(sensors, ref comp))
|
||||
return;
|
||||
|
||||
if (comp.User == null || userUid == comp.User)
|
||||
SetSensor(sensors, mode, userUid);
|
||||
else
|
||||
{
|
||||
var doAfterEvent = new SuitSensorChangeDoAfterEvent(mode);
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, userUid, comp.SensorsTime, doAfterEvent, sensors)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfterArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSuitSensorDoAfter(Entity<SuitSensorComponent> sensors, ref SuitSensorChangeDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
SetSensor(sensors, args.Mode, args.User);
|
||||
}
|
||||
|
||||
public void SetSensor(Entity<SuitSensorComponent> sensors, SuitSensorMode mode, EntityUid? userUid = null)
|
||||
{
|
||||
var comp = sensors.Comp;
|
||||
|
||||
comp.Mode = mode;
|
||||
|
||||
if (userUid != null)
|
||||
{
|
||||
var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode)));
|
||||
_popupSystem.PopupEntity(msg, sensors, userUid.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set all suit sensors on the equipment someone is wearing to the specified mode.
|
||||
/// </summary>
|
||||
public void SetAllSensors(EntityUid target, SuitSensorMode mode, SlotFlags slots = SlotFlags.All )
|
||||
{
|
||||
// iterate over all inventory slots
|
||||
var slotEnumerator = _inventory.GetSlotEnumerator(target, slots);
|
||||
while (slotEnumerator.NextItem(out var item, out _))
|
||||
{
|
||||
if (TryComp<SuitSensorComponent>(item, out var sensorComp))
|
||||
SetSensor((item, sensorComp), mode);
|
||||
}
|
||||
}
|
||||
|
||||
public SuitSensorStatus? GetSensorState(EntityUid uid, SuitSensorComponent? sensor = null, TransformComponent? transform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref sensor, ref transform))
|
||||
return null;
|
||||
|
||||
// check if sensor is enabled and worn by user
|
||||
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp<MobStateComponent>(sensor.User) || transform.GridUid == null)
|
||||
return null;
|
||||
|
||||
var sensorTransform = Transform(uid);
|
||||
var sensorMapId = sensorTransform.MapID;
|
||||
|
||||
// try to get mobs id from ID slot
|
||||
var userName = Loc.GetString("suit-sensor-component-unknown-name");
|
||||
var userJob = Loc.GetString("suit-sensor-component-unknown-job");
|
||||
var userJobIcon = "JobIconNoId";
|
||||
var userJobDepartments = new List<string>();
|
||||
|
||||
if (_idCardSystem.TryFindIdCard(sensor.User.Value, out var card))
|
||||
{
|
||||
if (card.Comp.FullName != null)
|
||||
userName = card.Comp.FullName;
|
||||
if (card.Comp.LocalizedJobTitle != null)
|
||||
userJob = card.Comp.LocalizedJobTitle;
|
||||
userJobIcon = card.Comp.JobIcon;
|
||||
|
||||
foreach (var department in card.Comp.JobDepartments)
|
||||
userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name));
|
||||
}
|
||||
|
||||
// get health mob state
|
||||
var isAlive = false;
|
||||
if (TryComp(sensor.User.Value, out MobStateComponent? mobState))
|
||||
isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState);
|
||||
|
||||
// get mob total damage
|
||||
var totalDamage = 0;
|
||||
if (TryComp<DamageableComponent>(sensor.User.Value, out var damageable))
|
||||
totalDamage = damageable.TotalDamage.Int();
|
||||
|
||||
// Get mob total damage crit threshold
|
||||
int? totalDamageThreshold = null;
|
||||
if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold))
|
||||
totalDamageThreshold = critThreshold.Value.Int();
|
||||
|
||||
// finally, form suit sensor status
|
||||
var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(uid), userName, userJob, userJobIcon, userJobDepartments, sensorMapId);
|
||||
switch (sensor.Mode)
|
||||
{
|
||||
case SuitSensorMode.SensorBinary:
|
||||
status.IsAlive = isAlive;
|
||||
break;
|
||||
case SuitSensorMode.SensorVitals:
|
||||
status.IsAlive = isAlive;
|
||||
status.TotalDamage = totalDamage;
|
||||
status.TotalDamageThreshold = totalDamageThreshold;
|
||||
break;
|
||||
case SuitSensorMode.SensorCords:
|
||||
status.IsAlive = isAlive;
|
||||
status.TotalDamage = totalDamage;
|
||||
status.TotalDamageThreshold = totalDamageThreshold;
|
||||
EntityCoordinates coordinates;
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (transform.GridUid != null)
|
||||
{
|
||||
coordinates = new EntityCoordinates(transform.GridUid.Value,
|
||||
Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery),
|
||||
_transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery)));
|
||||
}
|
||||
else if (transform.MapUid != null)
|
||||
{
|
||||
coordinates = new EntityCoordinates(transform.MapUid.Value,
|
||||
_transform.GetWorldPosition(transform, xformQuery));
|
||||
}
|
||||
else
|
||||
{
|
||||
coordinates = EntityCoordinates.Invalid;
|
||||
}
|
||||
|
||||
status.Coordinates = GetNetCoordinates(coordinates);
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize create a device network package from the suit sensors status.
|
||||
/// </summary>
|
||||
public NetworkPayload SuitSensorToPacket(SuitSensorStatus status)
|
||||
{
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
|
||||
[SuitSensorConstants.NET_NAME] = status.Name,
|
||||
[SuitSensorConstants.NET_JOB] = status.Job,
|
||||
[SuitSensorConstants.NET_JOB_ICON] = status.JobIcon,
|
||||
[SuitSensorConstants.NET_JOB_DEPARTMENTS] = status.JobDepartments,
|
||||
[SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive,
|
||||
[SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid,
|
||||
[SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid,
|
||||
};
|
||||
|
||||
if (status.TotalDamage != null)
|
||||
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage);
|
||||
if (status.TotalDamageThreshold != null)
|
||||
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold);
|
||||
if (status.Coordinates != null)
|
||||
payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates);
|
||||
if (status.MapId != null)
|
||||
payload.Add(SuitSensorConstants.MAP_ID, status.MapId);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to create the suit sensors status from the device network message
|
||||
/// </summary>
|
||||
public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload)
|
||||
{
|
||||
// check command
|
||||
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||
return null;
|
||||
if (command != DeviceNetworkConstants.CmdUpdatedState)
|
||||
return null;
|
||||
|
||||
// check name, job and alive
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_ICON, out string? jobIcon)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_DEPARTMENTS, out List<string>? jobDepartments)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null;
|
||||
|
||||
// try get total damage and cords (optionals)
|
||||
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage);
|
||||
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold);
|
||||
payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords);
|
||||
payload.TryGetValue(SuitSensorConstants.MAP_ID, out MapId? mapId);
|
||||
|
||||
var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments, mapId)
|
||||
{
|
||||
IsAlive = isAlive.Value,
|
||||
TotalDamage = totalDamage,
|
||||
TotalDamageThreshold = totalDamageThreshold,
|
||||
Coordinates = coords,
|
||||
MapId = mapId,
|
||||
};
|
||||
return status;
|
||||
SetSensor(ent.AsNullable(), ent.Comp.PreviousMode, null);
|
||||
ent.Comp.ControlsLocked = ent.Comp.PreviousControlsLocked;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Content.Shared.Implants;
|
|||
using Content.Shared.Implants.Components;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Revolutionary.Components;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Mindshield;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using Content.Server.Mind;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Ninja.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Sticky;
|
||||
using Content.Shared.Trigger;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Ninja.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Warps;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,9 +14,12 @@ namespace Content.Server.Power.Components
|
|||
/// <summary>
|
||||
/// Amount of charge this needs from an APC per second to function.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField("powerLoad")]
|
||||
public float Load { get => NetworkLoad.DesiredPower; set => NetworkLoad.DesiredPower = value; }
|
||||
public override float Load
|
||||
{
|
||||
get => NetworkLoad.DesiredPower;
|
||||
set => NetworkLoad.DesiredPower = value;
|
||||
}
|
||||
|
||||
public ApcPowerProviderComponent? Provider = null;
|
||||
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ public sealed class GeneratorSystem : SharedGeneratorSystem
|
|||
|
||||
generator.On = on;
|
||||
UpdateState(uid, generator);
|
||||
Dirty(uid, generator);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem
|
|||
SubscribeLocalEvent<PortableGeneratorComponent, PortableGeneratorStartMessage>(GeneratorStartMessage);
|
||||
SubscribeLocalEvent<PortableGeneratorComponent, PortableGeneratorStopMessage>(GeneratorStopMessage);
|
||||
SubscribeLocalEvent<PortableGeneratorComponent, PortableGeneratorSwitchOutputMessage>(GeneratorSwitchOutputMessage);
|
||||
|
||||
SubscribeLocalEvent<FuelGeneratorComponent, SwitchPowerCheckEvent>(OnSwitchPowerCheck);
|
||||
}
|
||||
|
||||
private void GeneratorSwitchOutputMessage(EntityUid uid, PortableGeneratorComponent component, PortableGeneratorSwitchOutputMessage args)
|
||||
|
|
@ -195,12 +193,6 @@ public sealed class PortableGeneratorSystem : SharedPortableGeneratorSystem
|
|||
}
|
||||
}
|
||||
|
||||
private void OnSwitchPowerCheck(EntityUid uid, FuelGeneratorComponent comp, ref SwitchPowerCheckEvent args)
|
||||
{
|
||||
if (comp.On)
|
||||
args.DisableMessage = Loc.GetString("fuel-generator-verb-disable-on");
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = EntityQueryEnumerator<PortableGeneratorComponent, FuelGeneratorComponent, PowerSupplierComponent>();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.Popups;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.Nodes;
|
||||
|
|
@ -7,9 +6,7 @@ using Content.Shared.NodeContainer;
|
|||
using Content.Shared.Power;
|
||||
using Content.Shared.Power.Generator;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Power.Generator;
|
||||
|
||||
|
|
@ -26,47 +23,9 @@ public sealed class PowerSwitchableSystem : SharedPowerSwitchableSystem
|
|||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<PowerSwitchableComponent, GetVerbsEvent<InteractionVerb>>(GetVerbs);
|
||||
}
|
||||
|
||||
private void GetVerbs(EntityUid uid, PowerSwitchableComponent comp, GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract)
|
||||
return;
|
||||
|
||||
var voltage = VoltageColor(GetNextVoltage(uid, comp));
|
||||
var msg = Loc.GetString("power-switchable-switch-voltage", ("voltage", voltage));
|
||||
|
||||
InteractionVerb verb = new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
// don't need to check it again since if its disabled server wont let the verb act
|
||||
Cycle(uid, args.User, comp);
|
||||
},
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/zap.svg.192dpi.png")),
|
||||
Text = msg
|
||||
};
|
||||
|
||||
var ev = new SwitchPowerCheckEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
if (ev.DisableMessage != null)
|
||||
{
|
||||
verb.Message = ev.DisableMessage;
|
||||
verb.Disabled = true;
|
||||
}
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cycles voltage then updates nodes and optionally power supplier to match it.
|
||||
/// </summary>
|
||||
public void Cycle(EntityUid uid, EntityUid user, PowerSwitchableComponent? comp = null)
|
||||
// TODO: Prediction
|
||||
/// <inheritdoc/>
|
||||
public override void Cycle(EntityUid uid, EntityUid user, PowerSwitchableComponent? comp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref comp))
|
||||
return;
|
||||
|
|
@ -115,10 +74,3 @@ public sealed class PowerSwitchableSystem : SharedPowerSwitchableSystem
|
|||
_useDelay.TryResetDelay((uid, useDelay));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on a <see cref="PowerSwitchableComponent"/> to see if its verb should work.
|
||||
/// If <see cref="DisableMessage"/> is non-null, the verb is disabled with that as the message.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct SwitchPowerCheckEvent(string? DisableMessage = null);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ using Content.Shared.DoAfter;
|
|||
using Content.Shared.Emag.Systems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
|
|
|
|||
|
|
@ -95,6 +95,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
|
|||
|
||||
private void OnDisable(Entity<RoboticsConsoleComponent> ent, ref RoboticsConsoleDisableMessage args)
|
||||
{
|
||||
if (!ent.Comp.AllowBorgControl)
|
||||
return;
|
||||
|
||||
if (_lock.IsLocked(ent.Owner))
|
||||
return;
|
||||
|
||||
|
|
@ -112,6 +115,9 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
|
|||
|
||||
private void OnDestroy(Entity<RoboticsConsoleComponent> ent, ref RoboticsConsoleDestroyMessage args)
|
||||
{
|
||||
if (!ent.Comp.AllowBorgControl)
|
||||
return;
|
||||
|
||||
if (_lock.IsLocked(ent.Owner))
|
||||
return;
|
||||
|
||||
|
|
@ -139,7 +145,7 @@ public sealed class RoboticsConsoleSystem : SharedRoboticsConsoleSystem
|
|||
|
||||
private void UpdateUserInterface(Entity<RoboticsConsoleComponent> ent)
|
||||
{
|
||||
var state = new RoboticsConsoleState(ent.Comp.Cyborgs);
|
||||
var state = new RoboticsConsoleState(ent.Comp.Cyborgs, ent.Comp.AllowBorgControl);
|
||||
_ui.SetUiState(ent.Owner, RoboticsConsoleUiKey.Key, state);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
using Content.Server.Dragon;
|
||||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a space dragon.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(DragonSystem))]
|
||||
public sealed partial class DragonRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are an initial infected.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class InitialInfectedRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a space ninja.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class NinjaRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a nuke operative.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class NukeopsRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a paradox clone.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ParadoxCloneRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Name modifer applied to the player when they turn into a ghost.
|
||||
/// Needed to be able to keep the original and the clone apart in dead chat.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? NameModifier = "paradox-clone-ghost-name-modifier";
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.NameModifier.EntitySystems;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using Content.Shared.Administration;
|
|||
using Content.Shared.Players;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a Revolutionary.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class RevolutionaryRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// For headrevs, how many people you have converted.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public uint ConvertedCount = 0;
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a briefing to the character info menu, does nothing else.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class RoleBriefingComponent : BaseMindRoleComponent
|
||||
{
|
||||
[DataField]
|
||||
public string Briefing;
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using Content.Shared.Roles.Components;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
public sealed class RoleBriefingSystem : EntitySystem
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a hacked borg.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SubvertedSiliconRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Adds to a mind role ent to tag they're a Survivor
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SurvivorRoleComponent : BaseMindRoleComponent;
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a thief.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ThiefRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a syndicate traitor.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TraitorRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Mind role to tag entities that they're a Wizard
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class WizardRoleComponent : Component;
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
using Content.Shared.Roles;
|
||||
|
||||
namespace Content.Server.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Added to mind role entities to tag that they are a zombie.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class ZombieRoleComponent : BaseMindRoleComponent
|
||||
{
|
||||
}
|
||||
|
|
@ -235,6 +235,8 @@ public sealed partial class ShuttleConsoleSystem : SharedShuttleConsoleSystem
|
|||
_xformQuery.TryGetComponent(comp.DockedWith, out var otherDockXform) ?
|
||||
GetNetEntity(otherDockXform.GridUid) :
|
||||
null,
|
||||
Color = comp.RadarColor,
|
||||
HighlightedColor = comp.HighlightedRadarColor
|
||||
};
|
||||
|
||||
gridDocks.Add(state);
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ public sealed partial class ShuttleSystem
|
|||
|
||||
if (direction.LengthSquared() > minsq)
|
||||
{
|
||||
_stuns.TryUpdateKnockdownDuration(uid, knockdownTime);
|
||||
_stuns.TryCrawling(uid, knockdownTime);
|
||||
_throwing.TryThrow(uid, direction, physics, Transform(uid), _projQuery, direction.Length(), playSound: false);
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using Content.Server.Roles;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Silicons.Borgs.Components;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ public sealed partial class BorgSystem
|
|||
|
||||
private void OnProvideItemStartup(EntityUid uid, ItemBorgModuleComponent component, ComponentStartup args)
|
||||
{
|
||||
component.ProvidedContainer = Container.EnsureContainer<Container>(uid, component.ProvidedContainerId);
|
||||
Container.EnsureContainer<Container>(uid, component.HoldingContainer);
|
||||
}
|
||||
|
||||
private void OnSelectableInstalled(EntityUid uid, SelectableBorgModuleComponent component, ref BorgModuleInstalledEvent args)
|
||||
|
|
@ -187,43 +187,43 @@ public sealed partial class BorgSystem
|
|||
if (!TryComp<HandsComponent>(chassis, out var hands))
|
||||
return;
|
||||
|
||||
var xform = Transform(chassis);
|
||||
foreach (var itemProto in component.Items)
|
||||
{
|
||||
EntityUid item;
|
||||
if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container))
|
||||
return;
|
||||
|
||||
if (!component.ItemsCreated)
|
||||
var xform = Transform(chassis);
|
||||
|
||||
for (var i = 0; i < component.Hands.Count; i++)
|
||||
{
|
||||
var hand = component.Hands[i];
|
||||
var handId = $"{uid}-hand-{i}";
|
||||
|
||||
_hands.AddHand((chassis, hands), handId, hand.Hand);
|
||||
EntityUid? item = null;
|
||||
|
||||
if (component.StoredItems is not null)
|
||||
{
|
||||
if (component.StoredItems.TryGetValue(handId, out var storedItem))
|
||||
{
|
||||
item = storedItem;
|
||||
_container.Remove(storedItem, container, force: true);
|
||||
}
|
||||
}
|
||||
else if (hand.Item is { } itemProto)
|
||||
{
|
||||
item = Spawn(itemProto, xform.Coordinates);
|
||||
}
|
||||
else
|
||||
|
||||
if (item is { } pickUp)
|
||||
{
|
||||
item = component.ProvidedContainer.ContainedEntities
|
||||
.FirstOrDefault(ent => Prototype(ent)?.ID == itemProto.Id);
|
||||
if (!item.IsValid())
|
||||
_hands.DoPickup(chassis, handId, pickUp, hands);
|
||||
if (!hand.ForceRemovable && hand.Hand.Whitelist == null && hand.Hand.Blacklist == null)
|
||||
{
|
||||
Log.Debug($"no items found: {component.ProvidedContainer.ContainedEntities.Count}");
|
||||
continue;
|
||||
EnsureComp<UnremoveableComponent>(pickUp);
|
||||
}
|
||||
|
||||
_container.Remove(item, component.ProvidedContainer, force: true);
|
||||
}
|
||||
|
||||
if (!item.IsValid())
|
||||
{
|
||||
Log.Debug("no valid item");
|
||||
continue;
|
||||
}
|
||||
|
||||
var handId = $"{uid}-item{component.HandCounter}";
|
||||
component.HandCounter++;
|
||||
_hands.AddHand((chassis, hands), handId, HandLocation.Middle);
|
||||
_hands.DoPickup(chassis, handId, item, hands);
|
||||
EnsureComp<UnremoveableComponent>(item);
|
||||
component.ProvidedItems.Add(handId, item);
|
||||
}
|
||||
|
||||
component.ItemsCreated = true;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
private void RemoveProvidedItems(EntityUid chassis, EntityUid uid, BorgChassisComponent? chassisComponent = null, ItemBorgModuleComponent? component = null)
|
||||
|
|
@ -234,27 +234,33 @@ public sealed partial class BorgSystem
|
|||
if (!TryComp<HandsComponent>(chassis, out var hands))
|
||||
return;
|
||||
|
||||
if (TerminatingOrDeleted(uid))
|
||||
{
|
||||
foreach (var (hand, item) in component.ProvidedItems)
|
||||
{
|
||||
QueueDel(item);
|
||||
_hands.RemoveHand(chassis, hand);
|
||||
}
|
||||
component.ProvidedItems.Clear();
|
||||
if (!_container.TryGetContainer(uid, component.HoldingContainer, out var container))
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var (handId, item) in component.ProvidedItems)
|
||||
if (TerminatingOrDeleted(uid))
|
||||
return;
|
||||
|
||||
component.StoredItems ??= new();
|
||||
|
||||
for (var i = 0; i < component.Hands.Count; i++)
|
||||
{
|
||||
if (LifeStage(item) <= EntityLifeStage.MapInitialized)
|
||||
var handId = $"{uid}-hand-{i}";
|
||||
|
||||
if (_hands.TryGetHeldItem(chassis, handId, out var held))
|
||||
{
|
||||
RemComp<UnremoveableComponent>(item);
|
||||
_container.Insert(item, component.ProvidedContainer);
|
||||
RemComp<UnremoveableComponent>(held.Value);
|
||||
_container.Insert(held.Value, container);
|
||||
component.StoredItems[handId] = held.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
component.StoredItems.Remove(handId);
|
||||
}
|
||||
|
||||
_hands.RemoveHand(chassis, handId);
|
||||
}
|
||||
component.ProvidedItems.Clear();
|
||||
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -286,8 +292,8 @@ public sealed partial class BorgSystem
|
|||
if (!TryComp<ItemBorgModuleComponent>(containedModuleUid, out var containedItemModuleComp))
|
||||
continue;
|
||||
|
||||
if (containedItemModuleComp.Items.Count == itemModuleComp.Items.Count &&
|
||||
containedItemModuleComp.Items.All(itemModuleComp.Items.Contains))
|
||||
if (containedItemModuleComp.Hands.Count == itemModuleComp.Hands.Count &&
|
||||
containedItemModuleComp.Hands.All(itemModuleComp.Hands.Contains))
|
||||
{
|
||||
if (user != null)
|
||||
Popup.PopupEntity(Loc.GetString("borg-module-duplicate"), uid, user.Value);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Robotics;
|
||||
|
|
@ -14,6 +19,9 @@ namespace Content.Server.Silicons.Borgs;
|
|||
public sealed partial class BorgSystem
|
||||
{
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
|
||||
|
||||
private void InitializeTransponder()
|
||||
{
|
||||
|
|
@ -28,7 +36,7 @@ public sealed partial class BorgSystem
|
|||
var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta))
|
||||
{
|
||||
if (comp.NextDisable is {} nextDisable && now >= nextDisable)
|
||||
if (comp.NextDisable is { } nextDisable && now >= nextDisable)
|
||||
DoDisable((uid, comp, chassis, meta));
|
||||
|
||||
if (now < comp.NextBroadcast)
|
||||
|
|
@ -38,13 +46,17 @@ public sealed partial class BorgSystem
|
|||
if (_powerCell.TryGetBatteryFromSlot(uid, out var battery))
|
||||
charge = battery.CurrentCharge / battery.MaxCharge;
|
||||
|
||||
var hasBrain = chassis.BrainEntity != null && !comp.FakeDisabled;
|
||||
var hpPercent = CalcHP(uid);
|
||||
|
||||
// checks if it has a brain and if the brain is not a empty MMI (gives false anyway if the fake disable is true)
|
||||
var hasBrain = CheckBrain(chassis.BrainEntity) && !comp.FakeDisabled;
|
||||
var canDisable = comp.NextDisable == null && !comp.FakeDisabling;
|
||||
var data = new CyborgControlData(
|
||||
comp.Sprite,
|
||||
comp.Name,
|
||||
meta.EntityName,
|
||||
charge,
|
||||
hpPercent,
|
||||
chassis.ModuleCount,
|
||||
hasBrain,
|
||||
canDisable);
|
||||
|
|
@ -75,7 +87,7 @@ public sealed partial class BorgSystem
|
|||
return;
|
||||
}
|
||||
|
||||
if (ent.Comp2.BrainEntity is not {} brain)
|
||||
if (ent.Comp2.BrainEntity is not { } brain)
|
||||
return;
|
||||
|
||||
var message = Loc.GetString(ent.Comp1.DisabledPopup, ("name", Name(ent, ent.Comp3)));
|
||||
|
|
@ -161,4 +173,40 @@ public sealed partial class BorgSystem
|
|||
{
|
||||
ent.Comp.Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a ratio between 0 and 1, 1 when they have no damage and 0 whenever they are crit (or more damaged)
|
||||
/// </summary>
|
||||
private float CalcHP(EntityUid uid)
|
||||
{
|
||||
if (!TryComp<DamageableComponent>(uid, out var damageable))
|
||||
return 1;
|
||||
|
||||
if (!_mobState.IsAlive(uid))
|
||||
return 0;
|
||||
|
||||
if (!_mobThresholdSystem.TryGetThresholdForState(uid, MobState.Critical, out var threshold))
|
||||
{
|
||||
Log.Error($"Borg({ToPrettyString(uid)}), doesn't have critical threshold.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1 - ((FixedPoint2)(damageable.TotalDamage / threshold)).Float();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the borg has a brain
|
||||
/// </summary>
|
||||
private bool CheckBrain(EntityUid? brainEntity)
|
||||
{
|
||||
if (brainEntity == null)
|
||||
return false;
|
||||
|
||||
// if the brainEntity.Value has the component MMIComponent then it is a MMI,
|
||||
// in that case it trys to get the "brain" of the MMI, if it is null the MMI is empty and so it returns false
|
||||
if (TryComp<MMIComponent>(brainEntity.Value, out var mmi) && _itemSlotsSystem.GetItemOrNull(brainEntity.Value, mmi.BrainSlotId) == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ using Content.Server.Chat.Managers;
|
|||
using Content.Server.Chat.Systems;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Radio.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chat;
|
||||
|
|
@ -13,9 +12,9 @@ using Content.Shared.GameTicking;
|
|||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace Content.Server.Stack
|
|||
base.SetCount(uid, amount, component);
|
||||
|
||||
// Queue delete stack if count reaches zero.
|
||||
if (component.Count <= 0 && !component.Lingering)
|
||||
if (component.Count <= 0)
|
||||
QueueDel(uid);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Content.Shared.Radio.Components;
|
|||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.GameTicking.Components;
|
||||
using Content.Shared.Light.Components;
|
||||
|
||||
namespace Content.Server.StationEvents.Events;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,58 +1,66 @@
|
|||
namespace Content.Server.Stunnable.Components
|
||||
using Content.Server.Stunnable.Systems;
|
||||
|
||||
namespace Content.Server.Stunnable.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Adds stun when it collides with an entity
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(StunOnCollideSystem))]
|
||||
public sealed partial class StunOnCollideComponent : Component
|
||||
{
|
||||
// TODO: Can probably predict this.
|
||||
|
||||
/// <summary>
|
||||
/// Adds stun when it collides with an entity
|
||||
/// How long we are stunned for
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(StunOnCollideSystem))]
|
||||
public sealed partial class StunOnCollideComponent : Component
|
||||
{
|
||||
// TODO: Can probably predict this.
|
||||
[DataField]
|
||||
public TimeSpan StunAmount;
|
||||
|
||||
/// <summary>
|
||||
/// How long we are stunned for
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan StunAmount;
|
||||
/// <summary>
|
||||
/// How long we are knocked down for
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan KnockdownAmount;
|
||||
|
||||
/// <summary>
|
||||
/// How long we are knocked down for
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan KnockdownAmount;
|
||||
/// <summary>
|
||||
/// How long we are slowed down for
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan SlowdownAmount;
|
||||
|
||||
/// <summary>
|
||||
/// How long we are slowed down for
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public TimeSpan SlowdownAmount;
|
||||
/// <summary>
|
||||
/// Multiplier for a mob's walking speed
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WalkSpeedModifier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier for a mob's walking speed
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float WalkSpeedModifier = 1f;
|
||||
/// <summary>
|
||||
/// Multiplier for a mob's sprinting speed
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float SprintSpeedModifier = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier for a mob's sprinting speed
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float SprintSpeedModifier = 1f;
|
||||
/// <summary>
|
||||
/// Refresh Stun or Slowdown on hit
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Refresh = true;
|
||||
|
||||
/// <summary>
|
||||
/// Refresh Stun or Slowdown on hit
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Refresh = true;
|
||||
/// <summary>
|
||||
/// Should the entity try and stand automatically after being knocked down?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AutoStand = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the entity try and stand automatically after being knocked down?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AutoStand = true;
|
||||
/// <summary>
|
||||
/// Should the entity drop their items upon first being knocked down?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool Drop = true;
|
||||
|
||||
/// <summary>
|
||||
/// Fixture we track for the collision.
|
||||
/// </summary>
|
||||
[DataField("fixture")] public string FixtureID = "projectile";
|
||||
}
|
||||
/// <summary>
|
||||
/// Fixture we track for the collision.
|
||||
/// </summary>
|
||||
[DataField("fixture")] public string FixtureID = "projectile";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,47 +4,60 @@ using JetBrains.Annotations;
|
|||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
namespace Content.Server.Stunnable
|
||||
namespace Content.Server.Stunnable.Systems;
|
||||
|
||||
[UsedImplicitly]
|
||||
internal sealed class StunOnCollideSystem : EntitySystem
|
||||
{
|
||||
[UsedImplicitly]
|
||||
internal sealed class StunOnCollideSystem : EntitySystem
|
||||
[Dependency] private readonly StunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly StunSystem _stunSystem = default!;
|
||||
[Dependency] private readonly MovementModStatusSystem _movementMod = default!;
|
||||
base.Initialize();
|
||||
|
||||
public override void Initialize()
|
||||
SubscribeLocalEvent<StunOnCollideComponent, StartCollideEvent>(HandleCollide);
|
||||
SubscribeLocalEvent<StunOnCollideComponent, ThrowDoHitEvent>(HandleThrow);
|
||||
}
|
||||
|
||||
private void TryDoCollideStun(Entity<StunOnCollideComponent> ent, EntityUid target)
|
||||
{
|
||||
_stunSystem.TryKnockdown(target, ent.Comp.KnockdownAmount, ent.Comp.Refresh, ent.Comp.AutoStand, ent.Comp.Drop);
|
||||
|
||||
if (ent.Comp.Refresh)
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<StunOnCollideComponent, StartCollideEvent>(HandleCollide);
|
||||
SubscribeLocalEvent<StunOnCollideComponent, ThrowDoHitEvent>(HandleThrow);
|
||||
}
|
||||
|
||||
private void TryDoCollideStun(EntityUid uid, StunOnCollideComponent component, EntityUid target)
|
||||
{
|
||||
_stunSystem.TryUpdateStunDuration(target, component.StunAmount);
|
||||
|
||||
_stunSystem.TryKnockdown(target, component.KnockdownAmount, component.Refresh, component.AutoStand, force: true);
|
||||
|
||||
_stunSystem.TryUpdateStunDuration(target, ent.Comp.StunAmount);
|
||||
_movementMod.TryUpdateMovementSpeedModDuration(
|
||||
target,
|
||||
MovementModStatusSystem.TaserSlowdown,
|
||||
component.SlowdownAmount,
|
||||
component.WalkSpeedModifier,
|
||||
component.SprintSpeedModifier
|
||||
ent.Comp.SlowdownAmount,
|
||||
ent.Comp.WalkSpeedModifier,
|
||||
ent.Comp.SprintSpeedModifier
|
||||
);
|
||||
}
|
||||
|
||||
private void HandleCollide(EntityUid uid, StunOnCollideComponent component, ref StartCollideEvent args)
|
||||
else
|
||||
{
|
||||
if (args.OurFixtureId != component.FixtureID)
|
||||
return;
|
||||
|
||||
TryDoCollideStun(uid, component, args.OtherEntity);
|
||||
}
|
||||
|
||||
private void HandleThrow(EntityUid uid, StunOnCollideComponent component, ThrowDoHitEvent args)
|
||||
{
|
||||
TryDoCollideStun(uid, component, args.Target);
|
||||
_stunSystem.TryAddStunDuration(target, ent.Comp.StunAmount);
|
||||
_movementMod.TryAddMovementSpeedModDuration(
|
||||
target,
|
||||
MovementModStatusSystem.TaserSlowdown,
|
||||
ent.Comp.SlowdownAmount,
|
||||
ent.Comp.WalkSpeedModifier,
|
||||
ent.Comp.SprintSpeedModifier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCollide(Entity<StunOnCollideComponent> ent, ref StartCollideEvent args)
|
||||
{
|
||||
if (args.OurFixtureId != ent.Comp.FixtureID)
|
||||
return;
|
||||
|
||||
TryDoCollideStun(ent, args.OtherEntity);
|
||||
}
|
||||
|
||||
private void HandleThrow(Entity<StunOnCollideComponent> ent, ref ThrowDoHitEvent args)
|
||||
{
|
||||
TryDoCollideStun(ent, args.Target);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using Content.Server.Mind;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Thief.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Foldable;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.Thief.Systems;
|
||||
|
|
|
|||
53
Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs
Normal file
53
Content.Server/Trigger/Systems/FireStackOnTriggerSystem.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Trigger;
|
||||
using Content.Shared.Trigger.Components.Effects;
|
||||
|
||||
namespace Content.Server.Trigger.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Trigger system for adding or removing fire stacks from an entity with <see cref="FlammableComponent"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="IgniteOnTriggerSystem"/>
|
||||
public sealed class FireStackOnTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly FlammableSystem _flame = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<FireStackOnTriggerComponent, TriggerEvent>(OnTriggerFlame);
|
||||
SubscribeLocalEvent<ExtinguishOnTriggerComponent, TriggerEvent>(OnTriggerExtinguish);
|
||||
}
|
||||
|
||||
private void OnTriggerFlame(Entity<FireStackOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
|
||||
return;
|
||||
|
||||
var target = ent.Comp.TargetUser ? args.User : ent.Owner;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
_flame.AdjustFireStacks(target.Value, ent.Comp.FireStacks, ignite: ent.Comp.DoIgnite);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnTriggerExtinguish(Entity<ExtinguishOnTriggerComponent> ent, ref TriggerEvent args)
|
||||
{
|
||||
if (args.Key != null && !ent.Comp.KeysIn.Contains(args.Key))
|
||||
return;
|
||||
|
||||
var target = ent.Comp.TargetUser ? args.User : ent.Owner;
|
||||
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
_flame.Extinguish(target.Value);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ namespace Content.Server.Trigger.Systems;
|
|||
/// <summary>
|
||||
/// Handles igniting when triggered and stopping ignition after the delay.
|
||||
/// </summary>
|
||||
/// <seealso cref="FireStackOnTriggerSystem"/>
|
||||
public sealed class IgniteOnTriggerSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ using Content.Shared.Chat;
|
|||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Lock;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Speech;
|
||||
using Content.Shared.VoiceMask;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.VoiceMask;
|
||||
|
|
@ -22,6 +24,8 @@ public sealed partial class VoiceMaskSystem : EntitySystem
|
|||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actions = default!;
|
||||
[Dependency] private readonly LockSystem _lock = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
|
||||
// CCVar.
|
||||
private int _maxNameLength;
|
||||
|
|
@ -30,6 +34,7 @@ public sealed partial class VoiceMaskSystem : EntitySystem
|
|||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<VoiceMaskComponent, InventoryRelayedEvent<TransformSpeakerNameEvent>>(OnTransformSpeakerName);
|
||||
SubscribeLocalEvent<VoiceMaskComponent, LockToggledEvent>(OnLockToggled);
|
||||
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeNameMessage>(OnChangeName);
|
||||
SubscribeLocalEvent<VoiceMaskComponent, VoiceMaskChangeVerbMessage>(OnChangeVerb);
|
||||
SubscribeLocalEvent<VoiceMaskComponent, ClothingGotEquippedEvent>(OnEquip);
|
||||
|
|
@ -47,6 +52,14 @@ public sealed partial class VoiceMaskSystem : EntitySystem
|
|||
args.Args.SpeechVerb = entity.Comp.VoiceMaskSpeechVerb ?? args.Args.SpeechVerb;
|
||||
}
|
||||
|
||||
private void OnLockToggled(Entity<VoiceMaskComponent> ent, ref LockToggledEvent args)
|
||||
{
|
||||
if (args.Locked)
|
||||
_actions.RemoveAction(ent.Comp.ActionEntity);
|
||||
else if (_container.TryGetContainingContainer(ent.Owner, out var container))
|
||||
_actions.AddAction(container.Owner, ref ent.Comp.ActionEntity, ent.Comp.Action, ent);
|
||||
}
|
||||
|
||||
#region User inputs from UI
|
||||
private void OnChangeVerb(Entity<VoiceMaskComponent> entity, ref VoiceMaskChangeVerbMessage msg)
|
||||
{
|
||||
|
|
@ -81,6 +94,9 @@ public sealed partial class VoiceMaskSystem : EntitySystem
|
|||
#region UI
|
||||
private void OnEquip(EntityUid uid, VoiceMaskComponent component, ClothingGotEquippedEvent args)
|
||||
{
|
||||
if (_lock.IsLocked(uid))
|
||||
return;
|
||||
|
||||
_actions.AddAction(args.Wearer, ref component.ActionEntity, component.Action, uid);
|
||||
EnsureComp<VoiceMaskerComponent>(args.Wearer, out var maskerComponent);
|
||||
maskerComponent.VoiceId = component.VoiceId;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using Content.Server.Ghost;
|
||||
using Content.Server.Light.Components;
|
||||
using Content.Server.Xenoarchaeology.Artifact.XAE.Components;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Xenoarchaeology.Artifact;
|
||||
using Content.Shared.Xenoarchaeology.Artifact.XAE;
|
||||
using Robust.Shared.Random;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ using Content.Server.Emoting.Systems;
|
|||
using Content.Server.GameTicking.Rules.Components;
|
||||
using Content.Server.Pinpointer;
|
||||
using Content.Server.Speech.EntitySystems;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Armor;
|
||||
using Content.Shared.Bed.Sleep;
|
||||
|
|
@ -25,6 +24,7 @@ using Content.Shared.Mobs.Components;
|
|||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Throwing;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
|
|
|
|||
|
|
@ -75,7 +75,21 @@ public partial struct ReagentId : IEquatable<ReagentId>
|
|||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Prototype, Data);
|
||||
// We need to make sure we take the hash code of Data by value in order
|
||||
// for hashed key lookups to work properly
|
||||
var hash = 17;
|
||||
unchecked
|
||||
{
|
||||
if (Data?.Count != 0)
|
||||
{
|
||||
foreach (var data in Data ?? [])
|
||||
{
|
||||
hash = hash * 23 + data.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HashCode.Combine(Prototype, hash);
|
||||
}
|
||||
|
||||
public string ToString(FixedPoint2 quantity)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ public partial struct ReagentQuantity : IEquatable<ReagentQuantity>
|
|||
|
||||
public bool Equals(ReagentQuantity other)
|
||||
{
|
||||
return Quantity != other.Quantity && Reagent.Equals(other.Reagent);
|
||||
return Quantity == other.Quantity && Reagent.Equals(other.Reagent);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Content.Shared.Contraband;
|
|||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.Events;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Lock;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
|
@ -24,6 +25,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
|
|||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly TagSystem _tag = default!;
|
||||
[Dependency] protected readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly LockSystem _lock = default!;
|
||||
[Dependency] private readonly BiocodeSystem _biocodeSystem = default!;
|
||||
|
||||
private static readonly SlotFlags[] IgnoredSlots =
|
||||
|
|
@ -170,7 +172,7 @@ public abstract class SharedChameleonClothingSystem : EntitySystem
|
|||
|
||||
private void OnVerb(Entity<ChameleonClothingComponent> ent, ref GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || ent.Comp.User != args.User)
|
||||
if (!args.CanAccess || !args.CanInteract || _lock.IsLocked(ent.Owner))
|
||||
return;
|
||||
|
||||
// Can't pass args from a ref event inside of lambdas
|
||||
|
|
|
|||
|
|
@ -33,6 +33,12 @@ public sealed partial class MachineBoardComponent : Component
|
|||
public EntProtoId Prototype;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marker component for any item that's machine board-like without necessarily being a MachineBoardComponent
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class CircuitboardComponent : Component;
|
||||
|
||||
[DataDefinition, Serializable]
|
||||
public partial struct GenericPartInfo
|
||||
{
|
||||
|
|
|
|||
|
|
@ -184,22 +184,22 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
// Whether the user and the target are too far apart.
|
||||
// Whether the user and the target are too far apart or they are inaccessible.
|
||||
if (args.Target != null)
|
||||
{
|
||||
if (args.DistanceThreshold != null)
|
||||
{
|
||||
if (!_interaction.InRangeUnobstructed(args.User, args.Target.Value, args.DistanceThreshold.Value))
|
||||
if (!_interaction.InRangeAndAccessible(args.User, args.Target.Value, args.DistanceThreshold.Value))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Whether the distance between the tool and the user has grown too much.
|
||||
// Whether the distance between the tool and the user has grown too much or they became inaccessible.
|
||||
if (args.Used != null)
|
||||
{
|
||||
if (args.DistanceThreshold != null)
|
||||
{
|
||||
if (!_interaction.InRangeUnobstructed(args.User,
|
||||
if (!_interaction.InRangeAndAccessible(args.User,
|
||||
args.Used.Value,
|
||||
args.DistanceThreshold.Value))
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using Content.Shared.Localizations;
|
|||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Roles.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.EntityEffects.EffectConditions;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using Content.Shared.DisplacementMap;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Hands.Components;
|
||||
|
|
@ -125,16 +127,45 @@ public sealed partial class HandsComponent : Component
|
|||
public partial record struct Hand
|
||||
{
|
||||
[DataField]
|
||||
public HandLocation Location = HandLocation.Right;
|
||||
public HandLocation Location = HandLocation.Middle;
|
||||
|
||||
/// <summary>
|
||||
/// The label to be displayed for this hand when it does not contain an entity
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? EmptyLabel;
|
||||
|
||||
/// <summary>
|
||||
/// The prototype ID of a "representative" entity prototype for what this hand could hold, used in the UI.
|
||||
/// It is not map-initted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntProtoId? EmptyRepresentative;
|
||||
|
||||
/// <summary>
|
||||
/// What this hand is allowed to hold
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Whitelist;
|
||||
|
||||
/// <summary>
|
||||
/// What this hand is not allowed to hold
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public EntityWhitelist? Blacklist;
|
||||
|
||||
public Hand()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Hand(HandLocation location)
|
||||
public Hand(HandLocation location, LocId? emptyLabel = null, EntProtoId? emptyRepresentative = null, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null)
|
||||
{
|
||||
Location = location;
|
||||
EmptyLabel = emptyLabel;
|
||||
EmptyRepresentative = emptyRepresentative;
|
||||
Whitelist = whitelist;
|
||||
Blacklist = blacklist;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -217,6 +217,9 @@ public abstract partial class SharedHandsSystem
|
|||
if (checkActionBlocker && !_actionBlocker.CanPickup(uid, entity))
|
||||
return false;
|
||||
|
||||
if (!CheckWhitelists((uid, handsComp), handId, entity))
|
||||
return false;
|
||||
|
||||
if (ContainerSystem.TryGetContainingContainer((entity, null, null), out var container))
|
||||
{
|
||||
if (!ContainerSystem.CanRemove(entity, container))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Whitelist;
|
||||
|
||||
namespace Content.Shared.Hands.EntitySystems;
|
||||
|
||||
public abstract partial class SharedHandsSystem
|
||||
{
|
||||
private bool CheckWhitelists(Entity<HandsComponent?> ent, string handId, EntityUid toTest)
|
||||
{
|
||||
if (!TryGetHand(ent, handId, out var hand))
|
||||
return false;
|
||||
|
||||
return _entityWhitelist.CheckBoth(toTest, hand.Value.Blacklist, hand.Value.Whitelist);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,10 @@ using Content.Shared.Interaction;
|
|||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Content.Shared.Whitelist;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Hands.EntitySystems;
|
||||
|
|
@ -23,6 +25,7 @@ public abstract partial class SharedHandsSystem
|
|||
[Dependency] private readonly SharedStorageSystem _storage = default!;
|
||||
[Dependency] protected readonly SharedTransformSystem TransformSystem = default!;
|
||||
[Dependency] private readonly SharedVirtualItemSystem _virtualSystem = default!;
|
||||
[Dependency] private readonly EntityWhitelistSystem _entityWhitelist = default!;
|
||||
|
||||
public event Action<Entity<HandsComponent>, string, HandLocation>? OnPlayerAddHand;
|
||||
public event Action<Entity<HandsComponent>, string>? OnPlayerRemoveHand;
|
||||
|
|
@ -66,9 +69,9 @@ public abstract partial class SharedHandsSystem
|
|||
/// <summary>
|
||||
/// Adds a hand with the given container id and supplied location to the specified entity.
|
||||
/// </summary>
|
||||
public void AddHand(Entity<HandsComponent?> ent, string handName, HandLocation handLocation)
|
||||
public void AddHand(Entity<HandsComponent?> ent, string handName, HandLocation handLocation, LocId? emptyLabel = null, EntProtoId? emptyRepresentative = null, EntityWhitelist? whitelist = null, EntityWhitelist? blacklist = null)
|
||||
{
|
||||
AddHand(ent, handName, new Hand(handLocation));
|
||||
AddHand(ent, handName, new Hand(handLocation, emptyLabel, emptyRepresentative, whitelist, blacklist));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ public sealed class ItemToggleSystem : EntitySystem
|
|||
|
||||
if (ent.Comp.Activated)
|
||||
{
|
||||
var ev = new ItemToggleActivateAttemptEvent(args.User);
|
||||
var ev = new ItemToggleDeactivateAttemptEvent(args.User);
|
||||
RaiseLocalEvent(ent.Owner, ref ev);
|
||||
|
||||
if (ev.Cancelled)
|
||||
|
|
@ -139,7 +139,7 @@ public sealed class ItemToggleSystem : EntitySystem
|
|||
}
|
||||
else
|
||||
{
|
||||
var ev = new ItemToggleDeactivateAttemptEvent(args.User);
|
||||
var ev = new ItemToggleActivateAttemptEvent(args.User);
|
||||
RaiseLocalEvent(ent.Owner, ref ev);
|
||||
|
||||
if (ev.Cancelled)
|
||||
|
|
|
|||
|
|
@ -8,69 +8,61 @@ namespace Content.Shared.Light.Components;
|
|||
/// Component that represents a light bulb. Can be broken, or burned, which turns them mostly useless.
|
||||
/// TODO: Breaking and burning should probably be moved to another component eventually.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class LightBulbComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The color of the lightbulb and the light it produces.
|
||||
/// </summary>
|
||||
[DataField("color")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, AutoNetworkedField]
|
||||
public Color Color = Color.White;
|
||||
|
||||
/// <summary>
|
||||
/// The type of lightbulb. Tube/bulb/etc...
|
||||
/// </summary>
|
||||
[DataField("bulb")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public LightBulbType Type = LightBulbType.Tube;
|
||||
|
||||
/// <summary>
|
||||
/// The initial state of the lightbulb.
|
||||
/// </summary>
|
||||
[DataField("startingState")]
|
||||
[DataField("startingState"), AutoNetworkedField]
|
||||
public LightBulbState State = LightBulbState.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// The temperature the air around the lightbulb is exposed to when the lightbulb burns out.
|
||||
/// </summary>
|
||||
[DataField("BurningTemperature")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int BurningTemperature = 1400;
|
||||
|
||||
/// <summary>
|
||||
/// Relates to how bright the light produced by the lightbulb is.
|
||||
/// </summary>
|
||||
[DataField("lightEnergy")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float LightEnergy = 0.8f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum radius of the point light source this light produces.
|
||||
/// </summary>
|
||||
[DataField("lightRadius")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float LightRadius = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Relates to the falloff constant of the light produced by the lightbulb.
|
||||
/// </summary>
|
||||
[DataField("lightSoftness")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public float LightSoftness = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of power used by the lightbulb when it's active.
|
||||
/// </summary>
|
||||
[DataField("PowerUse")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int PowerUse = 60;
|
||||
|
||||
/// <summary>
|
||||
/// The sound produced when the lightbulb breaks.
|
||||
/// </summary>
|
||||
[DataField("breakSound")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public SoundSpecifier BreakSound = new SoundCollectionSpecifier("GlassBreak", AudioParams.Default.WithVolume(-6f));
|
||||
|
||||
#region Appearance
|
||||
|
|
@ -78,22 +70,19 @@ public sealed partial class LightBulbComponent : Component
|
|||
/// <summary>
|
||||
/// The sprite state used when the lightbulb is intact.
|
||||
/// </summary>
|
||||
[DataField("normalSpriteState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string NormalSpriteState = "normal";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite state used when the lightbulb is broken.
|
||||
/// </summary>
|
||||
[DataField("brokenSpriteState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string BrokenSpriteState = "broken";
|
||||
|
||||
/// <summary>
|
||||
/// The sprite state used when the lightbulb is burned.
|
||||
/// </summary>
|
||||
[DataField("burnedSpriteState")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField]
|
||||
public string BurnedSpriteState = "burned";
|
||||
|
||||
#endregion Appearance
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Shared.Audio;
|
||||
|
|
|
|||
|
|
@ -1,80 +1,87 @@
|
|||
using Content.Server.Light.EntitySystems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
|
||||
namespace Content.Server.Light.Components
|
||||
namespace Content.Shared.Light.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that represents a wall light. It has a light bulb that can be replaced when broken.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(PoweredLightSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause, Access(typeof(SharedPoweredLightSystem))]
|
||||
public sealed partial class PoweredLightComponent : Component
|
||||
{
|
||||
[DataField("burnHandSound")]
|
||||
/*
|
||||
* Stop adding more fields, use components or I will shed you.
|
||||
*/
|
||||
|
||||
[DataField]
|
||||
public SoundSpecifier BurnHandSound = new SoundPathSpecifier("/Audio/Effects/lightburn.ogg");
|
||||
|
||||
[DataField("turnOnSound")]
|
||||
[DataField]
|
||||
public SoundSpecifier TurnOnSound = new SoundPathSpecifier("/Audio/Machines/light_tube_on.ogg");
|
||||
|
||||
[DataField("hasLampOnSpawn", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
|
||||
public string? HasLampOnSpawn = null;
|
||||
// Should be using containerfill?
|
||||
[DataField]
|
||||
public EntProtoId? HasLampOnSpawn = null;
|
||||
|
||||
[DataField("bulb")]
|
||||
public LightBulbType BulbType;
|
||||
|
||||
[DataField("on")]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool On = true;
|
||||
|
||||
[DataField("ignoreGhostsBoo")]
|
||||
[DataField]
|
||||
public bool IgnoreGhostsBoo;
|
||||
|
||||
[DataField("ghostBlinkingTime")]
|
||||
[DataField]
|
||||
public TimeSpan GhostBlinkingTime = TimeSpan.FromSeconds(10);
|
||||
|
||||
[DataField("ghostBlinkingCooldown")]
|
||||
[DataField]
|
||||
public TimeSpan GhostBlinkingCooldown = TimeSpan.FromSeconds(60);
|
||||
|
||||
[ViewVariables]
|
||||
public ContainerSlot LightBulbContainer = default!;
|
||||
[ViewVariables]
|
||||
|
||||
[AutoNetworkedField]
|
||||
public bool CurrentLit;
|
||||
[ViewVariables]
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool IsBlinking;
|
||||
[ViewVariables]
|
||||
|
||||
[DataField, AutoNetworkedField, AutoPausedField]
|
||||
public TimeSpan LastThunk;
|
||||
[ViewVariables]
|
||||
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan? LastGhostBlink;
|
||||
|
||||
[DataField("onPort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
|
||||
public string OnPort = "On";
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> OnPort = "On";
|
||||
|
||||
[DataField("offPort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
|
||||
public string OffPort = "Off";
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> OffPort = "Off";
|
||||
|
||||
[DataField("togglePort", customTypeSerializer: typeof(PrototypeIdSerializer<SinkPortPrototype>))]
|
||||
public string TogglePort = "Toggle";
|
||||
[DataField]
|
||||
public ProtoId<SinkPortPrototype> TogglePort = "Toggle";
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to eject a bulb from this
|
||||
/// </summary>
|
||||
[DataField("ejectBulbDelay")]
|
||||
[DataField]
|
||||
public float EjectBulbDelay = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Shock damage done to a mob that hits the light with an unarmed attack
|
||||
/// </summary>
|
||||
[DataField("unarmedHitShock")]
|
||||
[DataField]
|
||||
public int UnarmedHitShock = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Stun duration applied to a mob that hits the light with an unarmed attack
|
||||
/// </summary>
|
||||
[DataField("unarmedHitStun")]
|
||||
[DataField]
|
||||
public TimeSpan UnarmedHitStun = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
}
|
||||
84
Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs
Normal file
84
Content.Shared/Light/EntitySystems/SharedLightBulbSystem.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using Content.Shared.Destructible;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Shared.Light.EntitySystems;
|
||||
|
||||
public abstract class SharedLightBulbSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<LightBulbComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<LightBulbComponent, LandEvent>(HandleLand);
|
||||
SubscribeLocalEvent<LightBulbComponent, BreakageEventArgs>(OnBreak);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, LightBulbComponent bulb, ComponentInit args)
|
||||
{
|
||||
// update default state of bulbs
|
||||
SetColor(uid, bulb.Color, bulb);
|
||||
SetState(uid, bulb.State, bulb);
|
||||
}
|
||||
|
||||
private void HandleLand(EntityUid uid, LightBulbComponent bulb, ref LandEvent args)
|
||||
{
|
||||
PlayBreakSound(uid, bulb);
|
||||
SetState(uid, LightBulbState.Broken, bulb);
|
||||
}
|
||||
|
||||
private void OnBreak(EntityUid uid, LightBulbComponent component, BreakageEventArgs args)
|
||||
{
|
||||
SetState(uid, LightBulbState.Broken, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new color for a light bulb and raise event about change
|
||||
/// </summary>
|
||||
public void SetColor(EntityUid uid, Color color, LightBulbComponent? bulb = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bulb) || bulb.Color.Equals(color))
|
||||
return;
|
||||
|
||||
bulb.Color = color;
|
||||
Dirty(uid, bulb);
|
||||
UpdateAppearance(uid, bulb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a new state for a light bulb (broken, burned) and raise event about change
|
||||
/// </summary>
|
||||
public void SetState(EntityUid uid, LightBulbState state, LightBulbComponent? bulb = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bulb) || bulb.State == state)
|
||||
return;
|
||||
|
||||
bulb.State = state;
|
||||
Dirty(uid, bulb);
|
||||
UpdateAppearance(uid, bulb);
|
||||
}
|
||||
|
||||
public void PlayBreakSound(EntityUid uid, LightBulbComponent? bulb = null, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bulb))
|
||||
return;
|
||||
|
||||
_audio.PlayPredicted(bulb.BreakSound, uid, user: user);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, LightBulbComponent? bulb = null,
|
||||
AppearanceComponent? appearance = null)
|
||||
{
|
||||
if (!Resolve(uid, ref bulb, ref appearance, logMissing: false))
|
||||
return;
|
||||
|
||||
// try to update appearance and color
|
||||
_appearance.SetData(uid, LightBulbVisuals.State, bulb.State, appearance);
|
||||
_appearance.SetData(uid, LightBulbVisuals.Color, bulb.Color, appearance);
|
||||
}
|
||||
}
|
||||
425
Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs
Normal file
425
Content.Shared/Light/EntitySystems/SharedPoweredLightSystem.cs
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
using Content.Shared.Audio;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Components;
|
||||
using Content.Shared.Damage.Systems;
|
||||
using Content.Shared.DeviceLinking;
|
||||
using Content.Shared.DeviceLinking.Events;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DeviceNetwork.Events;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Power.Components;
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
using Content.Shared.Storage.EntitySystems;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Light.EntitySystems;
|
||||
|
||||
public abstract class SharedPoweredLightSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected readonly IGameTiming GameTiming = default!;
|
||||
[Dependency] private readonly DamageOnInteractSystem _damageOnInteractSystem = default!;
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly SharedLightBulbSystem _bulbSystem = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly SharedPowerReceiverSystem _receiver = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
|
||||
[Dependency] private readonly SharedStorageSystem _storage = default!;
|
||||
[Dependency] private readonly SharedDeviceLinkSystem _deviceLink = default!;
|
||||
|
||||
private static readonly TimeSpan ThunkDelay = TimeSpan.FromSeconds(2);
|
||||
public const string LightBulbContainer = "light_bulb";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<PoweredLightComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<PoweredLightComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
||||
SubscribeLocalEvent<PoweredLightComponent, EntInsertedIntoContainerMessage>(OnInserted);
|
||||
SubscribeLocalEvent<PoweredLightComponent, InteractUsingEvent>(OnInteractUsing);
|
||||
SubscribeLocalEvent<PoweredLightComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<PoweredLightComponent, SignalReceivedEvent>(OnSignalReceived);
|
||||
SubscribeLocalEvent<PoweredLightComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
|
||||
SubscribeLocalEvent<PoweredLightComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<PoweredLightComponent, PoweredLightDoAfterEvent>(OnDoAfter);
|
||||
SubscribeLocalEvent<PoweredLightComponent, DamageChangedEvent>(HandleLightDamaged);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, PoweredLightComponent light, ComponentInit args)
|
||||
{
|
||||
light.LightBulbContainer = ContainerSystem.EnsureContainer<ContainerSlot>(uid, LightBulbContainer);
|
||||
_deviceLink.EnsureSinkPorts(uid, light.OnPort, light.OffPort, light.TogglePort);
|
||||
}
|
||||
|
||||
private void OnRemoved(Entity<PoweredLightComponent> light, ref EntRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != LightBulbContainer)
|
||||
return;
|
||||
|
||||
UpdateLight(light, light);
|
||||
}
|
||||
|
||||
private void OnInserted(Entity<PoweredLightComponent> light, ref EntInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != LightBulbContainer)
|
||||
return;
|
||||
|
||||
UpdateLight(light, light);
|
||||
}
|
||||
|
||||
private void OnInteractUsing(EntityUid uid, PoweredLightComponent component, InteractUsingEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
args.Handled = InsertBulb(uid, args.Used, component, user: args.User, playAnimation: true);
|
||||
}
|
||||
|
||||
private void OnInteractHand(EntityUid uid, PoweredLightComponent light, InteractHandEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
// check if light has bulb to eject
|
||||
var bulbUid = GetBulb(uid, light);
|
||||
if (bulbUid == null)
|
||||
return;
|
||||
|
||||
var userUid = args.User;
|
||||
//removing a broken/burned bulb, so allow instant removal
|
||||
if (TryComp<LightBulbComponent>(bulbUid.Value, out var bulb) && bulb.State != LightBulbState.Normal)
|
||||
{
|
||||
args.Handled = EjectBulb(uid, userUid, light) != null;
|
||||
return;
|
||||
}
|
||||
|
||||
// removing a working bulb, so require a delay
|
||||
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, userUid, light.EjectBulbDelay, new PoweredLightDoAfterEvent(), uid, target: uid)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true,
|
||||
});
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnSignalReceived(Entity<PoweredLightComponent> ent, ref SignalReceivedEvent args)
|
||||
{
|
||||
if (args.Port == ent.Comp.OffPort)
|
||||
SetState(ent, false, ent.Comp);
|
||||
else if (args.Port == ent.Comp.OnPort)
|
||||
SetState(ent, true, ent.Comp);
|
||||
else if (args.Port == ent.Comp.TogglePort)
|
||||
ToggleLight(ent, ent.Comp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns the light on or of when receiving a <see cref="DeviceNetworkConstants.CmdSetState"/> command.
|
||||
/// The light is turned on or of according to the <see cref="DeviceNetworkConstants.StateEnabled"/> value
|
||||
/// </summary>
|
||||
private void OnPacketReceived(EntityUid uid, PoweredLightComponent component, DeviceNetworkPacketEvent args)
|
||||
{
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.Command, out string? command) || command != DeviceNetworkConstants.CmdSetState) return;
|
||||
if (!args.Data.TryGetValue(DeviceNetworkConstants.StateEnabled, out bool enabled)) return;
|
||||
|
||||
SetState(uid, enabled, component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts the bulb if possible.
|
||||
/// </summary>
|
||||
/// <returns>True if it could insert it, false if it couldn't.</returns>
|
||||
public bool InsertBulb(EntityUid uid, EntityUid bulbUid, PoweredLightComponent? light = null, EntityUid? user = null, bool playAnimation = false)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return false;
|
||||
|
||||
// check if light already has bulb
|
||||
if (GetBulb(uid, light) != null)
|
||||
return false;
|
||||
|
||||
// check if bulb fits
|
||||
if (!TryComp<LightBulbComponent>(bulbUid, out var lightBulb))
|
||||
return false;
|
||||
|
||||
if (lightBulb.Type != light.BulbType)
|
||||
return false;
|
||||
|
||||
// try to insert bulb in container
|
||||
if (!ContainerSystem.Insert(bulbUid, light.LightBulbContainer))
|
||||
return false;
|
||||
|
||||
if (playAnimation && TryComp(user, out TransformComponent? xform))
|
||||
{
|
||||
var itemXform = Transform(uid);
|
||||
_storage.PlayPickupAnimation(bulbUid, xform.Coordinates, itemXform.Coordinates, itemXform.LocalRotation, user: user);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ejects the bulb to a mob's hand if possible.
|
||||
/// </summary>
|
||||
/// <returns>Bulb uid if it was successfully ejected, null otherwise</returns>
|
||||
public EntityUid? EjectBulb(EntityUid uid, EntityUid? userUid = null, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return null;
|
||||
|
||||
// check if light has bulb
|
||||
if (GetBulb(uid, light) is not { Valid: true } bulb)
|
||||
return null;
|
||||
|
||||
// try to remove bulb from container
|
||||
if (!ContainerSystem.Remove(bulb, light.LightBulbContainer))
|
||||
return null;
|
||||
|
||||
// try to place bulb in hands
|
||||
_handsSystem.PickupOrDrop(userUid, bulb);
|
||||
|
||||
return bulb;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the spawned prototype of a pre-mapinit powered light with a different variant.
|
||||
/// </summary>
|
||||
public bool ReplaceSpawnedPrototype(Entity<PoweredLightComponent> light, string bulb)
|
||||
{
|
||||
if (light.Comp.LightBulbContainer.ContainedEntity != null)
|
||||
return false;
|
||||
|
||||
if (LifeStage(light.Owner) >= EntityLifeStage.MapInitialized)
|
||||
return false;
|
||||
|
||||
light.Comp.HasLampOnSpawn = bulb;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to replace current bulb with a new one
|
||||
/// If succeed old bulb just drops on floor
|
||||
/// </summary>
|
||||
public bool ReplaceBulb(EntityUid uid, EntityUid bulb, PoweredLightComponent? light = null)
|
||||
{
|
||||
EjectBulb(uid, null, light);
|
||||
return InsertBulb(uid, bulb, light);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to get light bulb inserted in powered light
|
||||
/// </summary>
|
||||
/// <returns>Bulb uid if it exist, null otherwise</returns>
|
||||
public EntityUid? GetBulb(EntityUid uid, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return null;
|
||||
|
||||
return light.LightBulbContainer?.ContainedEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to break bulb inside light fixture
|
||||
/// </summary>
|
||||
public bool TryDestroyBulb(EntityUid uid, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light, false))
|
||||
return false;
|
||||
|
||||
// if we aren't mapinited,
|
||||
// just null the spawned bulb
|
||||
if (LifeStage(uid) < EntityLifeStage.MapInitialized)
|
||||
{
|
||||
light.HasLampOnSpawn = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
// check bulb state
|
||||
var bulbUid = GetBulb(uid, light);
|
||||
if (bulbUid == null || !EntityManager.TryGetComponent(bulbUid.Value, out LightBulbComponent? lightBulb))
|
||||
return false;
|
||||
if (lightBulb.State == LightBulbState.Broken)
|
||||
return false;
|
||||
|
||||
// break it
|
||||
_bulbSystem.SetState(bulbUid.Value, LightBulbState.Broken, lightBulb);
|
||||
_bulbSystem.PlayBreakSound(bulbUid.Value, lightBulb);
|
||||
UpdateLight(uid, light);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void UpdateLight(EntityUid uid,
|
||||
PoweredLightComponent? light = null,
|
||||
SharedApcPowerReceiverComponent? powerReceiver = null,
|
||||
AppearanceComponent? appearance = null,
|
||||
EntityUid? user = null)
|
||||
{
|
||||
// We don't do anything during state application on the client as if
|
||||
// it's due to an entity spawn, we'd have to wait for component init to
|
||||
// be able to do anything, despite the server having already sent us the
|
||||
// state that we need. On the other hand, we still want this to run in
|
||||
// prediction so we can, well, predict lights turning on.
|
||||
if (GameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
if (!Resolve(uid, ref light, false))
|
||||
return;
|
||||
|
||||
if (!_receiver.ResolveApc(uid, ref powerReceiver))
|
||||
return;
|
||||
|
||||
// Optional component.
|
||||
Resolve(uid, ref appearance, false);
|
||||
|
||||
// check if light has bulb
|
||||
var bulbUid = GetBulb(uid, light);
|
||||
if (bulbUid == null || !TryComp<LightBulbComponent>(bulbUid.Value, out var lightBulb))
|
||||
{
|
||||
SetLight(uid, false, light: light);
|
||||
powerReceiver.Load = 0;
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Empty, appearance);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (lightBulb.State)
|
||||
{
|
||||
case LightBulbState.Normal:
|
||||
if (powerReceiver.Powered && light.On)
|
||||
{
|
||||
SetLight(uid, true, lightBulb.Color, light, lightBulb.LightRadius, lightBulb.LightEnergy, lightBulb.LightSoftness);
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.On, appearance);
|
||||
var time = GameTiming.CurTime;
|
||||
if (time > light.LastThunk + ThunkDelay)
|
||||
{
|
||||
light.LastThunk = time;
|
||||
Dirty(uid, light);
|
||||
_audio.PlayPredicted(light.TurnOnSound, uid, user: user, light.TurnOnSound.Params.AddVolume(-10f));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetLight(uid, false, light: light);
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Off, appearance);
|
||||
}
|
||||
break;
|
||||
case LightBulbState.Broken:
|
||||
SetLight(uid, false, light: light);
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Broken, appearance);
|
||||
break;
|
||||
case LightBulbState.Burned:
|
||||
SetLight(uid, false, light: light);
|
||||
_appearance.SetData(uid, PoweredLightVisuals.BulbState, PoweredLightState.Burned, appearance);
|
||||
break;
|
||||
}
|
||||
|
||||
powerReceiver.Load = (light.On && lightBulb.State == LightBulbState.Normal) ? lightBulb.PowerUse : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroy the light bulb if the light took any damage.
|
||||
/// </summary>
|
||||
public void HandleLightDamaged(EntityUid uid, PoweredLightComponent component, DamageChangedEvent args)
|
||||
{
|
||||
// Was it being repaired, or did it take damage?
|
||||
if (args.DamageIncreased)
|
||||
{
|
||||
// Eventually, this logic should all be done by this (or some other) system, not a component.
|
||||
TryDestroyBulb(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, PoweredLightComponent component, ref PowerChangedEvent args)
|
||||
{
|
||||
// TODO: Power moment
|
||||
var metadata = MetaData(uid);
|
||||
|
||||
if (metadata.EntityPaused || TerminatingOrDeleted(uid, metadata))
|
||||
return;
|
||||
|
||||
UpdateLight(uid, component);
|
||||
}
|
||||
|
||||
public void ToggleBlinkingLight(EntityUid uid, PoweredLightComponent light, bool isNowBlinking)
|
||||
{
|
||||
if (light.IsBlinking == isNowBlinking)
|
||||
return;
|
||||
|
||||
light.IsBlinking = isNowBlinking;
|
||||
Dirty(uid, light);
|
||||
|
||||
if (!TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, PoweredLightVisuals.Blinking, isNowBlinking, appearance);
|
||||
}
|
||||
|
||||
private void SetLight(EntityUid uid, bool value, Color? color = null, PoweredLightComponent? light = null, float? radius = null, float? energy = null, float? softness = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return;
|
||||
|
||||
if (light.CurrentLit != value)
|
||||
{
|
||||
light.CurrentLit = value;
|
||||
Dirty(uid, light);
|
||||
}
|
||||
|
||||
_ambientSystem.SetAmbience(uid, value);
|
||||
|
||||
if (_pointLight.TryGetLight(uid, out var pointLight))
|
||||
{
|
||||
_pointLight.SetEnabled(uid, value, pointLight);
|
||||
|
||||
if (color != null)
|
||||
_pointLight.SetColor(uid, color.Value, pointLight);
|
||||
if (radius != null)
|
||||
_pointLight.SetRadius(uid, (float)radius, pointLight);
|
||||
if (energy != null)
|
||||
_pointLight.SetEnergy(uid, (float)energy, pointLight);
|
||||
if (softness != null)
|
||||
_pointLight.SetSoftness(uid, (float)softness, pointLight);
|
||||
}
|
||||
|
||||
// light bulbs burn your hands!
|
||||
if (TryComp<DamageOnInteractComponent>(uid, out var damageOnInteractComp))
|
||||
_damageOnInteractSystem.SetIsDamageActiveTo((uid, damageOnInteractComp), value);
|
||||
}
|
||||
|
||||
public void ToggleLight(EntityUid uid, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return;
|
||||
|
||||
light.On = !light.On;
|
||||
UpdateLight(uid, light);
|
||||
}
|
||||
|
||||
public void SetState(EntityUid uid, bool state, PoweredLightComponent? light = null)
|
||||
{
|
||||
if (!Resolve(uid, ref light))
|
||||
return;
|
||||
|
||||
light.On = state;
|
||||
Dirty(uid, light);
|
||||
UpdateLight(uid, light);
|
||||
}
|
||||
|
||||
private void OnDoAfter(EntityUid uid, PoweredLightComponent component, DoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled || args.Args.Target == null)
|
||||
return;
|
||||
|
||||
EjectBulb(args.Args.Target.Value, args.Args.User, component);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,13 +5,19 @@ namespace Content.Shared.Lock;
|
|||
/// <summary>
|
||||
/// This is used for toggleable items that require the entity to have a lock in a certain state.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))]
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(LockSystem))]
|
||||
public sealed partial class ItemToggleRequiresLockComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// TRUE: the lock must be locked to toggle the item.
|
||||
/// FALSE: the lock must be unlocked to toggle the item.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool RequireLocked;
|
||||
|
||||
/// <summary>
|
||||
/// Popup text for when someone tries to toggle the item, but it's locked. If null, no popup will be shown.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public LocId? LockedPopup = "lock-comp-generic-fail";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,18 @@ public sealed partial class LockComponent : Component
|
|||
[AutoNetworkedField]
|
||||
public bool Locked = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true, will show verbs to lock and unlock the item. Otherwise, it will not.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool ShowLockVerbs = true;
|
||||
|
||||
/// <summary>
|
||||
/// If true will show examine text.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool ShowExamine = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not the lock is locked by simply clicking.
|
||||
/// </summary>
|
||||
|
|
@ -50,7 +62,7 @@ public sealed partial class LockComponent : Component
|
|||
/// The sound played when unlocked.
|
||||
/// </summary>
|
||||
[DataField("unlockingSound"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg")
|
||||
public SoundSpecifier? UnlockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_off.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-5f),
|
||||
};
|
||||
|
|
@ -59,7 +71,7 @@ public sealed partial class LockComponent : Component
|
|||
/// The sound played when locked.
|
||||
/// </summary>
|
||||
[DataField("lockingSound"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public SoundSpecifier LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg")
|
||||
public SoundSpecifier? LockSound = new SoundPathSpecifier("/Audio/Machines/door_lock_on.ogg")
|
||||
{
|
||||
Params = AudioParams.Default.WithVolume(-5f)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -29,12 +29,12 @@ public sealed class LockSystem : EntitySystem
|
|||
{
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly ActivatableUISystem _activatableUI = default!;
|
||||
[Dependency] private readonly EmagSystem _emag = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _sharedPopupSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
|
|
@ -55,8 +55,8 @@ public sealed class LockSystem : EntitySystem
|
|||
SubscribeLocalEvent<LockedWiresPanelComponent, AttemptChangePanelEvent>(OnAttemptChangePanel);
|
||||
SubscribeLocalEvent<LockedAnchorableComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
|
||||
|
||||
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
|
||||
SubscribeLocalEvent<ActivatableUIRequiresLockComponent, LockToggledEvent>(LockToggled);
|
||||
SubscribeLocalEvent<UIRequiresLockComponent, ActivatableUIOpenAttemptEvent>(OnUIOpenAttempt);
|
||||
SubscribeLocalEvent<UIRequiresLockComponent, LockToggledEvent>(LockToggled);
|
||||
|
||||
SubscribeLocalEvent<ItemToggleRequiresLockComponent, ItemToggleActivateAttemptEvent>(OnActivateAttempt);
|
||||
}
|
||||
|
|
@ -97,6 +97,9 @@ public sealed class LockSystem : EntitySystem
|
|||
|
||||
private void OnExamined(EntityUid uid, LockComponent lockComp, ExaminedEvent args)
|
||||
{
|
||||
if (!lockComp.ShowExamine)
|
||||
return;
|
||||
|
||||
args.PushText(Loc.GetString(lockComp.Locked
|
||||
? "lock-comp-on-examined-is-locked"
|
||||
: "lock-comp-on-examined-is-unlocked",
|
||||
|
|
@ -246,6 +249,20 @@ public sealed class LockSystem : EntitySystem
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle the lock to locked if unlocked, and unlocked if locked.
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity to toggle the lock state of.</param>
|
||||
/// <param name="user">The person trying to toggle the lock</param>
|
||||
/// <param name="lockComp">Entities lock comp (will be resolved)</param>
|
||||
public void ToggleLock(EntityUid uid, EntityUid? user, LockComponent? lockComp = null)
|
||||
{
|
||||
if (IsLocked((uid, lockComp)))
|
||||
Unlock(uid, user, lockComp);
|
||||
else
|
||||
Lock(uid, user, lockComp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the entity is locked.
|
||||
/// Entities with no lock component are considered unlocked.
|
||||
|
|
@ -304,11 +321,12 @@ public sealed class LockSystem : EntitySystem
|
|||
|
||||
private void AddToggleLockVerb(EntityUid uid, LockComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract)
|
||||
if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract || !component.ShowLockVerbs)
|
||||
return;
|
||||
|
||||
AlternativeVerb verb = new()
|
||||
{
|
||||
Disabled = !CanToggleLock(uid, args.User),
|
||||
Act = component.Locked
|
||||
? () => TryUnlock(uid, args.User, component)
|
||||
: () => TryLock(uid, args.User, component),
|
||||
|
|
@ -411,41 +429,54 @@ public sealed class LockSystem : EntitySystem
|
|||
args.Cancel();
|
||||
}
|
||||
|
||||
private void OnUIOpenAttempt(EntityUid uid, ActivatableUIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
private void OnUIOpenAttempt(EntityUid uid, UIRequiresLockComponent component, ActivatableUIOpenAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (TryComp<LockComponent>(uid, out var lockComp) && lockComp.Locked != component.RequireLocked)
|
||||
{
|
||||
args.Cancel();
|
||||
if (lockComp.Locked)
|
||||
{
|
||||
_sharedPopupSystem.PopupClient(Loc.GetString("entity-storage-component-locked-message"), uid, args.User);
|
||||
}
|
||||
if (!TryComp<LockComponent>(uid, out var lockComp) || lockComp.Locked == component.RequireLocked)
|
||||
return;
|
||||
|
||||
_audio.PlayPredicted(component.AccessDeniedSound, uid, args.User);
|
||||
args.Cancel();
|
||||
if (lockComp.Locked && component.Popup != null)
|
||||
{
|
||||
_sharedPopupSystem.PopupClient(Loc.GetString(component.Popup), uid, args.User);
|
||||
}
|
||||
|
||||
_audio.PlayPredicted(component.AccessDeniedSound, uid, args.User);
|
||||
}
|
||||
|
||||
private void LockToggled(EntityUid uid, ActivatableUIRequiresLockComponent component, LockToggledEvent args)
|
||||
private void LockToggled(EntityUid uid, UIRequiresLockComponent component, LockToggledEvent args)
|
||||
{
|
||||
if (!TryComp<LockComponent>(uid, out var lockComp) || lockComp.Locked == component.RequireLocked)
|
||||
return;
|
||||
|
||||
_activatableUI.CloseAll(uid);
|
||||
if (component.UserInterfaceKeys == null)
|
||||
{
|
||||
_ui.CloseUis(uid);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var key in component.UserInterfaceKeys)
|
||||
{
|
||||
_ui.CloseUi(uid, key);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnActivateAttempt(EntityUid uid, ItemToggleRequiresLockComponent component, ref ItemToggleActivateAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled)
|
||||
return;
|
||||
|
||||
if (TryComp<LockComponent>(uid, out var lockComp) && lockComp.Locked != component.RequireLocked)
|
||||
if (!TryComp<LockComponent>(uid, out var lockComp) || lockComp.Locked == component.RequireLocked)
|
||||
return;
|
||||
|
||||
args.Cancelled = true;
|
||||
|
||||
if (lockComp.Locked && component.LockedPopup != null)
|
||||
{
|
||||
args.Cancelled = true;
|
||||
if (lockComp.Locked)
|
||||
_sharedPopupSystem.PopupClient(Loc.GetString("lock-comp-generic-fail",
|
||||
("target", Identity.Entity(uid, EntityManager))),
|
||||
_sharedPopupSystem.PopupClient(Loc.GetString(component.LockedPopup,
|
||||
("target", Identity.Entity(uid, EntityManager))),
|
||||
uid,
|
||||
args.User);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,15 @@ namespace Content.Shared.Lock;
|
|||
/// This is used for activatable UIs that require the entity to have a lock in a certain state.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(LockSystem))]
|
||||
public sealed partial class ActivatableUIRequiresLockComponent : Component
|
||||
public sealed partial class UIRequiresLockComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// UIs that are locked behind this component.
|
||||
/// If null, will close all UIs.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<Enum>? UserInterfaceKeys;
|
||||
|
||||
/// <summary>
|
||||
/// TRUE: the lock must be locked to access the UI.
|
||||
/// FALSE: the lock must be unlocked to access the UI.
|
||||
|
|
@ -21,4 +28,7 @@ public sealed partial class ActivatableUIRequiresLockComponent : Component
|
|||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier? AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
|
||||
|
||||
[DataField]
|
||||
public LocId? Popup = "entity-storage-component-locked-message";
|
||||
}
|
||||
468
Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs
Normal file
468
Content.Shared/Medical/SuitSensors/SharedSuitSensorSystem.cs
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
using System.Numerics;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Medical.SuitSensor;
|
||||
using Content.Shared.Mobs;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Station;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Medical.SuitSensors;
|
||||
|
||||
public abstract class SharedSuitSensorSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedStationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
[Dependency] private readonly SharedIdCardSystem _idCardSystem = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private EntityQuery<SuitSensorComponent> _sensorQuery;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<SuitSensorComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawn);
|
||||
SubscribeLocalEvent<SuitSensorComponent, ClothingGotEquippedEvent>(OnEquipped);
|
||||
SubscribeLocalEvent<SuitSensorComponent, ClothingGotUnequippedEvent>(OnUnequipped);
|
||||
SubscribeLocalEvent<SuitSensorComponent, ExaminedEvent>(OnExamine);
|
||||
SubscribeLocalEvent<SuitSensorComponent, GetVerbsEvent<Verb>>(OnVerb);
|
||||
SubscribeLocalEvent<SuitSensorComponent, EntGotInsertedIntoContainerMessage>(OnInsert);
|
||||
SubscribeLocalEvent<SuitSensorComponent, EntGotRemovedFromContainerMessage>(OnRemove);
|
||||
SubscribeLocalEvent<SuitSensorComponent, SuitSensorChangeDoAfterEvent>(OnSuitSensorDoAfter);
|
||||
|
||||
_sensorQuery = GetEntityQuery<SuitSensorComponent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the sensor is assigned to a station or not
|
||||
/// and tries to assign an unassigned sensor to a station if it's currently on a grid.
|
||||
/// </summary>
|
||||
/// <returns>True if the sensor is assigned to a station or assigning it was successful. False otherwise.</returns>
|
||||
public bool CheckSensorAssignedStation(Entity<SuitSensorComponent> sensor)
|
||||
{
|
||||
if (!sensor.Comp.StationId.HasValue && Transform(sensor.Owner).GridUid == null)
|
||||
return false;
|
||||
|
||||
sensor.Comp.StationId = _stationSystem.GetOwningStation(sensor.Owner);
|
||||
Dirty(sensor);
|
||||
return sensor.Comp.StationId.HasValue;
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<SuitSensorComponent> ent, ref MapInitEvent args)
|
||||
{
|
||||
// Fallback
|
||||
ent.Comp.StationId ??= _stationSystem.GetOwningStation(ent.Owner);
|
||||
|
||||
// generate random mode
|
||||
if (ent.Comp.RandomMode)
|
||||
{
|
||||
//make the sensor mode favor higher levels, except coords.
|
||||
var modesDist = new[]
|
||||
{
|
||||
SuitSensorMode.SensorOff,
|
||||
SuitSensorMode.SensorBinary, SuitSensorMode.SensorBinary,
|
||||
SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals, SuitSensorMode.SensorVitals,
|
||||
SuitSensorMode.SensorCords, SuitSensorMode.SensorCords
|
||||
};
|
||||
ent.Comp.Mode = _random.Pick(modesDist);
|
||||
}
|
||||
|
||||
ent.Comp.NextUpdate = _timing.CurTime;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnPlayerSpawn(PlayerSpawnCompleteEvent ev)
|
||||
{
|
||||
// If the player spawns in arrivals then the grid underneath them may not be appropriate.
|
||||
// in which case we'll just use the station spawn code told us they are attached to and set all of their
|
||||
// sensors.
|
||||
RecursiveSensor(ev.Mob, ev.Station);
|
||||
}
|
||||
|
||||
private void RecursiveSensor(EntityUid uid, EntityUid stationUid)
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
var enumerator = xform.ChildEnumerator;
|
||||
|
||||
while (enumerator.MoveNext(out var child))
|
||||
{
|
||||
if (_sensorQuery.TryComp(child, out var sensor))
|
||||
{
|
||||
sensor.StationId = stationUid;
|
||||
Dirty(child, sensor);
|
||||
}
|
||||
|
||||
RecursiveSensor(child, stationUid);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEquipped(Entity<SuitSensorComponent> ent, ref ClothingGotEquippedEvent args)
|
||||
{
|
||||
ent.Comp.User = args.Wearer;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnUnequipped(Entity<SuitSensorComponent> ent, ref ClothingGotUnequippedEvent args)
|
||||
{
|
||||
ent.Comp.User = null;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnExamine(Entity<SuitSensorComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
string msg;
|
||||
switch (ent.Comp.Mode)
|
||||
{
|
||||
case SuitSensorMode.SensorOff:
|
||||
msg = "suit-sensor-examine-off";
|
||||
break;
|
||||
case SuitSensorMode.SensorBinary:
|
||||
msg = "suit-sensor-examine-binary";
|
||||
break;
|
||||
case SuitSensorMode.SensorVitals:
|
||||
msg = "suit-sensor-examine-vitals";
|
||||
break;
|
||||
case SuitSensorMode.SensorCords:
|
||||
msg = "suit-sensor-examine-cords";
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
args.PushMarkup(Loc.GetString(msg));
|
||||
}
|
||||
|
||||
private void OnVerb(Entity<SuitSensorComponent> ent, ref GetVerbsEvent<Verb> args)
|
||||
{
|
||||
// check if user can change sensor
|
||||
if (ent.Comp.ControlsLocked)
|
||||
return;
|
||||
|
||||
// standard interaction checks
|
||||
if (!args.CanInteract || args.Hands == null)
|
||||
return;
|
||||
|
||||
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target))
|
||||
return;
|
||||
|
||||
// check if target is incapacitated (cuffed, dead, etc)
|
||||
if (ent.Comp.User != null && args.User != ent.Comp.User && _actionBlocker.CanInteract(ent.Comp.User.Value, null))
|
||||
return;
|
||||
|
||||
args.Verbs.UnionWith(new[]
|
||||
{
|
||||
CreateVerb(ent, args.User, SuitSensorMode.SensorOff),
|
||||
CreateVerb(ent, args.User, SuitSensorMode.SensorBinary),
|
||||
CreateVerb(ent, args.User, SuitSensorMode.SensorVitals),
|
||||
CreateVerb(ent, args.User, SuitSensorMode.SensorCords)
|
||||
});
|
||||
}
|
||||
|
||||
private void OnInsert(Entity<SuitSensorComponent> ent, ref EntGotInsertedIntoContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != ent.Comp.ActivationContainer)
|
||||
return;
|
||||
|
||||
ent.Comp.User = args.Container.Owner;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnRemove(Entity<SuitSensorComponent> ent, ref EntGotRemovedFromContainerMessage args)
|
||||
{
|
||||
if (args.Container.ID != ent.Comp.ActivationContainer)
|
||||
return;
|
||||
|
||||
ent.Comp.User = null;
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
private Verb CreateVerb(Entity<SuitSensorComponent> ent, EntityUid userUid, SuitSensorMode mode)
|
||||
{
|
||||
return new Verb()
|
||||
{
|
||||
Text = GetModeName(mode),
|
||||
Disabled = ent.Comp.Mode == mode,
|
||||
Priority = -(int)mode, // sort them in descending order
|
||||
Category = VerbCategory.SetSensor,
|
||||
Act = () => TrySetSensor(ent.AsNullable(), mode, userUid)
|
||||
};
|
||||
}
|
||||
|
||||
public string GetModeName(SuitSensorMode mode)
|
||||
{
|
||||
string name;
|
||||
switch (mode)
|
||||
{
|
||||
case SuitSensorMode.SensorOff:
|
||||
name = "suit-sensor-mode-off";
|
||||
break;
|
||||
case SuitSensorMode.SensorBinary:
|
||||
name = "suit-sensor-mode-binary";
|
||||
break;
|
||||
case SuitSensorMode.SensorVitals:
|
||||
name = "suit-sensor-mode-vitals";
|
||||
break;
|
||||
case SuitSensorMode.SensorCords:
|
||||
name = "suit-sensor-mode-cords";
|
||||
break;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
return Loc.GetString(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set <see cref="SuitSensorComponent"/> mode of the entity to the selected in params.
|
||||
/// Works instantly if the user is the player wearing the sensors and will start a DoAfter otherwise.
|
||||
/// </summary>
|
||||
/// <param name="sensors">Entity and its component that should be changed.</param>
|
||||
/// <param name="mode">Selected mode</param>
|
||||
/// <param name="userUid">userUid, when not equal to the <see cref="SuitSensorComponent.User"/>, creates doafter</param>
|
||||
public bool TrySetSensor(Entity<SuitSensorComponent?> sensors, SuitSensorMode mode, EntityUid userUid)
|
||||
{
|
||||
if (!Resolve(sensors, ref sensors.Comp, false))
|
||||
return false;
|
||||
|
||||
if (sensors.Comp.User == null || userUid == sensors.Comp.User)
|
||||
SetSensor(sensors, mode, userUid);
|
||||
else
|
||||
{
|
||||
var doAfterEvent = new SuitSensorChangeDoAfterEvent(mode);
|
||||
var doAfterArgs = new DoAfterArgs(EntityManager, userUid, sensors.Comp.SensorsTime, doAfterEvent, sensors)
|
||||
{
|
||||
BreakOnMove = true,
|
||||
BreakOnDamage = true
|
||||
};
|
||||
|
||||
_doAfterSystem.TryStartDoAfter(doAfterArgs);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnSuitSensorDoAfter(Entity<SuitSensorComponent> sensors, ref SuitSensorChangeDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
SetSensor(sensors.AsNullable(), args.Mode, args.User);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets mode of the <see cref="SuitSensorComponent"/> of the chosen entity.
|
||||
/// Makes popup when <param name="userUid"> not null
|
||||
/// </summary>
|
||||
/// <param name="sensors">Entity and it's component that should be changed</param>
|
||||
/// <param name="mode">Selected mode</param>
|
||||
/// <param name="userUid">uid, required for the popup</param>
|
||||
public void SetSensor(Entity<SuitSensorComponent?> sensors, SuitSensorMode mode, EntityUid? userUid = null)
|
||||
{
|
||||
if (!Resolve(sensors, ref sensors.Comp, false))
|
||||
return;
|
||||
|
||||
sensors.Comp.Mode = mode;
|
||||
Dirty(sensors);
|
||||
|
||||
if (userUid != null)
|
||||
{
|
||||
var msg = Loc.GetString("suit-sensor-mode-state", ("mode", GetModeName(mode)));
|
||||
_popupSystem.PopupClient(msg, sensors, userUid.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set all suit sensors on the equipment someone is wearing to the specified mode.
|
||||
/// </summary>
|
||||
public void SetAllSensors(EntityUid target, SuitSensorMode mode, SlotFlags slots = SlotFlags.All)
|
||||
{
|
||||
// iterate over all inventory slots
|
||||
var slotEnumerator = _inventory.GetSlotEnumerator(target, slots);
|
||||
while (slotEnumerator.NextItem(out var item, out _))
|
||||
{
|
||||
if (TryComp<SuitSensorComponent>(item, out var sensorComp))
|
||||
SetSensor((item, sensorComp), mode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get full <see cref="SuitSensorStatus"/> from the <see cref="SuitSensorComponent"/>
|
||||
/// </summary>
|
||||
/// <param name="uid">Entity to get status</param>
|
||||
/// <returns>Full <see cref="SuitSensorStatus"/> of the chosen uid</returns>
|
||||
public SuitSensorStatus? GetSensorState(Entity<SuitSensorComponent?, TransformComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp1, ref ent.Comp2, false))
|
||||
return null;
|
||||
|
||||
var sensor = ent.Comp1;
|
||||
var transform = ent.Comp2;
|
||||
|
||||
// check if sensor is enabled and worn by user
|
||||
if (sensor.Mode == SuitSensorMode.SensorOff || sensor.User == null || !HasComp<MobStateComponent>(sensor.User) || transform.GridUid == null)
|
||||
return null;
|
||||
|
||||
// try to get mobs id from ID slot
|
||||
var userName = Loc.GetString("suit-sensor-component-unknown-name");
|
||||
var userJob = Loc.GetString("suit-sensor-component-unknown-job");
|
||||
var userJobIcon = "JobIconNoId";
|
||||
var userJobDepartments = new List<string>();
|
||||
|
||||
if (_idCardSystem.TryFindIdCard(sensor.User.Value, out var card))
|
||||
{
|
||||
if (card.Comp.FullName != null)
|
||||
userName = card.Comp.FullName;
|
||||
if (card.Comp.LocalizedJobTitle != null)
|
||||
userJob = card.Comp.LocalizedJobTitle;
|
||||
userJobIcon = card.Comp.JobIcon;
|
||||
|
||||
foreach (var department in card.Comp.JobDepartments)
|
||||
userJobDepartments.Add(Loc.GetString(_proto.Index(department).Name));
|
||||
}
|
||||
|
||||
// get health mob state
|
||||
var isAlive = false;
|
||||
if (TryComp(sensor.User.Value, out MobStateComponent? mobState))
|
||||
isAlive = !_mobStateSystem.IsDead(sensor.User.Value, mobState);
|
||||
|
||||
// get mob total damage
|
||||
var totalDamage = 0;
|
||||
if (TryComp<DamageableComponent>(sensor.User.Value, out var damageable))
|
||||
totalDamage = damageable.TotalDamage.Int();
|
||||
|
||||
// Get mob total damage crit threshold
|
||||
int? totalDamageThreshold = null;
|
||||
if (_mobThresholdSystem.TryGetThresholdForState(sensor.User.Value, MobState.Critical, out var critThreshold))
|
||||
totalDamageThreshold = critThreshold.Value.Int();
|
||||
|
||||
// finally, form suit sensor status
|
||||
var status = new SuitSensorStatus(GetNetEntity(sensor.User.Value), GetNetEntity(ent.Owner), userName, userJob, userJobIcon, userJobDepartments);
|
||||
switch (sensor.Mode)
|
||||
{
|
||||
case SuitSensorMode.SensorBinary:
|
||||
status.IsAlive = isAlive;
|
||||
break;
|
||||
case SuitSensorMode.SensorVitals:
|
||||
status.IsAlive = isAlive;
|
||||
status.TotalDamage = totalDamage;
|
||||
status.TotalDamageThreshold = totalDamageThreshold;
|
||||
break;
|
||||
case SuitSensorMode.SensorCords:
|
||||
status.IsAlive = isAlive;
|
||||
status.TotalDamage = totalDamage;
|
||||
status.TotalDamageThreshold = totalDamageThreshold;
|
||||
EntityCoordinates coordinates;
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (transform.GridUid != null)
|
||||
{
|
||||
coordinates = new EntityCoordinates(transform.GridUid.Value,
|
||||
Vector2.Transform(_transform.GetWorldPosition(transform, xformQuery),
|
||||
_transform.GetInvWorldMatrix(xformQuery.GetComponent(transform.GridUid.Value), xformQuery)));
|
||||
}
|
||||
else if (transform.MapUid != null)
|
||||
{
|
||||
coordinates = new EntityCoordinates(transform.MapUid.Value,
|
||||
_transform.GetWorldPosition(transform, xformQuery));
|
||||
}
|
||||
else
|
||||
{
|
||||
coordinates = EntityCoordinates.Invalid;
|
||||
}
|
||||
|
||||
status.Coordinates = GetNetCoordinates(coordinates);
|
||||
break;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a device network package from the suit sensors status.
|
||||
/// </summary>
|
||||
public NetworkPayload SuitSensorToPacket(SuitSensorStatus status)
|
||||
{
|
||||
var payload = new NetworkPayload()
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
|
||||
[SuitSensorConstants.NET_NAME] = status.Name,
|
||||
[SuitSensorConstants.NET_JOB] = status.Job,
|
||||
[SuitSensorConstants.NET_JOB_ICON] = status.JobIcon,
|
||||
[SuitSensorConstants.NET_JOB_DEPARTMENTS] = status.JobDepartments,
|
||||
[SuitSensorConstants.NET_IS_ALIVE] = status.IsAlive,
|
||||
[SuitSensorConstants.NET_SUIT_SENSOR_UID] = status.SuitSensorUid,
|
||||
[SuitSensorConstants.NET_OWNER_UID] = status.OwnerUid,
|
||||
};
|
||||
|
||||
if (status.TotalDamage != null)
|
||||
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE, status.TotalDamage);
|
||||
if (status.TotalDamageThreshold != null)
|
||||
payload.Add(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, status.TotalDamageThreshold);
|
||||
if (status.Coordinates != null)
|
||||
payload.Add(SuitSensorConstants.NET_COORDINATES, status.Coordinates);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to create the suit sensors status from the device network message.
|
||||
/// </summary>
|
||||
public SuitSensorStatus? PacketToSuitSensor(NetworkPayload payload)
|
||||
{
|
||||
// check command
|
||||
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||
return null;
|
||||
if (command != DeviceNetworkConstants.CmdUpdatedState)
|
||||
return null;
|
||||
|
||||
// check name, job and alive
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_NAME, out string? name)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB, out string? job)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_ICON, out string? jobIcon)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_JOB_DEPARTMENTS, out List<string>? jobDepartments)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_IS_ALIVE, out bool? isAlive)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_SUIT_SENSOR_UID, out NetEntity suitSensorUid)) return null;
|
||||
if (!payload.TryGetValue(SuitSensorConstants.NET_OWNER_UID, out NetEntity ownerUid)) return null;
|
||||
|
||||
// try get total damage and cords (optionals)
|
||||
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE, out int? totalDamage);
|
||||
payload.TryGetValue(SuitSensorConstants.NET_TOTAL_DAMAGE_THRESHOLD, out int? totalDamageThreshold);
|
||||
payload.TryGetValue(SuitSensorConstants.NET_COORDINATES, out NetCoordinates? coords);
|
||||
|
||||
var status = new SuitSensorStatus(ownerUid, suitSensorUid, name, job, jobIcon, jobDepartments)
|
||||
{
|
||||
IsAlive = isAlive.Value,
|
||||
TotalDamage = totalDamage,
|
||||
TotalDamageThreshold = totalDamageThreshold,
|
||||
Coordinates = coords,
|
||||
};
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
using Content.Shared.Medical.SuitSensor;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Medical.SuitSensors;
|
||||
namespace Content.Shared.Medical.SuitSensors;
|
||||
|
||||
/// <summary>
|
||||
/// Tracking device, embedded in almost all uniforms and jumpsuits.
|
||||
/// If enabled, will report to crew monitoring console owners position and status.
|
||||
/// </summary>
|
||||
[RegisterComponent, AutoGenerateComponentPause]
|
||||
[Access(typeof(SuitSensorSystem))]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[Access(typeof(SharedSuitSensorSystem))]
|
||||
[AutoGenerateComponentState, AutoGenerateComponentPause]
|
||||
public sealed partial class SuitSensorComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -20,7 +22,7 @@ public sealed partial class SuitSensorComponent : Component
|
|||
/// <summary>
|
||||
/// If true user can't change suit sensor mode
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool ControlsLocked = false;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -32,7 +34,7 @@ public sealed partial class SuitSensorComponent : Component
|
|||
/// <summary>
|
||||
/// Current sensor mode. Can be switched by user verbs.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
[DataField, AutoNetworkedField]
|
||||
public SuitSensorMode Mode = SuitSensorMode.SensorOff;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -56,7 +58,7 @@ public sealed partial class SuitSensorComponent : Component
|
|||
/// <summary>
|
||||
/// Current user that wears suit sensor. Null if nobody wearing it.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[DataField, AutoNetworkedField]
|
||||
public EntityUid? User = null;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -69,7 +71,7 @@ public sealed partial class SuitSensorComponent : Component
|
|||
/// <summary>
|
||||
/// The station this suit sensor belongs to. If it's null the suit didn't spawn on a station and the sensor doesn't work.
|
||||
/// </summary>
|
||||
[DataField("station")]
|
||||
[DataField("station"), AutoNetworkedField]
|
||||
public EntityUid? StationId = null;
|
||||
|
||||
/// <summary>
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue