This commit is contained in:
Kill_Me_I_Noobs 2025-04-05 22:38:19 +03:00
commit 7fd14e7a15
313 changed files with 15775 additions and 9072 deletions

View file

@ -1,4 +1,3 @@
using Content.Client.Atmos.UI;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using Content.Shared.Atmos.Piping.Binary.Components;
@ -15,7 +14,12 @@ public sealed class GasPressurePumpSystem : SharedGasPressurePumpSystem
private void OnPumpUpdate(Entity<GasPressurePumpComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (UserInterfaceSystem.TryGetOpenUi<GasPressurePumpBoundUserInterface>(ent.Owner, GasPressurePumpUiKey.Key, out var bui))
UpdateUi(ent);
}
protected override void UpdateUi(Entity<GasPressurePumpComponent> ent)
{
if (UserInterfaceSystem.TryGetOpenUi(ent.Owner, GasPressurePumpUiKey.Key, out var bui))
{
bui.Update();
}

View file

@ -103,7 +103,7 @@ public sealed partial class AirAlarmWindow : FancyWindow
_temperature.SetMarkup(Loc.GetString("air-alarm-ui-window-temperature", ("tempC", $"{TemperatureHelpers.KelvinToCelsius(state.TemperatureAverage):0.#}"), ("temperature", $"{state.TemperatureAverage:0.##}")));
_alarmState.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state",
("color", ColorForAlarm(state.AlarmType)),
("state", $"{state.AlarmType}")));
("state", state.AlarmType)));
UpdateModeSelector(state.Mode);
UpdateAutoMode(state.AutoMode);
foreach (var (addr, dev) in state.DeviceData)

View file

@ -27,11 +27,11 @@ public sealed partial class SensorInfo : BoxContainer
_address = address;
SensorAddress.Title = $"{address} : {data.AlarmState}";
SensorAddress.Title = Loc.GetString("air-alarm-ui-window-listing-title", ("address", _address), ("state", data.AlarmState));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state-indicator",
("color", AirAlarmWindow.ColorForAlarm(data.AlarmState)),
("state", $"{data.AlarmState}")));
("state", data.AlarmState)));
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Pressure, data.PressureThreshold)),
("pressure", $"{data.Pressure:0.##}")));
@ -90,11 +90,11 @@ public sealed partial class SensorInfo : BoxContainer
public void ChangeData(AtmosSensorData data)
{
SensorAddress.Title = $"{_address} : {data.AlarmState}";
SensorAddress.Title = Loc.GetString("air-alarm-ui-window-listing-title", ("address", _address), ("state", data.AlarmState));
AlarmStateLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-alarm-state-indicator",
("color", AirAlarmWindow.ColorForAlarm(data.AlarmState)),
("state", $"{data.AlarmState}")));
("state", data.AlarmState)));
PressureLabel.SetMarkup(Loc.GetString("air-alarm-ui-window-pressure-indicator",
("color", AirAlarmWindow.ColorForThreshold(data.Pressure, data.PressureThreshold)),

View file

@ -1,8 +1,7 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Piping.Binary.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Localizations;
using JetBrains.Annotations;
using Robust.Client.UserInterface;
@ -12,7 +11,7 @@ namespace Content.Client.Atmos.UI;
/// Initializes a <see cref="GasPressurePumpWindow"/> and updates it when new server messages are received.
/// </summary>
[UsedImplicitly]
public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface
public sealed class GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
[ViewVariables]
private const float MaxPressure = Atmospherics.MaxOutputPressure;
@ -20,10 +19,6 @@ public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface
[ViewVariables]
private GasPressurePumpWindow? _window;
public GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
@ -35,7 +30,7 @@ public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface
Update();
}
public void Update()
public override void Update()
{
if (_window == null)
return;
@ -52,7 +47,9 @@ public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface
private void OnToggleStatusButtonPressed()
{
if (_window is null) return;
if (_window is null)
return;
SendPredictedMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
}

View file

@ -1,7 +1,6 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Atmos;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Atmos.UI

View file

@ -1,6 +1,7 @@
using Content.Client.Rotation;
using Content.Shared.Buckle;
using Content.Shared.Buckle.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Rotation;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@ -21,6 +22,15 @@ internal sealed class BuckleSystem : SharedBuckleSystem
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
SubscribeLocalEvent<BuckleComponent, BuckledEvent>(OnBuckledEvent);
SubscribeLocalEvent<BuckleComponent, UnbuckledEvent>(OnUnbuckledEvent);
SubscribeLocalEvent<BuckleComponent, AttemptMobCollideEvent>(OnMobCollide);
}
private void OnMobCollide(Entity<BuckleComponent> ent, ref AttemptMobCollideEvent args)
{
if (ent.Comp.Buckled)
{
args.Cancelled = true;
}
}
private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)

View file

@ -67,8 +67,10 @@ public sealed partial class CargoSystem
if (!Resolve(uid, ref sprite))
return;
if (!TryComp<AnimationPlayerComponent>(uid, out var player))
return;
_appearance.TryGetData<CargoTelepadState?>(uid, CargoTelepadVisuals.State, out var state);
AnimationPlayerComponent? player = null;
switch (state)
{
@ -76,7 +78,7 @@ public sealed partial class CargoSystem
if (_player.HasRunningAnimation(uid, TelepadBeamKey))
return;
_player.Stop(uid, player, TelepadIdleKey);
_player.Play(uid, player, CargoTelepadBeamAnimation, TelepadBeamKey);
_player.Play((uid, player), CargoTelepadBeamAnimation, TelepadBeamKey);
break;
case CargoTelepadState.Unpowered:
sprite.LayerSetVisible(CargoTelepadLayers.Beam, false);
@ -90,7 +92,7 @@ public sealed partial class CargoSystem
_player.HasRunningAnimation(uid, player, TelepadBeamKey))
return;
_player.Play(uid, player, CargoTelepadIdleAnimation, TelepadIdleKey);
_player.Play((uid, player), CargoTelepadIdleAnimation, TelepadIdleKey);
break;
}
}

View file

@ -1,31 +0,0 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<!-- Main -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="False">
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-general'}" TargetLayer="General" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Head/Soft/mimesoft.rsi/icon.png"/>
</ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-vocal'}" TargetLayer="Vocal" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Emotes/vocal.png"/>
</ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'emote-menu-category-hands'}" TargetLayer="Hands" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Hands/Gloves/latex.rsi/icon.png"/>
</ui:RadialMenuTextureButtonWithSector>
</ui:RadialContainer>
<!-- General -->
<ui:RadialContainer Name="General" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Vocal -->
<ui:RadialContainer Name="Vocal" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Hands -->
<ui:RadialContainer Name="Hands" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
</ui:RadialMenu>

View file

@ -1,111 +0,0 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Speech;
using Content.Shared.Whitelist;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Chat.UI;
[GenerateTypedNameReferences]
public sealed partial class EmotesMenu : RadialMenu
{
[Dependency] private readonly EntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
public EmotesMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
var spriteSystem = _entManager.System<SpriteSystem>();
var whitelistSystem = _entManager.System<EntityWhitelistSystem>();
var main = FindControl<RadialContainer>("Main");
var emotes = _prototypeManager.EnumeratePrototypes<EmotePrototype>();
foreach (var emote in emotes)
{
var player = _playerManager.LocalSession?.AttachedEntity;
if (emote.Category == EmoteCategory.Invalid ||
emote.ChatTriggers.Count == 0 ||
!(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
continue;
if (!emote.Available &&
_entManager.TryGetComponent<SpeechComponent>(player.Value, out var speech) &&
!speech.AllowedEmotes.Contains(emote.ID))
continue;
var parent = FindControl<RadialContainer>(emote.Category.ToString());
var button = new EmoteMenuButton
{
SetSize = new Vector2(64f, 64f),
ToolTip = Loc.GetString(emote.Name),
ProtoId = emote.ID,
};
var tex = new TextureRect
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Texture = spriteSystem.Frame0(emote.Icon),
TextureScale = new Vector2(2f, 2f),
};
button.AddChild(tex);
parent.AddChild(button);
foreach (var child in main.Children)
{
if (child is not RadialMenuTextureButton castChild)
continue;
if (castChild.TargetLayer == emote.Category.ToString())
{
castChild.Visible = true;
break;
}
}
}
// Set up menu actions
foreach (var child in Children)
{
if (child is not RadialContainer container)
continue;
AddEmoteClickAction(container);
}
}
private void AddEmoteClickAction(RadialContainer container)
{
foreach (var child in container.Children)
{
if (child is not EmoteMenuButton castChild)
continue;
castChild.OnButtonUp += _ =>
{
OnPlayEmote?.Invoke(castChild.ProtoId);
Close();
};
}
}
}
public sealed class EmoteMenuButton : RadialMenuTextureButtonWithSector
{
public ProtoId<EmotePrototype> ProtoId { get; set; }
}

View file

@ -6,7 +6,6 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Linq;
@ -14,6 +13,7 @@ using System.Numerics;
using Content.Shared.FixedPoint;
using Robust.Client.Graphics;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Robust.Client.GameObjects;
namespace Content.Client.Chemistry.UI
{
@ -24,6 +24,10 @@ namespace Content.Client.Chemistry.UI
public sealed partial class ChemMasterWindow : FancyWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly SpriteSystem _sprite;
public event Action<BaseButton.ButtonEventArgs, ReagentButton>? OnReagentButtonPressed;
public readonly Button[] PillTypeButtons;
@ -38,6 +42,8 @@ namespace Content.Client.Chemistry.UI
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_sprite = _entityManager.System<SpriteSystem>();
// Pill type selection buttons, in total there are 20 pills.
// Pill rsi file should have states named as pill1, pill2, and so on.
var resourcePath = new ResPath(PillsRsiPath);
@ -69,7 +75,7 @@ namespace Content.Client.Chemistry.UI
var specifier = new SpriteSpecifier.Rsi(resourcePath, "pill" + (i + 1));
TextureRect pillTypeTexture = new TextureRect
{
Texture = specifier.Frame0(),
Texture = _sprite.Frame0(specifier),
TextureScale = new Vector2(1.75f, 1.75f),
Stretch = TextureRect.StretchMode.KeepCentered,
};

View file

@ -1,4 +1,4 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Timing;
@ -37,7 +37,7 @@ public sealed class FoamVisualizerSystem : VisualizerSystem<FoamVisualsComponent
if (TryComp(uid, out AnimationPlayerComponent? animPlayer)
&& !AnimationSystem.HasRunningAnimation(uid, animPlayer, FoamVisualsComponent.AnimationKey))
{
AnimationSystem.Play(uid, animPlayer, comp.Animation, FoamVisualsComponent.AnimationKey);
AnimationSystem.Play((uid, animPlayer), comp.Animation, FoamVisualsComponent.AnimationKey);
}
}
}

View file

@ -1,4 +1,4 @@
using Content.Shared.Vapor;
using Content.Shared.Vapor;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
@ -41,7 +41,7 @@ public sealed class VaporVisualizerSystem : VisualizerSystem<VaporVisualsCompone
TryComp<AnimationPlayerComponent>(uid, out var animPlayer) &&
!AnimationSystem.HasRunningAnimation(uid, animPlayer, VaporVisualsComponent.AnimationKey))
{
AnimationSystem.Play(uid, animPlayer, comp.VaporFlick, VaporVisualsComponent.AnimationKey);
AnimationSystem.Play((uid, animPlayer), comp.VaporFlick, VaporVisualsComponent.AnimationKey);
}
}

View file

@ -1,4 +1,5 @@
using System.Numerics;
using Content.Client.Sprite;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Utility;
@ -17,12 +18,14 @@ public sealed class ClickableSystem : EntitySystem
private EntityQuery<ClickableComponent> _clickableQuery;
private EntityQuery<TransformComponent> _xformQuery;
private EntityQuery<FadingSpriteComponent> _fadingSpriteQuery;
public override void Initialize()
{
base.Initialize();
_clickableQuery = GetEntityQuery<ClickableComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
_fadingSpriteQuery = GetEntityQuery<FadingSpriteComponent>();
}
/// <summary>
@ -34,7 +37,7 @@ public sealed class ClickableSystem : EntitySystem
/// The draw depth for the sprite that captured the click.
/// </param>
/// <returns>True if the click worked, false otherwise.</returns>
public bool CheckClick(Entity<ClickableComponent?, SpriteComponent, TransformComponent?> entity, Vector2 worldPos, IEye eye, out int drawDepth, out uint renderOrder, out float bottom)
public bool CheckClick(Entity<ClickableComponent?, SpriteComponent, TransformComponent?, FadingSpriteComponent?> entity, Vector2 worldPos, IEye eye, bool excludeFaded, out int drawDepth, out uint renderOrder, out float bottom)
{
if (!_clickableQuery.Resolve(entity.Owner, ref entity.Comp1, false))
{
@ -52,6 +55,14 @@ public sealed class ClickableSystem : EntitySystem
return false;
}
if (excludeFaded && _fadingSpriteQuery.Resolve(entity.Owner, ref entity.Comp4, false))
{
drawDepth = default;
renderOrder = default;
bottom = default;
return false;
}
var sprite = entity.Comp2;
var transform = entity.Comp3;

View file

@ -1,11 +1,7 @@
using Content.Client.ContextMenu.UI;
using Content.Client.Stylesheets;
using Content.Shared.Verbs;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Utility;
@ -27,14 +23,16 @@ public sealed class ExamineButton : ContainerButton
public TextureRect Icon;
public ExamineVerb Verb;
private SpriteSystem _sprite;
public ExamineButton(ExamineVerb verb)
public ExamineButton(ExamineVerb verb, SpriteSystem spriteSystem)
{
Margin = new Thickness(Thickness, Thickness, Thickness, Thickness);
SetOnlyStyleClass(StyleClassExamineButton);
Verb = verb;
_sprite = spriteSystem;
if (verb.Disabled)
{
@ -61,7 +59,7 @@ public sealed class ExamineButton : ContainerButton
if (verb.Icon != null)
{
Icon.Texture = verb.Icon.Frame0();
Icon.Texture = _sprite.Frame0(verb.Icon);
Icon.Stretch = TextureRect.StretchMode.KeepAspectCentered;
AddChild(Icon);

View file

@ -30,6 +30,7 @@ namespace Content.Client.Examine
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly VerbSystem _verbSystem = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
public const string StyleClassEntityTooltip = "entity-tooltip";
@ -332,7 +333,7 @@ namespace Content.Client.Examine
if (!examine.ShowOnExamineTooltip)
continue;
var button = new ExamineButton(examine);
var button = new ExamineButton(examine, _sprite);
if (examine.HoverVerb)
{

View file

@ -113,18 +113,18 @@ namespace Content.Client.Gameplay
return first.IsValid() ? first : null;
}
public IEnumerable<EntityUid> GetClickableEntities(EntityCoordinates coordinates)
public IEnumerable<EntityUid> GetClickableEntities(EntityCoordinates coordinates, bool excludeFaded = true)
{
var transformSystem = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
return GetClickableEntities(transformSystem.ToMapCoordinates(coordinates));
return GetClickableEntities(transformSystem.ToMapCoordinates(coordinates), excludeFaded);
}
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates)
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates, bool excludeFaded = true)
{
return GetClickableEntities(coordinates, _eyeManager.CurrentEye);
return GetClickableEntities(coordinates, _eyeManager.CurrentEye, excludeFaded);
}
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates, IEye? eye)
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates, IEye? eye, bool excludeFaded = true)
{
/*
* TODO:
@ -147,7 +147,7 @@ namespace Content.Client.Gameplay
foreach (var entity in entities)
{
if (clickQuery.TryGetComponent(entity.Uid, out var component) &&
clickables.CheckClick((entity.Uid, component, entity.Component, entity.Transform), coordinates.Position, eye, out var drawDepthClicked, out var renderOrder, out var bottom))
clickables.CheckClick((entity.Uid, component, entity.Component, entity.Transform), coordinates.Position, eye, excludeFaded, out var drawDepthClicked, out var renderOrder, out var bottom))
{
foundEntities.Add((entity.Uid, drawDepthClicked, renderOrder, bottom));
}

View file

@ -85,7 +85,7 @@ public sealed class RotatingLightSystem : SharedRotatingLightSystem
if (!_animations.HasRunningAnimation(uid, player, AnimKey))
{
_animations.Play(uid, player, GetAnimation(comp.Speed), AnimKey);
_animations.Play((uid, player), GetAnimation(comp.Speed), AnimKey);
}
}
}

View file

@ -2,7 +2,6 @@ using Content.Shared.Light;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Animations;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Random;
@ -53,13 +52,14 @@ public sealed class PoweredLightVisualizerSystem : VisualizerSystem<PoweredLight
/// </summary>
private void OnAnimationCompleted(EntityUid uid, PoweredLightVisualsComponent comp, AnimationCompletedEvent args)
{
if (!TryComp<AnimationPlayerComponent>(uid, out var animationPlayer))
return;
if (args.Key != PoweredLightVisualsComponent.BlinkingAnimationKey)
return;
if(!comp.IsBlinking)
return;
AnimationSystem.Play(uid, Comp<AnimationPlayerComponent>(uid), BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey);
AnimationSystem.Play((uid, animationPlayer), BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey);
}
/// <summary>
@ -76,7 +76,7 @@ public sealed class PoweredLightVisualizerSystem : VisualizerSystem<PoweredLight
var animationPlayer = EnsureComp<AnimationPlayerComponent>(uid);
if (shouldBeBlinking)
{
AnimationSystem.Play(uid, animationPlayer, BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey);
AnimationSystem.Play((uid, animationPlayer), BlinkingAnimation(comp), PoweredLightVisualsComponent.BlinkingAnimationKey);
}
else if (AnimationSystem.HasRunningAnimation(uid, animationPlayer, PoweredLightVisualsComponent.BlinkingAnimationKey))
{

View file

@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Numerics;
using Content.Client.Administration.Managers;
using Content.Client.ContextMenu.UI;
@ -149,7 +149,7 @@ public sealed class MappingState : GameplayStateBase
{
Deselect();
var coords = args.Coordinates.ToMap(_entityManager, _transform);
var coords = _transform.ToMapCoordinates(args.Coordinates);
if (_verbs.TryGetEntityMenuEntities(coords, out var entities))
_entityMenuController.OpenRootMenu(entities);

View file

@ -1,7 +1,6 @@
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Robust.Client.GameObjects;
using Robust.Shared.Timing;
namespace Content.Client.Movement.Systems;
@ -10,8 +9,6 @@ namespace Content.Client.Movement.Systems;
/// </summary>
public sealed class ClientSpriteMovementSystem : SharedSpriteMovementSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
private EntityQuery<SpriteComponent> _spriteQuery;
public override void Initialize()

View file

@ -1,8 +1,6 @@
using System.Numerics;
using Content.Client.Movement.Components;
using Content.Shared.Camera;
using Content.Shared.Inventory;
using Content.Shared.Movement.Systems;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Shared.Map;
@ -16,8 +14,6 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedContentEyeSystem _contentEye = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IClyde _clyde = default!;
// This value is here to make sure the user doesn't have to move their mouse
@ -42,7 +38,7 @@ public sealed partial class EyeCursorOffsetSystem : EntitySystem
public Vector2? OffsetAfterMouse(EntityUid uid, EyeCursorOffsetComponent? component)
{
var localPlayer = _player.LocalPlayer?.ControlledEntity;
var localPlayer = _player.LocalEntity;
var mousePos = _inputManager.MouseScreenPosition;
var screenSize = _clyde.MainWindow.Size;
var minValue = MathF.Min(screenSize.X / 2, screenSize.Y / 2) * _edgeOffset;

View file

@ -49,13 +49,17 @@ public sealed class JetpackSystem : SharedJetpackSystem
// TODO: Please don't copy-paste this I beg
// make a generic particle emitter system / actual particles instead.
var query = EntityQueryEnumerator<ActiveJetpackComponent>();
var query = EntityQueryEnumerator<ActiveJetpackComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var comp))
while (query.MoveNext(out var uid, out var comp, out var xform))
{
if (_timing.CurTime < comp.TargetTime)
continue;
if (_transform.InRange(xform.Coordinates, comp.LastCoordinates, comp.MaxDistance))
{
if (_timing.CurTime < comp.TargetTime)
continue;
}
comp.LastCoordinates = _transform.GetMoverCoordinates(xform.Coordinates);
comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(comp.EffectCooldown);
CreateParticles(uid);

View file

@ -0,0 +1,42 @@
using System.Numerics;
using Content.Shared.CCVar;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Robust.Client.Player;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
namespace Content.Client.Movement.Systems;
public sealed class MobCollisionSystem : SharedMobCollisionSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _player = default!;
public override void Update(float frameTime)
{
if (!CfgManager.GetCVar(CCVars.MovementMobPushing))
return;
if (_timing.IsFirstTimePredicted)
{
var player = _player.LocalEntity;
if (MobQuery.TryComp(player, out var comp) && PhysicsQuery.TryComp(player, out var physics))
{
HandleCollisions((player.Value, comp, physics), frameTime);
}
}
base.Update(frameTime);
}
protected override void RaiseCollisionEvent(EntityUid uid, Vector2 direction, float speedMod)
{
RaisePredictiveEvent(new MobCollisionMessage()
{
Direction = direction,
SpeedModifier = speedMod,
});
}
}

View file

@ -62,16 +62,16 @@ public sealed class MoverController : SharedMoverController
private void OnRelayPlayerAttached(Entity<RelayInputMoverComponent> entity, ref LocalPlayerAttachedEvent args)
{
Physics.UpdateIsPredicted(entity.Owner);
Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
PhysicsSystem.UpdateIsPredicted(entity.Owner);
PhysicsSystem.UpdateIsPredicted(entity.Comp.RelayEntity);
if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
}
private void OnRelayPlayerDetached(Entity<RelayInputMoverComponent> entity, ref LocalPlayerDetachedEvent args)
{
Physics.UpdateIsPredicted(entity.Owner);
Physics.UpdateIsPredicted(entity.Comp.RelayEntity);
PhysicsSystem.UpdateIsPredicted(entity.Owner);
PhysicsSystem.UpdateIsPredicted(entity.Comp.RelayEntity);
if (MoverQuery.TryGetComponent(entity.Comp.RelayEntity, out var inputMover))
SetMoveInput((entity.Comp.RelayEntity, inputMover), MoveButtons.None);
}

View file

@ -1,47 +0,0 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:rcd="clr-namespace:Content.Client.RCD"
BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<!-- Note: The min size of the window just determine how close to the edge of the screen the center of the radial menu can be placed -->
<!-- The radial menu will try to open so that its center is located where the player's cursor is currently -->
<!-- Entry layer (shows main categories) -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="False">
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-walls-and-flooring'}" TargetLayer="WallsAndFlooring" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/walls_and_flooring.png"/>
</ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-windows-and-grilles'}" TargetLayer="WindowsAndGrilles" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/windows_and_grilles.png"/>
</ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-airlocks'}" TargetLayer="Airlocks" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/airlocks.png"/>
</ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-electrical'}" TargetLayer="Electrical" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/multicoil.png"/>
</ui:RadialMenuTextureButtonWithSector>
<ui:RadialMenuTextureButtonWithSector SetSize="64 64" ToolTip="{Loc 'rcd-component-lighting'}" TargetLayer="Lighting" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Radial/RCD/lighting.png"/>
</ui:RadialMenuTextureButtonWithSector>
</ui:RadialContainer>
<!-- Walls and flooring -->
<ui:RadialContainer Name="WallsAndFlooring" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Windows and grilles -->
<ui:RadialContainer Name="WindowsAndGrilles" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Airlocks -->
<ui:RadialContainer Name="Airlocks" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Computer and machine frames -->
<ui:RadialContainer Name="Electrical" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
<!-- Lighting -->
<ui:RadialContainer Name="Lighting" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100"/>
</ui:RadialMenu>

View file

@ -1,172 +0,0 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Popups;
using Content.Shared.RCD;
using Content.Shared.RCD.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using System.Numerics;
namespace Content.Client.RCD;
[GenerateTypedNameReferences]
public sealed partial class RCDMenu : RadialMenu
{
[Dependency] private readonly EntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private SharedPopupSystem _popup;
private SpriteSystem _sprites;
public event Action<ProtoId<RCDPrototype>>? SendRCDSystemMessageAction;
private EntityUid _owner;
public RCDMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_popup = _entManager.System<SharedPopupSystem>();
_sprites = _entManager.System<SpriteSystem>();
OnChildAdded += AddRCDMenuButtonOnClickActions;
}
public void SetEntity(EntityUid uid)
{
_owner = uid;
Refresh();
}
public void Refresh()
{
// Find the main radial container
var main = FindControl<RadialContainer>("Main");
// Populate secondary radial containers
if (!_entManager.TryGetComponent<RCDComponent>(_owner, out var rcd))
return;
foreach (var protoId in rcd.AvailablePrototypes)
{
if (!_protoManager.TryIndex(protoId, out var proto))
continue;
if (proto.Mode == RcdMode.Invalid)
continue;
var parent = FindControl<RadialContainer>(proto.Category);
var tooltip = Loc.GetString(proto.SetName);
if ((proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject) &&
proto.Prototype != null && _protoManager.TryIndex(proto.Prototype, out var entProto, logError: false))
{
tooltip = Loc.GetString(entProto.Name);
}
tooltip = OopsConcat(char.ToUpper(tooltip[0]).ToString(), tooltip.Remove(0, 1));
var button = new RCDMenuButton()
{
SetSize = new Vector2(64f, 64f),
ToolTip = tooltip,
ProtoId = protoId,
};
if (proto.Sprite != null)
{
var tex = new TextureRect()
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Texture = _sprites.Frame0(proto.Sprite),
TextureScale = new Vector2(2f, 2f),
};
button.AddChild(tex);
}
parent.AddChild(button);
// Ensure that the button that transitions the menu to the associated category layer
// is visible in the main radial container (as these all start with Visible = false)
foreach (var child in main.Children)
{
if (child is not RadialMenuTextureButton castChild)
continue;
if (castChild.TargetLayer == proto.Category)
{
castChild.Visible = true;
break;
}
}
}
// Set up menu actions
foreach (var child in Children)
{
AddRCDMenuButtonOnClickActions(child);
}
}
private static string OopsConcat(string a, string b)
{
// This exists to prevent Roslyn being clever and compiling something that fails sandbox checks.
return a + b;
}
private void AddRCDMenuButtonOnClickActions(Control control)
{
var radialContainer = control as RadialContainer;
if (radialContainer == null)
return;
foreach (var child in radialContainer.Children)
{
var castChild = child as RCDMenuButton;
if (castChild == null)
continue;
castChild.OnButtonUp += _ =>
{
SendRCDSystemMessageAction?.Invoke(castChild.ProtoId);
if (_playerManager.LocalSession?.AttachedEntity != null &&
_protoManager.TryIndex(castChild.ProtoId, out var proto))
{
var msg = Loc.GetString("rcd-component-change-mode", ("mode", Loc.GetString(proto.SetName)));
if (proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject)
{
var name = Loc.GetString(proto.SetName);
if (proto.Prototype != null &&
_protoManager.TryIndex(proto.Prototype, out var entProto, logError: false))
name = entProto.Name;
msg = Loc.GetString("rcd-component-change-build-mode", ("name", name));
}
// Popup message
_popup.PopupClient(msg, _owner, _playerManager.LocalSession.AttachedEntity);
}
Close();
};
}
}
}
public sealed class RCDMenuButton : RadialMenuTextureButtonWithSector
{
public ProtoId<RCDPrototype> ProtoId { get; set; }
}

View file

@ -1,20 +1,32 @@
using Content.Client.Popups;
using Content.Client.UserInterface.Controls;
using Content.Shared.RCD;
using Content.Shared.RCD.Components;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.RCD;
[UsedImplicitly]
public sealed class RCDMenuBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
private static readonly Dictionary<string, (string Tooltip, SpriteSpecifier Sprite)> PrototypesGroupingInfo
= new Dictionary<string, (string Tooltip, SpriteSpecifier Sprite)>
{
["WallsAndFlooring"] = ("rcd-component-walls-and-flooring", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/walls_and_flooring.png"))),
["WindowsAndGrilles"] = ("rcd-component-windows-and-grilles", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/windows_and_grilles.png"))),
["Airlocks"] = ("rcd-component-airlocks", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/airlocks.png"))),
["Electrical"] = ("rcd-component-electrical", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/multicoil.png"))),
["Lighting"] = ("rcd-component-lighting", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Radial/RCD/lighting.png"))),
};
private RCDMenu? _menu;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
private SimpleRadialMenu? _menu;
public RCDMenuBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
@ -25,19 +37,107 @@ public sealed class RCDMenuBoundUserInterface : BoundUserInterface
{
base.Open();
_menu = this.CreateWindow<RCDMenu>();
_menu.SetEntity(Owner);
_menu.SendRCDSystemMessageAction += SendRCDSystemMessage;
if (!EntMan.TryGetComponent<RCDComponent>(Owner, out var rcd))
return;
// Open the menu, centered on the mouse
var vpSize = _displayManager.ScreenSize;
_menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize);
_menu = this.CreateWindow<SimpleRadialMenu>();
_menu.Track(Owner);
var models = ConvertToButtons(rcd.AvailablePrototypes);
_menu.SetButtons(models);
_menu.OpenOverMouseScreenPosition();
}
public void SendRCDSystemMessage(ProtoId<RCDPrototype> protoId)
private IEnumerable<RadialMenuNestedLayerOption> ConvertToButtons(HashSet<ProtoId<RCDPrototype>> prototypes)
{
Dictionary<string, List<RadialMenuActionOption>> buttonsByCategory = new();
foreach (var protoId in prototypes)
{
var prototype = _prototypeManager.Index(protoId);
if (!PrototypesGroupingInfo.TryGetValue(prototype.Category, out var groupInfo))
continue;
if (!buttonsByCategory.TryGetValue(prototype.Category, out var list))
{
list = new List<RadialMenuActionOption>();
buttonsByCategory.Add(prototype.Category, list);
}
var actionOption = new RadialMenuActionOption<RCDPrototype>(HandleMenuOptionClick, prototype)
{
Sprite = prototype.Sprite,
ToolTip = GetTooltip(prototype)
};
list.Add(actionOption);
}
var models = new RadialMenuNestedLayerOption[buttonsByCategory.Count];
var i = 0;
foreach (var (key, list) in buttonsByCategory)
{
var groupInfo = PrototypesGroupingInfo[key];
models[i] = new RadialMenuNestedLayerOption(list)
{
Sprite = groupInfo.Sprite,
ToolTip = Loc.GetString(groupInfo.Tooltip)
};
i++;
}
return models;
}
private void HandleMenuOptionClick(RCDPrototype proto)
{
// A predicted message cannot be used here as the RCD UI is closed immediately
// after this message is sent, which will stop the server from receiving it
SendMessage(new RCDSystemMessage(protoId));
SendMessage(new RCDSystemMessage(proto.ID));
if (_playerManager.LocalSession?.AttachedEntity == null)
return;
var msg = Loc.GetString("rcd-component-change-mode", ("mode", Loc.GetString(proto.SetName)));
if (proto.Mode is RcdMode.ConstructTile or RcdMode.ConstructObject)
{
var name = Loc.GetString(proto.SetName);
if (proto.Prototype != null &&
_prototypeManager.TryIndex(proto.Prototype, out var entProto, logError: false))
name = entProto.Name;
msg = Loc.GetString("rcd-component-change-build-mode", ("name", name));
}
// Popup message
var popup = EntMan.System<PopupSystem>();
popup.PopupClient(msg, Owner, _playerManager.LocalSession.AttachedEntity);
}
private string GetTooltip(RCDPrototype proto)
{
string tooltip;
if (proto.Mode is RcdMode.ConstructTile or RcdMode.ConstructObject
&& proto.Prototype != null
&& _prototypeManager.TryIndex(proto.Prototype, out var entProto, logError: false))
{
tooltip = Loc.GetString(entProto.Name);
}
else
{
tooltip = Loc.GetString(proto.SetName);
}
tooltip = OopsConcat(char.ToUpper(tooltip[0]).ToString(), tooltip.Remove(0, 1));
return tooltip;
}
private static string OopsConcat(string a, string b)
{
// This exists to prevent Roslyn being clever and compiling something that fails sandbox checks.
return a + b;
}
}

View file

@ -52,7 +52,7 @@ public sealed class RotationVisualizerSystem : SharedRotationVisualsSystem
// Stop the current rotate animation and then start a new one
if (_animation.HasRunningAnimation(animationComp, animationKey))
{
_animation.Stop(animationComp, animationKey);
_animation.Stop((uid, animationComp), animationKey);
}
var animation = new Animation

View file

@ -1,28 +1,46 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Silicons.StationAi;
using Robust.Client.UserInterface;
namespace Content.Client.Silicons.StationAi;
public sealed class StationAiBoundUserInterface : BoundUserInterface
public sealed class StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
{
private StationAiMenu? _menu;
public StationAiBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
private SimpleRadialMenu? _menu;
protected override void Open()
{
base.Open();
_menu = this.CreateWindow<StationAiMenu>();
_menu.Track(Owner);
_menu.OnAiRadial += args =>
var ev = new GetStationAiRadialEvent();
EntMan.EventBus.RaiseLocalEvent(Owner, ref ev);
_menu = this.CreateWindow<SimpleRadialMenu>();
_menu.Track(Owner);
var buttonModels = ConvertToButtons(ev.Actions);
_menu.SetButtons(buttonModels);
_menu.Open();
}
private IEnumerable<RadialMenuActionOption> ConvertToButtons(IReadOnlyList<StationAiRadial> actions)
{
var models = new RadialMenuActionOption[actions.Count];
for (int i = 0; i < actions.Count; i++)
{
SendPredictedMessage(new StationAiRadialMessage()
var action = actions[i];
models[i] = new RadialMenuActionOption<BaseStationAiAction>(HandleRadialMenuClick, action.Event)
{
Event = args,
});
};
Sprite = action.Sprite,
ToolTip = action.Tooltip
};
}
return models;
}
private void HandleRadialMenuClick(BaseStationAiAction p)
{
SendPredictedMessage(new StationAiRadialMessage { Event = p });
}
}

View file

@ -1,13 +0,0 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<!-- Main -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" InitialRadius="100" ReserveSpaceForHiddenChildren="False">
</ui:RadialContainer>
</ui:RadialMenu>

View file

@ -1,126 +0,0 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Silicons.StationAi;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
namespace Content.Client.Silicons.StationAi;
[GenerateTypedNameReferences]
public sealed partial class StationAiMenu : RadialMenu
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
public event Action<BaseStationAiAction>? OnAiRadial;
private EntityUid _tracked;
public StationAiMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
}
public void Track(EntityUid owner)
{
_tracked = owner;
if (!_entManager.EntityExists(_tracked))
{
Close();
return;
}
BuildButtons();
UpdatePosition();
}
private void BuildButtons()
{
var ev = new GetStationAiRadialEvent();
_entManager.EventBus.RaiseLocalEvent(_tracked, ref ev);
var main = FindControl<RadialContainer>("Main");
main.DisposeAllChildren();
var sprites = _entManager.System<SpriteSystem>();
foreach (var action in ev.Actions)
{
// TODO: This radial boilerplate is quite annoying
var button = new StationAiMenuButton(action.Event)
{
SetSize = new Vector2(64f, 64f),
ToolTip = action.Tooltip != null ? Loc.GetString(action.Tooltip) : null,
};
if (action.Sprite != null)
{
var texture = sprites.Frame0(action.Sprite);
var scale = Vector2.One;
if (texture.Width <= 32)
{
scale *= 2;
}
var tex = new TextureRect
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Texture = texture,
TextureScale = scale,
};
button.AddChild(tex);
}
button.OnPressed += args =>
{
OnAiRadial?.Invoke(action.Event);
Close();
};
main.AddChild(button);
}
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdatePosition();
}
private void UpdatePosition()
{
if (!_entManager.TryGetComponent(_tracked, out TransformComponent? xform))
{
Close();
return;
}
if (!xform.Coordinates.IsValid(_entManager))
{
Close();
return;
}
var coords = _entManager.System<SpriteSystem>().GetSpriteScreenCoordinates((_tracked, null, xform));
if (!coords.IsValid)
{
Close();
return;
}
OpenScreenAt(coords.Position, _clyde);
}
}
public sealed class StationAiMenuButton(BaseStationAiAction action) : RadialMenuTextureButtonWithSector
{
public BaseStationAiAction Action = action;
}

View file

@ -1,8 +1,14 @@
using System.Numerics;
using Content.Client.Gameplay;
using Content.Shared.Sprite;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.State;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface;
using Robust.Shared.Map;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Physics;
namespace Content.Client.Sprite;
@ -17,6 +23,9 @@ public sealed class SpriteFadeSystem : EntitySystem
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
private readonly HashSet<FadingSpriteComponent> _comps = new();
@ -24,6 +33,11 @@ public sealed class SpriteFadeSystem : EntitySystem
private EntityQuery<SpriteFadeComponent> _fadeQuery;
private EntityQuery<FadingSpriteComponent> _fadingQuery;
/// <summary>
/// Radius of the mouse point for the intersection test
/// </summary>
private static Vector2 MouseRadius = new Vector2(10f * float.Epsilon, 10f * float.Epsilon);
private const float TargetAlpha = 0.4f;
private const float ChangeRate = 1f;
@ -46,46 +60,82 @@ public sealed class SpriteFadeSystem : EntitySystem
sprite.Color = sprite.Color.WithAlpha(component.OriginalAlpha);
}
public override void FrameUpdate(float frameTime)
/// <summary>
/// Adds sprites to the fade set, and brings their alpha downwards
/// </summary>
private void FadeIn(float change)
{
base.FrameUpdate(frameTime);
var player = _playerManager.LocalEntity;
var change = ChangeRate * frameTime;
// ExcludeBoundingBox is set if we don't want to fade this sprite within the collision bounding boxes for the given POI
var pointsOfInterest = new List<(MapCoordinates Point, bool ExcludeBoundingBox)>();
if (TryComp(player, out TransformComponent? playerXform) &&
_stateManager.CurrentState is GameplayState state &&
_spriteQuery.TryGetComponent(player, out var playerSprite))
if (_uiManager.CurrentlyHovered is IViewportControl vp
&& _inputManager.MouseScreenPosition.IsValid)
{
var mapPos = _transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: playerXform);
pointsOfInterest.Add((vp.PixelToMap(_inputManager.MouseScreenPosition.Position), true));
}
// Also want to handle large entities even if they may not be clickable.
foreach (var ent in state.GetClickableEntities(mapPos))
if (TryComp(player, out TransformComponent? playerXform))
{
pointsOfInterest.Add((_transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: playerXform), false));
}
if (_stateManager.CurrentState is GameplayState state && _spriteQuery.TryGetComponent(player, out var playerSprite))
{
foreach (var (mapPos, excludeBB) in pointsOfInterest)
{
if (ent == player ||
!_fadeQuery.HasComponent(ent) ||
!_spriteQuery.TryGetComponent(ent, out var sprite) ||
sprite.DrawDepth < playerSprite.DrawDepth)
// Also want to handle large entities even if they may not be clickable.
foreach (var ent in state.GetClickableEntities(mapPos, excludeFaded: false))
{
continue;
}
if (ent == player ||
!_fadeQuery.HasComponent(ent) ||
!_spriteQuery.TryGetComponent(ent, out var sprite) ||
sprite.DrawDepth < playerSprite.DrawDepth)
{
continue;
}
if (!_fadingQuery.TryComp(ent, out var fading))
{
fading = AddComp<FadingSpriteComponent>(ent);
fading.OriginalAlpha = sprite.Color.A;
}
if (excludeBB)
{
var test = new Box2Rotated(mapPos.Position - MouseRadius, mapPos.Position + MouseRadius);
var collided = false;
foreach (var fixture in _physics.GetCollidingEntities(mapPos.MapId, test))
{
if (fixture.Owner == ent)
{
collided = true;
break;
}
}
if (collided)
{
break;
}
}
_comps.Add(fading);
var newColor = Math.Max(sprite.Color.A - change, TargetAlpha);
if (!_fadingQuery.TryComp(ent, out var fading))
{
fading = AddComp<FadingSpriteComponent>(ent);
fading.OriginalAlpha = sprite.Color.A;
}
if (!sprite.Color.A.Equals(newColor))
{
sprite.Color = sprite.Color.WithAlpha(newColor);
_comps.Add(fading);
var newColor = Math.Max(sprite.Color.A - change, TargetAlpha);
if (!sprite.Color.A.Equals(newColor))
{
sprite.Color = sprite.Color.WithAlpha(newColor);
}
}
}
}
}
/// <summary>
/// Bring sprites back up to their original alpha if they aren't in the fade set, and removes their fade component when done
/// </summary>
private void FadeOut(float change)
{
var query = AllEntityQuery<FadingSpriteComponent>();
while (query.MoveNext(out var uid, out var comp))
{
@ -106,6 +156,16 @@ public sealed class SpriteFadeSystem : EntitySystem
RemCompDeferred<FadingSpriteComponent>(uid);
}
}
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
var change = ChangeRate * frameTime;
FadeIn(change);
FadeOut(change);
_comps.Clear();
}

View file

@ -0,0 +1,5 @@
using Content.Shared.Temperature.Systems;
namespace Content.Client.Temperature.Systems;
public sealed partial class EntityHeaterSystem : SharedEntityHeaterSystem;

View file

@ -0,0 +1,121 @@
using Content.Client.Power;
using Content.Shared.Turrets;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
namespace Content.Client.Turrets;
public sealed partial class DeployableTurretSystem : SharedDeployableTurretSystem
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DeployableTurretComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<DeployableTurretComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeLocalEvent<DeployableTurretComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
private void OnComponentInit(Entity<DeployableTurretComponent> ent, ref ComponentInit args)
{
ent.Comp.DeploymentAnimation = new Animation
{
Length = TimeSpan.FromSeconds(ent.Comp.DeploymentLength),
AnimationTracks = {
new AnimationTrackSpriteFlick() {
LayerKey = DeployableTurretVisuals.Turret,
KeyFrames = {new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.DeployingState, 0f)}
},
}
};
ent.Comp.RetractionAnimation = new Animation
{
Length = TimeSpan.FromSeconds(ent.Comp.RetractionLength),
AnimationTracks = {
new AnimationTrackSpriteFlick() {
LayerKey = DeployableTurretVisuals.Turret,
KeyFrames = {new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.RetractingState, 0f)}
},
}
};
}
private void OnAnimationCompleted(Entity<DeployableTurretComponent> ent, ref AnimationCompletedEvent args)
{
if (args.Key != DeployableTurretComponent.AnimationKey)
return;
if (!TryComp<SpriteComponent>(ent, out var sprite))
return;
if (!_appearance.TryGetData<DeployableTurretState>(ent, DeployableTurretVisuals.Turret, out var state))
state = ent.Comp.VisualState;
// Convert to terminal state
var targetState = state & DeployableTurretState.Deployed;
UpdateVisuals(ent, targetState, sprite, args.AnimationPlayer);
}
private void OnAppearanceChange(Entity<DeployableTurretComponent> ent, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (!TryComp<AnimationPlayerComponent>(ent, out var animPlayer))
return;
if (!_appearance.TryGetData<DeployableTurretState>(ent, DeployableTurretVisuals.Turret, out var state, args.Component))
state = DeployableTurretState.Retracted;
UpdateVisuals(ent, state, args.Sprite, animPlayer);
}
private void UpdateVisuals(Entity<DeployableTurretComponent> ent, DeployableTurretState state, SpriteComponent sprite, AnimationPlayerComponent? animPlayer = null)
{
if (!Resolve(ent, ref animPlayer))
return;
if (_animation.HasRunningAnimation(ent, animPlayer, DeployableTurretComponent.AnimationKey))
return;
if (state == ent.Comp.VisualState)
return;
var targetState = state & DeployableTurretState.Deployed;
var destinationState = ent.Comp.VisualState & DeployableTurretState.Deployed;
if (targetState != destinationState)
targetState = targetState | DeployableTurretState.Retracting;
ent.Comp.VisualState = state;
// Toggle layer visibility
sprite.LayerSetVisible(DeployableTurretVisuals.Weapon, (targetState & DeployableTurretState.Deployed) > 0);
sprite.LayerSetVisible(PowerDeviceVisualLayers.Powered, HasAmmo(ent) && targetState == DeployableTurretState.Retracted);
// Change the visual state
switch (targetState)
{
case DeployableTurretState.Deploying:
_animation.Play((ent, animPlayer), (Animation)ent.Comp.DeploymentAnimation, DeployableTurretComponent.AnimationKey);
break;
case DeployableTurretState.Retracting:
_animation.Play((ent, animPlayer), (Animation)ent.Comp.RetractionAnimation, DeployableTurretComponent.AnimationKey);
break;
case DeployableTurretState.Deployed:
sprite.LayerSetState(DeployableTurretVisuals.Turret, ent.Comp.DeployedState);
break;
case DeployableTurretState.Retracted:
sprite.LayerSetState(DeployableTurretVisuals.Turret, ent.Comp.RetractedState);
break;
}
}
}

View file

@ -1,10 +1,10 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using System.Linq;
using System.Numerics;
using Content.Shared.Input;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Input;
namespace Content.Client.UserInterface.Controls;
@ -143,11 +143,8 @@ public class RadialMenu : BaseWindow
return children.First(x => x.Visible);
}
public bool TryToMoveToNewLayer(string newLayer)
public bool TryToMoveToNewLayer(Control newLayer)
{
if (newLayer == string.Empty)
return false;
var currentLayer = GetCurrentActiveLayer();
if (currentLayer == null)
@ -161,7 +158,7 @@ public class RadialMenu : BaseWindow
continue;
// Hide layers which are not of interest
if (result == true || child.Name != newLayer)
if (result == true || child != newLayer)
{
child.Visible = false;
}
@ -186,6 +183,19 @@ public class RadialMenu : BaseWindow
return result;
}
public bool TryToMoveToNewLayer(string targetLayerControlName)
{
foreach (var child in Children)
{
if (child.Name == targetLayerControlName && child is RadialContainer)
{
return TryToMoveToNewLayer(child);
}
}
return false;
}
public void ReturnToPreviousLayer()
{
// Close the menu if the traversal path is empty
@ -296,9 +306,15 @@ public sealed class RadialMenuOuterAreaButton : RadialMenuTextureButtonBase
public class RadialMenuTextureButton : RadialMenuTextureButtonBase
{
/// <summary>
/// Upon clicking this button the radial menu will be moved to the named layer
/// Upon clicking this button the radial menu will be moved to the layer of this control.
/// </summary>
public string TargetLayer { get; set; } = string.Empty;
public Control? TargetLayer { get; set; }
/// <summary>
/// Other way to set navigation to other container, as <see cref="TargetLayer"/>,
/// but using <see cref="Control.Name"/> property of target <see cref="RadialContainer"/>.
/// </summary>
public string? TargetLayerControlName { get; set; }
/// <summary>
/// A simple texture button that can move the user to a different layer within a radial menu
@ -311,7 +327,7 @@ public class RadialMenuTextureButton : RadialMenuTextureButtonBase
private void OnClicked(ButtonEventArgs args)
{
if (TargetLayer == string.Empty)
if (TargetLayer == null && TargetLayerControlName == null)
return;
var parent = FindParentMultiLayerContainer(this);
@ -319,7 +335,14 @@ public class RadialMenuTextureButton : RadialMenuTextureButtonBase
if (parent == null)
return;
parent.TryToMoveToNewLayer(TargetLayer);
if (TargetLayer != null)
{
parent.TryToMoveToNewLayer(TargetLayer);
}
else
{
parent.TryToMoveToNewLayer(TargetLayerControlName!);
}
}
private RadialMenu? FindParentMultiLayerContainer(Control control)
@ -387,7 +410,7 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia
private Color _hoverBorderColorSrgb = Color.ToSrgb(new Color(87, 91, 127, 128));
/// <summary>
/// Marker, that control should render border of segment. Is false by default.
/// Marker, that controls if border of segment should be rendered. Is false by default.
/// </summary>
/// <remarks>
/// By default color of border is same as color of background. Use <see cref="BorderColor"/>
@ -400,13 +423,6 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia
/// </summary>
public bool DrawBackground { get; set; } = true;
/// <summary>
/// Marker, that control should render separator lines.
/// Separator lines are used to visually separate sector of radial menu items.
/// Is true by default
/// </summary>
public bool DrawSeparators { get; set; } = true;
/// <summary>
/// Color of background in non-hovered state. Accepts RGB color, works with sRGB for DrawPrimitive internally.
/// </summary>
@ -520,7 +536,7 @@ public class RadialMenuTextureButtonWithSector : RadialMenuTextureButton, IRadia
DrawAnnulusSector(handle, containerCenter, _innerRadius * UIScale, _outerRadius * UIScale, angleFrom, angleTo, borderColor, false);
}
if (!_isWholeCircle && DrawSeparators)
if (!_isWholeCircle && DrawBorder)
{
DrawSeparatorLines(handle, containerCenter, _innerRadius * UIScale, _outerRadius * UIScale, angleFrom, angleTo, SeparatorColor);
}

View file

@ -0,0 +1,8 @@
<ui:SimpleRadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
</ui:SimpleRadialMenu>

View file

@ -0,0 +1,279 @@
using Robust.Client.UserInterface;
using System.Numerics;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Shared.Utility;
using Robust.Client.GameObjects;
using Robust.Shared.Timing;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Input;
namespace Content.Client.UserInterface.Controls;
[GenerateTypedNameReferences]
public partial class SimpleRadialMenu : RadialMenu
{
private EntityUid? _attachMenuToEntity;
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
public SimpleRadialMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
}
public void Track(EntityUid owner)
{
_attachMenuToEntity = owner;
}
public void SetButtons(IEnumerable<RadialMenuOption> models, SimpleRadialMenuSettings? settings = null)
{
ClearExistingChildrenRadialButtons();
var sprites = _entManager.System<SpriteSystem>();
Fill(models, sprites, Children, settings ?? new SimpleRadialMenuSettings());
}
public void OpenOverMouseScreenPosition()
{
var vpSize = _clyde.ScreenSize;
OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize);
}
private void Fill(
IEnumerable<RadialMenuOption> models,
SpriteSystem sprites,
ICollection<Control> rootControlChildren,
SimpleRadialMenuSettings settings
)
{
var rootContainer = new RadialContainer
{
HorizontalExpand = true,
VerticalExpand = true,
InitialRadius = settings.DefaultContainerRadius,
ReserveSpaceForHiddenChildren = false,
Visible = true
};
rootControlChildren.Add(rootContainer);
foreach (var model in models)
{
if (model is RadialMenuNestedLayerOption nestedMenuModel)
{
var linkButton = RecursiveContainerExtraction(sprites, rootControlChildren, nestedMenuModel, settings);
linkButton.Visible = true;
rootContainer.AddChild(linkButton);
}
else
{
var rootButtons = ConvertToButton(model, sprites, settings, false);
rootContainer.AddChild(rootButtons);
}
}
}
private RadialMenuTextureButton RecursiveContainerExtraction(
SpriteSystem sprites,
ICollection<Control> rootControlChildren,
RadialMenuNestedLayerOption model,
SimpleRadialMenuSettings settings
)
{
var container = new RadialContainer
{
HorizontalExpand = true,
VerticalExpand = true,
InitialRadius = model.ContainerRadius!.Value,
ReserveSpaceForHiddenChildren = false,
Visible = false
};
foreach (var nested in model.Nested)
{
if (nested is RadialMenuNestedLayerOption nestedMenuModel)
{
var linkButton = RecursiveContainerExtraction(sprites, rootControlChildren, nestedMenuModel, settings);
container.AddChild(linkButton);
}
else
{
var button = ConvertToButton(nested, sprites, settings, false);
container.AddChild(button);
}
}
rootControlChildren.Add(container);
var thisLayerLinkButton = ConvertToButton(model, sprites, settings, true);
thisLayerLinkButton.TargetLayer = container;
return thisLayerLinkButton;
}
private RadialMenuTextureButton ConvertToButton(
RadialMenuOption model,
SpriteSystem sprites,
SimpleRadialMenuSettings settings,
bool haveNested
)
{
var button = settings.UseSectors
? ConvertToButtonWithSector(model, settings)
: new RadialMenuTextureButton();
button.SetSize = new Vector2(64f, 64f);
button.ToolTip = model.ToolTip;
if (model.Sprite != null)
{
var scale = Vector2.One;
var texture = sprites.Frame0(model.Sprite);
if (texture.Width <= 32)
{
scale *= 2;
}
button.TextureNormal = texture;
button.Scale = scale;
}
if (model is RadialMenuActionOption actionOption)
{
button.OnPressed += _ =>
{
actionOption.OnPressed?.Invoke();
if(!haveNested)
Close();
};
}
return button;
}
private static RadialMenuTextureButtonWithSector ConvertToButtonWithSector(RadialMenuOption model, SimpleRadialMenuSettings settings)
{
var button = new RadialMenuTextureButtonWithSector
{
DrawBorder = settings.DisplayBorders,
DrawBackground = !settings.NoBackground
};
if (model.BackgroundColor.HasValue)
{
button.BackgroundColor = model.BackgroundColor.Value;
}
if (model.HoverBackgroundColor.HasValue)
{
button.HoverBackgroundColor = model.HoverBackgroundColor.Value;
}
return button;
}
private void ClearExistingChildrenRadialButtons()
{
var toRemove = new List<Control>(ChildCount);
foreach (var child in Children)
{
if (child != ContextualButton && child != MenuOuterAreaButton)
{
toRemove.Add(child);
}
}
foreach (var control in toRemove)
{
Children.Remove(control);
}
}
#region target entity tracking
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_attachMenuToEntity != null)
{
UpdatePosition();
}
}
private void UpdatePosition()
{
if (!_entManager.TryGetComponent(_attachMenuToEntity, out TransformComponent? xform))
{
Close();
return;
}
if (!xform.Coordinates.IsValid(_entManager))
{
Close();
return;
}
var coords = _entManager.System<SpriteSystem>().GetSpriteScreenCoordinates((_attachMenuToEntity.Value, null, xform));
if (!coords.IsValid)
{
Close();
return;
}
OpenScreenAt(coords.Position, _clyde);
}
#endregion
}
public abstract class RadialMenuOption
{
public string? ToolTip { get; init; }
public SpriteSpecifier? Sprite { get; init; }
public Color? BackgroundColor { get; set; }
public Color? HoverBackgroundColor { get; set; }
}
public class RadialMenuActionOption(Action onPressed) : RadialMenuOption
{
public Action OnPressed { get; } = onPressed;
}
public class RadialMenuActionOption<T>(Action<T> onPressed, T data)
: RadialMenuActionOption(onPressed: () => onPressed(data));
public class RadialMenuNestedLayerOption(IReadOnlyCollection<RadialMenuOption> nested, float containerRadius = 100)
: RadialMenuOption
{
public float? ContainerRadius { get; } = containerRadius;
public IReadOnlyCollection<RadialMenuOption> Nested { get; } = nested;
}
public class SimpleRadialMenuSettings
{
/// <summary>
/// Default container draw radius. Is going to be further affected by per sector increment.
/// </summary>
public int DefaultContainerRadius = 100;
/// <summary>
/// Marker, if sector-buttons should be used.
/// </summary>
public bool UseSectors = true;
/// <summary>
/// Marker, if border of buttons should be rendered. Can only be used when <see cref="UseSectors"/> = true.
/// </summary>
public bool DisplayBorders = true;
/// <summary>
/// Marker, if sector background should not be rendered. Can only be used when <see cref="UseSectors"/> = true.
/// </summary>
public bool NoBackground = false;
}

View file

@ -1,16 +1,17 @@
using Content.Client.Chat.UI;
using Content.Client.Gameplay;
using Content.Client.UserInterface.Controls;
using Content.Shared.Chat;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Input;
using Content.Shared.Speech;
using Content.Shared.Whitelist;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.UserInterface.Controllers;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input.Binding;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.UserInterface.Systems.Emotes;
@ -18,11 +19,19 @@ namespace Content.Client.UserInterface.Systems.Emotes;
public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayState>
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
private MenuButton? EmotesButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.EmotesButton;
private EmotesMenu? _menu;
private SimpleRadialMenu? _menu;
private static readonly Dictionary<EmoteCategory, (string Tooltip, SpriteSpecifier Sprite)> EmoteGroupingInfo
= new Dictionary<EmoteCategory, (string Tooltip, SpriteSpecifier Sprite)>
{
[EmoteCategory.General] = ("emote-menu-category-general", new SpriteSpecifier.Texture(new ResPath("/Textures/Clothing/Head/Soft/mimesoft.rsi/icon.png"))),
[EmoteCategory.Hands] = ("emote-menu-category-hands", new SpriteSpecifier.Texture(new ResPath("/Textures/Clothing/Hands/Gloves/latex.rsi/icon.png"))),
[EmoteCategory.Vocal] = ("emote-menu-category-vocal", new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/Emotes/vocal.png"))),
};
public void OnStateEntered(GameplayState state)
{
@ -42,10 +51,16 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
if (_menu == null)
{
// setup window
_menu = UIManager.CreateWindow<EmotesMenu>();
var prototypes = _prototypeManager.EnumeratePrototypes<EmotePrototype>();
var models = ConvertToButtons(prototypes);
_menu = new SimpleRadialMenu();
_menu.SetButtons(models);
_menu.Open();
_menu.OnClose += OnWindowClosed;
_menu.OnOpen += OnWindowOpen;
_menu.OnPlayEmote += OnPlayEmote;
if (EmotesButton != null)
EmotesButton.SetClickPressed(true);
@ -56,16 +71,13 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
}
else
{
// Open the menu, centered on the mouse
var vpSize = _displayManager.ScreenSize;
_menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize);
_menu.OpenOverMouseScreenPosition();
}
}
else
{
_menu.OnClose -= OnWindowClosed;
_menu.OnOpen -= OnWindowOpen;
_menu.OnPlayEmote -= OnPlayEmote;
if (EmotesButton != null)
EmotesButton.SetClickPressed(false);
@ -118,8 +130,62 @@ public sealed class EmotesUIController : UIController, IOnStateChanged<GameplayS
_menu = null;
}
private void OnPlayEmote(ProtoId<EmotePrototype> protoId)
private IEnumerable<RadialMenuOption> ConvertToButtons(IEnumerable<EmotePrototype> emotePrototypes)
{
_entityManager.RaisePredictiveEvent(new PlayEmoteMessage(protoId));
var whitelistSystem = EntitySystemManager.GetEntitySystem<EntityWhitelistSystem>();
var player = _playerManager.LocalSession?.AttachedEntity;
Dictionary<EmoteCategory, List<RadialMenuOption>> emotesByCategory = new();
foreach (var emote in emotePrototypes)
{
if(emote.Category == EmoteCategory.Invalid)
continue;
// only valid emotes that have ways to be triggered by chat and player have access / no restriction on
if (emote.Category == EmoteCategory.Invalid
|| emote.ChatTriggers.Count == 0
|| !(player.HasValue && whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value))
|| whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
continue;
if (!emote.Available
&& EntityManager.TryGetComponent<SpeechComponent>(player.Value, out var speech)
&& !speech.AllowedEmotes.Contains(emote.ID))
continue;
if (!emotesByCategory.TryGetValue(emote.Category, out var list))
{
list = new List<RadialMenuOption>();
emotesByCategory.Add(emote.Category, list);
}
var actionOption = new RadialMenuActionOption<EmotePrototype>(HandleRadialButtonClick, emote)
{
Sprite = emote.Icon,
ToolTip = Loc.GetString(emote.Name)
};
list.Add(actionOption);
}
var models = new RadialMenuOption[emotesByCategory.Count];
var i = 0;
foreach (var (key, list) in emotesByCategory)
{
var tuple = EmoteGroupingInfo[key];
models[i] = new RadialMenuNestedLayerOption(list)
{
Sprite = tuple.Sprite,
ToolTip = Loc.GetString(tuple.Tooltip)
};
i++;
}
return models;
}
private void HandleRadialButtonClick(EmotePrototype prototype)
{
_entityManager.RaisePredictiveEvent(new PlayEmoteMessage(prototype.ID));
}
}

View file

@ -42,6 +42,9 @@ public sealed class StorageWindow : BaseWindow
private ValueList<EntityUid> _contained = new();
private ValueList<EntityUid> _toRemove = new();
// Manually store this because you can't have a 0x0 GridContainer but we still need to add child controls for 1x1 containers.
private Vector2i _pieceGridSize;
private TextureButton? _backButton;
private bool _isDirty;
@ -408,11 +411,14 @@ public sealed class StorageWindow : BaseWindow
_contained.Clear();
_contained.AddRange(storageComp.Container.ContainedEntities.Reverse());
var width = boundingGrid.Width + 1;
var height = boundingGrid.Height + 1;
// Build the grid representation
if (_pieceGrid.Rows - 1 != boundingGrid.Height || _pieceGrid.Columns - 1 != boundingGrid.Width)
if (_pieceGrid.Rows != _pieceGridSize.Y || _pieceGrid.Columns != _pieceGridSize.X)
{
_pieceGrid.Rows = boundingGrid.Height + 1;
_pieceGrid.Columns = boundingGrid.Width + 1;
_pieceGrid.Rows = height;
_pieceGrid.Columns = width;
_controlGrid.Clear();
for (var y = boundingGrid.Bottom; y <= boundingGrid.Top; y++)
@ -430,6 +436,7 @@ public sealed class StorageWindow : BaseWindow
}
}
_pieceGridSize = new(width, height);
_toRemove.Clear();
// Remove entities no longer relevant / Update existing ones

View file

@ -16,6 +16,7 @@ using Robust.Client.State;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Verbs
@ -36,6 +37,8 @@ namespace Content.Client.Verbs
private float _lookupSize;
private static readonly ProtoId<TagPrototype> HideContextMenuTag = "HideContextMenu";
/// <summary>
/// These flags determine what entities the user can see on the context menu.
/// </summary>
@ -147,7 +150,7 @@ namespace Content.Client.Verbs
for (var i = entities.Count - 1; i >= 0; i--)
{
if (_tagSystem.HasTag(entities[i], "HideContextMenu"))
if (_tagSystem.HasTag(entities[i], HideContextMenuTag))
entities.RemoveSwap(i);
}

View file

@ -39,6 +39,7 @@ public static partial class PoolManager
(CVars.NetBufferSize.Name, "0"),
(CCVars.InteractionRateLimitCount.Name, "9999999"),
(CCVars.InteractionRateLimitPeriod.Name, "0.1"),
(CCVars.MovementMobPushing.Name, "false"),
};
public static async Task SetupCVars(RobustIntegrationTest.IntegrationInstance instance, PoolSettings settings)

View file

@ -12,35 +12,6 @@ namespace Content.IntegrationTests.Tests.Access
[TestOf(typeof(AccessReaderComponent))]
public sealed class AccessReaderTest
{
[Test]
public async Task TestProtoTags()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;
var protoManager = server.ResolveDependency<IPrototypeManager>();
var accessName = server.ResolveDependency<IComponentFactory>().GetComponentName(typeof(AccessReaderComponent));
await server.WaitAssertion(() =>
{
foreach (var ent in protoManager.EnumeratePrototypes<EntityPrototype>())
{
if (!ent.Components.TryGetComponent(accessName, out var access))
continue;
var reader = (AccessReaderComponent) access;
var allTags = reader.AccessLists.SelectMany(c => c).Union(reader.DenyTags);
foreach (var level in allTags)
{
Assert.That(protoManager.HasIndex<AccessLevelPrototype>(level), $"Invalid access level: {level} found on {ent}");
}
}
});
await pair.CleanReturnAsync();
}
[Test]
public async Task TestTags()
{

View file

@ -80,7 +80,7 @@ namespace Content.IntegrationTests.Tests
var pos = clientEntManager.System<SharedTransformSystem>().GetWorldPosition(clientEnt);
hit = clientEntManager.System<ClickableSystem>().CheckClick((clientEnt, null, sprite, null), new Vector2(clickPosX, clickPosY) + pos, eye, out _, out _, out _);
hit = clientEntManager.System<ClickableSystem>().CheckClick((clientEnt, null, sprite, null), new Vector2(clickPosX, clickPosY) + pos, eye, false, out _, out _, out _);
});
await server.WaitPost(() =>

View file

@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.Execution;
@ -52,7 +52,7 @@ public sealed class SuicideCommandTests
name: test version of the material reclaimer
components:
- type: MaterialReclaimer";
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
/// <summary>
/// Run the suicide command in the console
/// Should successfully kill the player and ghost them
@ -201,7 +201,7 @@ public sealed class SuicideCommandTests
mobStateComp = entManager.GetComponent<MobStateComponent>(player);
});
tagSystem.AddTag(player, "CannotSuicide");
tagSystem.AddTag(player, CannotSuicideTag);
// Check that running the suicide command kills the player
// and properly ghosts them without them being able to return to their body

View file

@ -1,5 +1,6 @@
using Content.IntegrationTests.Tests.Interaction;
using Content.Shared.Projectiles;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
namespace Content.IntegrationTests.Tests.Embedding;
@ -88,4 +89,84 @@ public sealed class EmbedTest : InteractionTest
AssertExists(projectile);
await AssertEntityLookup(EmbeddableProtoId);
}
/// <summary>
/// Throws two embeddable projectiles at a target, then deletes them
/// one at a time, making sure that they are tracked correctly and that
/// the <see cref="EmbeddedContainerComponent"/> is removed once all
/// projectiles are gone.
/// </summary>
[Test]
public async Task TestDeleteWhileEmbedded()
{
// Spawn the target we're going to throw at
await SpawnTarget(TargetProtoId);
// Give the player the embeddable to throw
var projectile1 = await PlaceInHands(EmbeddableProtoId);
Assert.That(TryComp<EmbeddableProjectileComponent>(projectile1, out var embedComp),
$"{EmbeddableProtoId} does not have EmbeddableProjectileComponent.");
// Make sure the projectile isn't already embedded into anything
Assert.That(embedComp.EmbeddedIntoUid, Is.Null,
$"Projectile already embedded into {SEntMan.ToPrettyString(embedComp.EmbeddedIntoUid)}.");
// Have the player throw the embeddable at the target
await ThrowItem();
// Give the player a second embeddable to throw
var projectile2 = await PlaceInHands(EmbeddableProtoId);
Assert.That(TryComp<EmbeddableProjectileComponent>(projectile1, out var embedComp2),
$"{EmbeddableProtoId} does not have EmbeddableProjectileComponent.");
// Wait a moment for the projectile to hit and embed
await RunSeconds(0.5f);
// Make sure the projectile is embedded into the target
Assert.That(embedComp.EmbeddedIntoUid, Is.EqualTo(ToServer(Target)),
"First projectile not embedded into target.");
Assert.That(TryComp<EmbeddedContainerComponent>(out var containerComp),
"Target was not given EmbeddedContainerComponent.");
Assert.That(containerComp.EmbeddedObjects, Does.Contain(ToServer(projectile1)),
"Target is not tracking the first projectile as embedded.");
Assert.That(containerComp.EmbeddedObjects, Has.Count.EqualTo(1),
"Target has unexpected EmbeddedObjects count.");
// Wait for the cooldown between throws
await RunSeconds(Hands.ThrowCooldown.Seconds);
// Throw the second projectile
await ThrowItem();
// Wait a moment for the second projectile to hit and embed
await RunSeconds(0.5f);
Assert.That(embedComp2.EmbeddedIntoUid, Is.EqualTo(ToServer(Target)),
"Second projectile not embedded into target");
AssertComp<EmbeddedContainerComponent>();
Assert.That(containerComp.EmbeddedObjects, Does.Contain(ToServer(projectile1)),
"Target is not tracking the second projectile as embedded.");
Assert.That(containerComp.EmbeddedObjects, Has.Count.EqualTo(2),
"Target EmbeddedObjects count did not increase with second projectile.");
// Delete the first projectile
await Delete(projectile1);
Assert.That(containerComp.EmbeddedObjects, Does.Not.Contain(ToServer(projectile1)),
"Target did not stop tracking first projectile after it was deleted.");
Assert.That(containerComp.EmbeddedObjects, Does.Not.Contain(EntityUid.Invalid),
"Target EmbeddedObjects contains an invalid entity.");
foreach (var embedded in containerComp.EmbeddedObjects)
{
Assert.That(!SEntMan.Deleted(embedded),
"Target EmbeddedObjects contains a deleted entity.");
}
Assert.That(containerComp.EmbeddedObjects, Has.Count.EqualTo(1),
"Target EmbeddedObjects count did not decrease after deleting first projectile.");
// Delete the second projectile
await Delete(projectile2);
Assert.That(!SEntMan.HasComponent<EmbeddedContainerComponent>(ToServer(Target)),
"Target did not remove EmbeddedContainerComponent after both projectiles were deleted.");
}
}

View file

@ -1,5 +1,4 @@
using System.Linq;
using Content.Client.Chat.UI;
using Content.Client.LateJoin;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.ContentPack;
@ -14,7 +13,6 @@ public sealed class UiControlTest
// You should not be adding to this.
private Type[] _ignored = new Type[]
{
typeof(EmotesMenu),
typeof(LateJoinGui),
};

View file

@ -42,21 +42,9 @@ namespace Content.Server.Access.Systems
access.Tags.UnionWith(targetAccess.Tags);
var addedLength = access.Tags.Count - beforeLength;
if (addedLength == 0)
{
_popupSystem.PopupEntity(Loc.GetString("agent-id-no-new", ("card", args.Target)), args.Target.Value, args.User);
return;
}
Dirty(uid, access);
if (addedLength == 1)
{
_popupSystem.PopupEntity(Loc.GetString("agent-id-new-1", ("card", args.Target)), args.Target.Value, args.User);
return;
}
_popupSystem.PopupEntity(Loc.GetString("agent-id-new", ("number", addedLength), ("card", args.Target)), args.Target.Value, args.User);
if (addedLength > 0)
Dirty(uid, access);
}
private void AfterUIOpen(EntityUid uid, AgentIDCardComponent component, AfterActivatableUIOpenEvent args)

View file

@ -168,7 +168,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
/*TODO: ECS SharedIdCardConsoleComponent and then log on card ejection, together with the save.
This current implementation is pretty shit as it logs 27 entries (27 lines) if someone decides to give themselves AA*/
_adminLogger.Add(LogType.Action, LogImpact.High,
_adminLogger.Add(LogType.Action, LogImpact.Medium,
$"{ToPrettyString(player):player} has modified {ToPrettyString(targetId):entity} with the following accesses: [{string.Join(", ", addedTags.Union(removedTags))}] [{string.Join(", ", newAccessList)}]");
}

View file

@ -43,5 +43,13 @@ namespace Content.Server.Administration.Commands
_entities.System<MindSystem>().ControlMob(player.UserId, target.Value);
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length != 1)
return CompletionResult.Empty;
return CompletionResult.FromOptions(CompletionHelper.NetEntities(args[0], entManager: _entities));
}
}
}

View file

@ -81,14 +81,14 @@ public sealed class ProjectileAnomalySystem : EntitySystem
EntityCoordinates targetCoords,
float severity)
{
var mapPos = coords.ToMap(EntityManager, _xform);
var mapPos = _xform.ToMapCoordinates(coords);
var spawnCoords = _mapManager.TryFindGridAt(mapPos, out var gridUid, out _)
? coords.WithEntityId(gridUid, EntityManager)
? _xform.WithEntityId(coords, gridUid)
: new(_mapManager.GetMapEntityId(mapPos.MapId), mapPos.Position);
var ent = Spawn(component.ProjectilePrototype, spawnCoords);
var direction = targetCoords.ToMapPos(EntityManager, _xform) - mapPos.Position;
var direction = _xform.ToMapCoordinates(targetCoords).Position - mapPos.Position;
if (!TryComp<ProjectileComponent>(ent, out var comp))
return;

View file

@ -16,7 +16,6 @@ public sealed class TechAnomalySystem : EntitySystem
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly BeamSystem _beam = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly EmagSystem _emag = default!;
public override void Initialize()
{

View file

@ -3,6 +3,7 @@ using Content.Server.Atmos.Piping.Components;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
@ -17,6 +18,7 @@ public sealed class GasPressurePumpSystem : SharedGasPressurePumpSystem
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
[Dependency] private readonly PowerReceiverSystem _power = default!;
public override void Initialize()
{
@ -25,33 +27,33 @@ public sealed class GasPressurePumpSystem : SharedGasPressurePumpSystem
SubscribeLocalEvent<GasPressurePumpComponent, AtmosDeviceUpdateEvent>(OnPumpUpdated);
}
private void OnPumpUpdated(EntityUid uid, GasPressurePumpComponent pump, ref AtmosDeviceUpdateEvent args)
private void OnPumpUpdated(Entity<GasPressurePumpComponent> ent, ref AtmosDeviceUpdateEvent args)
{
if (!pump.Enabled
|| (TryComp<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered)
|| !_nodeContainer.TryGetNodes(uid, pump.InletName, pump.OutletName, out PipeNode? inlet, out PipeNode? outlet))
if (!ent.Comp.Enabled
|| !_power.IsPowered(ent)
|| !_nodeContainer.TryGetNodes(ent.Owner, ent.Comp.InletName, ent.Comp.OutletName, out PipeNode? inlet, out PipeNode? outlet))
{
_ambientSoundSystem.SetAmbience(uid, false);
_ambientSoundSystem.SetAmbience(ent, false);
return;
}
var outputStartingPressure = outlet.Air.Pressure;
if (outputStartingPressure >= pump.TargetPressure)
if (outputStartingPressure >= ent.Comp.TargetPressure)
{
_ambientSoundSystem.SetAmbience(uid, false);
_ambientSoundSystem.SetAmbience(ent, false);
return; // No need to pump gas if target has been reached.
}
if (inlet.Air.TotalMoles > 0 && inlet.Air.Temperature > 0)
{
// We calculate the necessary moles to transfer using our good ol' friend PV=nRT.
var pressureDelta = pump.TargetPressure - outputStartingPressure;
var pressureDelta = ent.Comp.TargetPressure - outputStartingPressure;
var transferMoles = (pressureDelta * outlet.Air.Volume) / (inlet.Air.Temperature * Atmospherics.R);
var removed = inlet.Air.Remove(transferMoles);
_atmosphereSystem.Merge(outlet.Air, removed);
_ambientSoundSystem.SetAmbience(uid, removed.TotalMoles > 0f);
_ambientSoundSystem.SetAmbience(ent, removed.TotalMoles > 0f);
}
}
}

View file

@ -1,6 +1,6 @@
using Content.Server.Atmos.Piping.Binary.Components;
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems;
using Content.Shared.DeviceLinking.Events;
namespace Content.Server.Atmos.Piping.Binary.EntitySystems;

View file

@ -1,10 +1,8 @@
using System.Diagnostics.CodeAnalysis;
using Content.Server.Atmos.Piping.Binary.Components;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Construction.Components;
using JetBrains.Annotations;
using Robust.Shared.Map;
@ -16,7 +14,6 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
public sealed class GasPortableSystem : EntitySystem
{
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
public override void Initialize()

View file

@ -2,14 +2,12 @@ using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Monitor.Systems;
using Content.Server.Atmos.Piping.Components;
using Content.Server.Atmos.Piping.Unary.Components;
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.Administration.Logs;
using Content.Shared.Atmos;
@ -20,6 +18,7 @@ using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Atmos.Visuals;
using Content.Shared.Audio;
using Content.Shared.Database;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork;
using Content.Shared.DoAfter;
using Content.Shared.Examine;

View file

@ -50,6 +50,9 @@ public sealed class PlantHolderSystem : EntitySystem
public const float HydroponicsSpeedMultiplier = 1f;
public const float HydroponicsConsumptionMultiplier = 2f;
private static readonly ProtoId<TagPrototype> HoeTag = "Hoe";
private static readonly ProtoId<TagPrototype> PlantSampleTakerTag = "PlantSampleTaker";
public override void Initialize()
{
base.Initialize();
@ -203,7 +206,7 @@ public sealed class PlantHolderSystem : EntitySystem
return;
}
if (_tagSystem.HasTag(args.Used, "Hoe"))
if (_tagSystem.HasTag(args.Used, HoeTag))
{
args.Handled = true;
if (component.WeedLevel > 0)
@ -243,7 +246,7 @@ public sealed class PlantHolderSystem : EntitySystem
return;
}
if (_tagSystem.HasTag(args.Used, "PlantSampleTaker"))
if (_tagSystem.HasTag(args.Used, PlantSampleTakerTag))
{
args.Handled = true;
if (component.Seed == null)

View file

@ -249,7 +249,7 @@ internal sealed partial class ChatManager : IChatManager
Color? colorOverride = null;
var wrappedMessage = Loc.GetString("chat-manager-send-ooc-wrap-message", ("playerName",player.Name), ("message", FormattedMessage.EscapeText(message)));
if (_adminManager.HasAdminFlag(player, AdminFlags.Admin))
if (_adminManager.HasAdminFlag(player, AdminFlags.NameColor))
{
var prefs = _preferencesManager.GetPreferences(player.UserId);
colorOverride = prefs.AdminOOCColor;

View file

@ -4,6 +4,7 @@ using Content.Shared.Chat;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.Hands.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction.Events;
using Content.Shared.Item;
using Content.Shared.Mind;
@ -13,6 +14,7 @@ using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
using Content.Shared.Tag;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Server.Chat;
@ -26,6 +28,8 @@ public sealed class SuicideSystem : EntitySystem
[Dependency] private readonly GhostSystem _ghostSystem = default!;
[Dependency] private readonly SharedSuicideSystem _suicide = default!;
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
public override void Initialize()
{
base.Initialize();
@ -59,7 +63,7 @@ public sealed class SuicideSystem : EntitySystem
// Suicide is considered a fail if the user wasn't able to ghost
// Suiciding with the CannotSuicide tag will ghost the player but not kill the body
if (!suicideGhostEvent.Handled || _tagSystem.HasTag(victim, "CannotSuicide"))
if (!suicideGhostEvent.Handled || _tagSystem.HasTag(victim, CannotSuicideTag))
return false;
var suicideEvent = new SuicideEvent(victim);
@ -94,7 +98,7 @@ public sealed class SuicideSystem : EntitySystem
// CannotSuicide tag will allow the user to ghost, but also return to their mind
// This is kind of weird, not sure what it applies to?
if (_tagSystem.HasTag(victim, "CannotSuicide"))
if (_tagSystem.HasTag(victim, CannotSuicideTag))
args.CanReturnToBody = true;
if (_ghostSystem.OnGhostAttempt(victim.Comp.Mind.Value, args.CanReturnToBody, mind: mindComponent))
@ -149,7 +153,7 @@ public sealed class SuicideSystem : EntitySystem
if (args.Handled)
return;
var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", victim));
var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", Identity.Entity(victim, EntityManager)));
_popup.PopupEntity(othersMessage, victim, Filter.PvsExcept(victim), true);
var selfMessage = Loc.GetString("suicide-command-default-text-self");

View file

@ -9,6 +9,7 @@ using Content.Shared.Projectiles;
using Content.Shared.Tag;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Collections;
using Robust.Shared.Prototypes;
namespace Content.Server.Chemistry.EntitySystems;
@ -24,6 +25,8 @@ public sealed class SolutionInjectOnCollideSystem : EntitySystem
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly TagSystem _tag = default!;
private static readonly ProtoId<TagPrototype> HardsuitTag = "Hardsuit";
public override void Initialize()
{
base.Initialize();
@ -93,7 +96,7 @@ public sealed class SolutionInjectOnCollideSystem : EntitySystem
// Yuck, this is way to hardcodey for my tastes
// TODO blocking injection with a hardsuit should probably done with a cancellable event or something
if (!injector.Comp.PierceArmor && _inventory.TryGetSlotEntity(target, "outerClothing", out var suit) && _tag.HasTag(suit.Value, "Hardsuit"))
if (!injector.Comp.PierceArmor && _inventory.TryGetSlotEntity(target, "outerClothing", out var suit) && _tag.HasTag(suit.Value, HardsuitTag))
{
// Only show popup to attacker
if (source != null)

View file

@ -259,7 +259,7 @@ public sealed class IPIntel
{
_chatManager.SendAdminAlert(Loc.GetString("admin-alert-ipintel-warning",
("player", username),
("percent", Math.Round(score))));
("percent", score)));
}
if (!decisionIsReject)
@ -269,7 +269,7 @@ public sealed class IPIntel
{
_chatManager.SendAdminAlert(Loc.GetString("admin-alert-ipintel-blocked",
("player", username),
("percent", Math.Round(score))));
("percent", score)));
}
return _rejectBad ? (true, Loc.GetString("ipintel-suspicious")) : (false, string.Empty);

View file

@ -5,6 +5,7 @@ using Content.Shared.Construction;
using Content.Shared.Tag;
using Robust.Shared.Console;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
namespace Content.Server.Construction.Commands;
@ -13,6 +14,10 @@ public sealed class FixRotationsCommand : IConsoleCommand
{
[Dependency] private readonly IEntityManager _entManager = default!;
private static readonly ProtoId<TagPrototype> ForceFixRotationsTag = "ForceFixRotations";
private static readonly ProtoId<TagPrototype> ForceNoFixRotationsTag = "ForceNoFixRotations";
private static readonly ProtoId<TagPrototype> DiagonalTag = "Diagonal";
// ReSharper disable once StringLiteralTypo
public string Command => "fixrotations";
public string Description => "Sets the rotation of all occluders, low walls and windows to south.";
@ -86,11 +91,11 @@ public sealed class FixRotationsCommand : IConsoleCommand
// cables
valid |= _entManager.HasComponent<CableComponent>(child);
// anything else that might need this forced
valid |= tagSystem.HasTag(child, "ForceFixRotations");
valid |= tagSystem.HasTag(child, ForceFixRotationsTag);
// override
valid &= !tagSystem.HasTag(child, "ForceNoFixRotations");
valid &= !tagSystem.HasTag(child, ForceNoFixRotationsTag);
// remove diagonal entities as well
valid &= !tagSystem.HasTag(child, "Diagonal");
valid &= !tagSystem.HasTag(child, DiagonalTag);
if (!valid)
continue;

View file

@ -4,7 +4,7 @@ using Content.Server.StationRecords.Systems;
using Content.Shared.CriminalRecords;
using Content.Shared.CriminalRecords.Components;
using Content.Shared.CriminalRecords.Systems;
using Content.Shared.Dataset;
using Content.Shared.Random.Helpers;
using Content.Shared.Security;
using Content.Shared.StationRecords;
using Robust.Shared.Prototypes;
@ -36,10 +36,10 @@ public sealed class CriminalRecordsHackerSystem : SharedCriminalRecordsHackerSys
if (_station.GetOwningStation(ent) is not {} station)
return;
var reasons = _proto.Index<DatasetPrototype>(ent.Comp.Reasons);
var reasons = _proto.Index(ent.Comp.Reasons);
foreach (var (key, record) in _records.GetRecordsOfType<CriminalRecord>(station))
{
var reason = _random.Pick(reasons.Values);
var reason = _random.Pick(reasons);
_criminalRecords.OverwriteStatus(new StationRecordKey(key, station), record, SecurityStatus.Wanted, reason);
// no radio message since spam
// no history since lazy and its easy to remove anyway

View file

@ -9,8 +9,17 @@ namespace Content.Server.Destructible
[RegisterComponent]
public sealed partial class DestructibleComponent : Component
{
[DataField("thresholds")]
/// <summary>
/// A list of damage thresholds for the entity;
/// includes their triggers and resultant behaviors
/// </summary>
[DataField]
public List<DamageThreshold> Thresholds = new();
/// <summary>
/// Specifies whether the entity has passed a damage threshold that causes it to break
/// </summary>
[DataField]
public bool IsBroken = false;
}
}

View file

@ -57,6 +57,8 @@ namespace Content.Server.Destructible
/// </summary>
public void Execute(EntityUid uid, DestructibleComponent component, DamageChangedEvent args)
{
component.IsBroken = false;
foreach (var threshold in component.Thresholds)
{
if (threshold.Reached(args.Damageable, this))
@ -96,6 +98,12 @@ namespace Content.Server.Destructible
threshold.Execute(uid, this, EntityManager, args.Origin);
}
if (threshold.OldTriggered)
{
component.IsBroken |= threshold.Behaviors.Any(b => b is DoActsBehavior doActsBehavior &&
(doActsBehavior.HasAct(ThresholdActs.Breakage) || doActsBehavior.HasAct(ThresholdActs.Destruction)));
}
// if destruction behavior (or some other deletion effect) occurred, don't run other triggers.
if (EntityManager.IsQueuedForDeletion(uid) || Deleted(uid))
return;

View file

@ -1,4 +1,5 @@
using Content.Shared.Body.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Inventory;
using Content.Shared.Popups;
using JetBrains.Annotations;
@ -25,7 +26,8 @@ public sealed partial class BurnBodyBehavior : IThresholdBehavior
}
}
sharedPopupSystem.PopupCoordinates(Loc.GetString("bodyburn-text-others", ("name", bodyId)), transformSystem.GetMoverCoordinates(bodyId), PopupType.LargeCaution);
var bodyIdentity = Identity.Entity(bodyId, system.EntityManager);
sharedPopupSystem.PopupCoordinates(Loc.GetString("bodyburn-text-others", ("name", bodyIdentity)), transformSystem.GetMoverCoordinates(bodyId), PopupType.LargeCaution);
system.EntityManager.QueueDeleteEntity(bodyId);
}

View file

@ -6,7 +6,7 @@ namespace Content.Server.DeviceLinking.Components.Overload;
/// <summary>
/// Plays a sound when a device link overloads.
/// An overload happens when a device link sink is invoked to many times per tick
/// and it raises a <see cref="Content.Server.DeviceLinking.Events.DeviceLinkOverloadedEvent"/>
/// and it raises a <see cref="Content.Shared.DeviceLinking.Events.DeviceLinkOverloadedEvent"/>
/// </summary>
[RegisterComponent]
[Access(typeof(DeviceLinkOverloadSystem))]

View file

@ -7,7 +7,7 @@ namespace Content.Server.DeviceLinking.Components.Overload;
/// <summary>
/// Spawns an entity when a device link overloads.
/// An overload happens when a device link sink is invoked to many times per tick
/// and it raises a <see cref="Content.Server.DeviceLinking.Events.DeviceLinkOverloadedEvent"/>
/// and it raises a <see cref="Content.Shared.DeviceLinking.Events.DeviceLinkOverloadedEvent"/>
/// </summary>
[RegisterComponent]
[Access(typeof(DeviceLinkOverloadSystem))]

View file

@ -1,9 +1,7 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceLinking.Components.Overload;
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Components.Overload;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Content.Shared.DeviceLinking.Events;
namespace Content.Server.DeviceLinking.Systems;

View file

@ -1,5 +1,4 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;

View file

@ -1,11 +1,10 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Server.Doors.Systems;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.Doors.Components;
using Content.Shared.Doors;
using JetBrains.Annotations;
using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent;
namespace Content.Server.DeviceLinking.Systems
{

View file

@ -1,6 +1,6 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent;
using Content.Shared.DeviceLinking.Events;
namespace Content.Server.DeviceLinking.Systems;

View file

@ -1,9 +1,7 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceLinking.Events;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Map;
using System.Numerics;
namespace Content.Server.DeviceLinking.Systems;

View file

@ -1,13 +1,13 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceNetwork;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Timing;
using Content.Shared.Tools.Systems;
using Robust.Shared.Audio.Systems;
using SignalReceivedEvent = Content.Server.DeviceLinking.Events.SignalReceivedEvent;
namespace Content.Server.DeviceLinking.Systems;

View file

@ -1,7 +1,7 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceNetwork;
using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
namespace Content.Server.DeviceLinking.Systems;

View file

@ -1,7 +1,7 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceLinking.Events;
using Content.Shared.UserInterface;
using Content.Shared.Access.Systems;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.MachineLinking;
using Content.Shared.TextScreen;
using Robust.Server.GameObjects;

View file

@ -1,7 +1,6 @@
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems;
using Content.Server.Disposal.Tube;
using Content.Server.Disposal.Tube.Components;
using Content.Shared.DeviceLinking.Events;
namespace Content.Server.Disposal.Tube.Systems;

View file

@ -1,6 +1,6 @@
using Content.Server.DeviceLinking.Events;
using Content.Server.Power.Components;
using Content.Server.Wires;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.Doors.Components;
using Content.Shared.Doors.Systems;
using Content.Shared.Interaction;

View file

@ -13,6 +13,7 @@ using Robust.Shared.Serialization.Manager;
using System.Numerics;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Systems;
using Robust.Shared.GameStates;
using Robust.Shared.Utility;
namespace Content.Server.Dragon;
@ -33,11 +34,20 @@ public sealed class DragonRiftSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<DragonRiftComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<DragonRiftComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<DragonRiftComponent, AnchorStateChangedEvent>(OnAnchorChange);
SubscribeLocalEvent<DragonRiftComponent, ComponentShutdown>(OnShutdown);
}
private void OnGetState(Entity<DragonRiftComponent> ent, ref ComponentGetState args)
{
args.State = new DragonRiftComponentState
{
State = ent.Comp.State,
};
}
public override void Update(float frameTime)
{
base.Update(frameTime);

View file

@ -62,6 +62,8 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
[ValidatePrototypeId<DamageTypePrototype>]
private const string DamageType = "Shock";
private static readonly ProtoId<TagPrototype> WindowTag = "Window";
// Multiply and shift the log scale for shock damage.
private const float RecursiveDamageMultiplier = 0.75f;
private const float RecursiveTimeMultiplier = 0.8f;
@ -139,7 +141,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem
{
foreach (var entity in _entityLookup.GetLocalEntitiesIntersecting(tileRef.Value, flags: LookupFlags.StaticSundries))
{
if (_tag.HasTag(entity, "Window"))
if (_tag.HasTag(entity, WindowTag))
return false;
}
}

View file

@ -1,6 +1,6 @@
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems;
using Content.Server.Explosion.Components;
using Content.Shared.DeviceLinking.Events;
namespace Content.Server.Explosion.EntitySystems
{

View file

@ -21,6 +21,7 @@ using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Random;
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
using Robust.Shared.Prototypes;
namespace Content.Server.Flash
{
@ -39,6 +40,8 @@ namespace Content.Server.Flash
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!;
private static readonly ProtoId<TagPrototype> TrashTag = "Trash";
public override void Initialize()
{
base.Initialize();
@ -94,7 +97,7 @@ namespace Content.Server.Flash
if (_charges.IsEmpty(uid, charges))
{
_appearance.SetData(uid, FlashVisuals.Burnt, true);
_tag.AddTag(uid, "Trash");
_tag.AddTag(uid, TrashTag);
_popup.PopupEntity(Loc.GetString("flash-component-becomes-empty"), user);
}

View file

@ -16,6 +16,7 @@ using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Content.Server.Chemistry.Containers.EntitySystems;
using Robust.Shared.Prototypes;
// todo: remove this stinky LINQy
namespace Content.Server.Forensics
@ -33,6 +34,8 @@ namespace Content.Server.Forensics
[Dependency] private readonly ForensicsSystem _forensicsSystem = default!;
[Dependency] private readonly TagSystem _tag = default!;
private static readonly ProtoId<TagPrototype> DNASolutionScannableTag = "DNASolutionScannable";
public override void Initialize()
{
base.Initialize();
@ -86,7 +89,7 @@ namespace Content.Server.Forensics
scanner.Residues = forensics.Residues.ToList();
}
if (_tag.HasTag(args.Args.Target.Value, "DNASolutionScannable"))
if (_tag.HasTag(args.Args.Target.Value, DNASolutionScannableTag))
{
scanner.SolutionDNAs = _forensicsSystem.GetSolutionsDNA(args.Args.Target.Value);
} else

View file

@ -34,7 +34,7 @@ namespace Content.Server.GameTicking
if (args.NewStatus != SessionStatus.Disconnected)
{
mind.Session = session;
_pvsOverride.AddSessionOverride(GetNetEntity(mindId.Value), session);
_pvsOverride.AddSessionOverride(mindId.Value, session);
}
DebugTools.Assert(mind.Session == session);

View file

@ -196,7 +196,7 @@ namespace Content.Server.GameTicking
if (ev.GameMap.IsGrid)
{
var mapUid = _map.CreateMap(out mapId);
var mapUid = _map.CreateMap(out mapId, runMapInit: options?.InitializeMaps ?? false);
if (!_loader.TryLoadGrid(mapId,
ev.GameMap.MapPath,
out var grid,
@ -558,7 +558,7 @@ namespace Content.Server.GameTicking
if (TryGetEntity(mind.OriginalOwnedEntity, out var entity) && pvsOverride)
{
_pvsOverride.AddGlobalOverride(GetNetEntity(entity.Value), recursive: true);
_pvsOverride.AddGlobalOverride(entity.Value);
}
var roles = _roles.MindGetAllRoleInfo(mindId);

View file

@ -429,7 +429,7 @@ namespace Content.Server.GameTicking
// Ideally engine would just spawn them on grid directly I guess? Right now grid traversal is handling it during
// update which means we need to add a hack somewhere around it.
var spawn = _robustRandom.Pick(_possiblePositions);
var toMap = spawn.ToMap(EntityManager, _transform);
var toMap = _transform.ToMapCoordinates(spawn);
if (_mapManager.TryFindGridAt(toMap, out var gridUid, out _))
{

View file

@ -9,6 +9,7 @@ using Content.Shared.Mobs.Systems;
using Content.Shared.Survivor.Components;
using Content.Shared.Tag;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Server.GameTicking.Rules;
@ -22,6 +23,8 @@ public sealed class SurvivorRuleSystem : GameRuleSystem<SurvivorRuleComponent>
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
private static readonly ProtoId<TagPrototype> InvalidForSurvivorAntagTag = "InvalidForSurvivorAntag";
public override void Initialize()
{
base.Initialize();
@ -44,7 +47,7 @@ public sealed class SurvivorRuleSystem : GameRuleSystem<SurvivorRuleComponent>
var mind = humanMind.Owner;
var ent = humanMind.Comp.OwnedEntity.Value;
if (HasComp<SurvivorComponent>(mind) || _tag.HasTag(mind, "InvalidForSurvivorAntag"))
if (HasComp<SurvivorComponent>(mind) || _tag.HasTag(mind, InvalidForSurvivorAntagTag))
continue;
EnsureComp<SurvivorComponent>(mind);

View file

@ -72,6 +72,8 @@ namespace Content.Server.Ghost
private EntityQuery<GhostComponent> _ghostQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private static readonly ProtoId<TagPrototype> AllowGhostShownByEventTag = "AllowGhostShownByEvent";
public override void Initialize()
{
base.Initialize();
@ -403,7 +405,7 @@ namespace Content.Server.Ghost
var entityQuery = EntityQueryEnumerator<GhostComponent, VisibilityComponent>();
while (entityQuery.MoveNext(out var uid, out var _, out var vis))
{
if (!_tag.HasTag(uid, "AllowGhostShownByEvent"))
if (!_tag.HasTag(uid, AllowGhostShownByEventTag))
continue;
if (visible)

View file

@ -8,6 +8,7 @@ using Content.Shared.Examine;
using Content.Shared.Guardian;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Mobs;
@ -188,7 +189,9 @@ namespace Content.Server.Guardian
// Can only inject things with the component...
if (!HasComp<CanHostGuardianComponent>(target))
{
_popupSystem.PopupEntity(Loc.GetString("guardian-activator-invalid-target"), user, user);
var msg = Loc.GetString("guardian-activator-invalid-target", ("entity", Identity.Entity(target, EntityManager, user)));
_popupSystem.PopupEntity(msg, user, user);
return;
}

View file

@ -39,6 +39,15 @@ namespace Content.Server.Hands.Systems
[Dependency] private readonly PullingSystem _pullingSystem = default!;
[Dependency] private readonly ThrowingSystem _throwingSystem = default!;
private EntityQuery<PhysicsComponent> _physicsQuery;
/// <summary>
/// Items dropped when the holder falls down will be launched in
/// a direction offset by up to this many degrees from the holder's
/// movement direction.
/// </summary>
private const float DropHeldItemsSpread = 45;
public override void Initialize()
{
base.Initialize();
@ -60,6 +69,8 @@ namespace Content.Server.Hands.Systems
CommandBinds.Builder
.Bind(ContentKeyFunctions.ThrowItemInHand, new PointerInputCmdHandler(HandleThrowItem))
.Register<HandsSystem>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
}
public override void Shutdown()
@ -234,13 +245,13 @@ namespace Content.Server.Hands.Systems
private void OnDropHandItems(Entity<HandsComponent> entity, ref DropHandItemsEvent args)
{
var direction = EntityManager.TryGetComponent(entity, out PhysicsComponent? comp) ? comp.LinearVelocity / 50 : Vector2.Zero;
var dropAngle = _random.NextFloat(0.8f, 1.2f);
// If the holder doesn't have a physics component, they ain't moving
var holderVelocity = _physicsQuery.TryComp(entity, out var physics) ? physics.LinearVelocity : Vector2.Zero;
var spreadMaxAngle = Angle.FromDegrees(DropHeldItemsSpread);
var fellEvent = new FellDownEvent(entity);
RaiseLocalEvent(entity, fellEvent, false);
var worldRotation = TransformSystem.GetWorldRotation(entity).ToVec();
foreach (var hand in entity.Comp.Hands.Values)
{
if (hand.HeldEntity is not EntityUid held)
@ -255,10 +266,26 @@ namespace Content.Server.Hands.Systems
if (!TryDrop(entity, hand, null, checkActionBlocker: false, handsComp: entity.Comp))
continue;
// Rotate the item's throw vector a bit for each item
var angleOffset = _random.NextAngle(-spreadMaxAngle, spreadMaxAngle);
// Rotate the holder's velocity vector by the angle offset to get the item's velocity vector
var itemVelocity = angleOffset.RotateVec(holderVelocity);
// Decrease the distance of the throw by a random amount
itemVelocity *= _random.NextFloat(1f);
// Heavier objects don't get thrown as far
// If the item doesn't have a physics component, it isn't going to get thrown anyway, but we'll assume infinite mass
itemVelocity *= _physicsQuery.TryComp(held, out var heldPhysics) ? heldPhysics.InvMass : 0;
// Throw at half the holder's intentional throw speed and
// vary the speed a little to make it look more interesting
var throwSpeed = entity.Comp.BaseThrowspeed * _random.NextFloat(0.45f, 0.55f);
_throwingSystem.TryThrow(held,
_random.NextAngle().RotateVec(direction / dropAngle + worldRotation / 50),
0.5f * dropAngle * _random.NextFloat(-0.9f, 1.1f),
entity, 0);
itemVelocity,
throwSpeed,
entity,
pushbackRatio: 0,
compensateFriction: false
);
}
}

View file

@ -2,7 +2,6 @@ using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
using Content.Server.Construction;
using Content.Server.Explosion.EntitySystems;
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems;
using Content.Server.Hands.Systems;
using Content.Server.Kitchen.Components;
@ -17,6 +16,7 @@ using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Database;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.Destructible;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
@ -73,6 +73,9 @@ namespace Content.Server.Kitchen.EntitySystems
[ValidatePrototypeId<EntityPrototype>]
private const string MalfunctionSpark = "Spark";
private static readonly ProtoId<TagPrototype> MetalTag = "Metal";
private static readonly ProtoId<TagPrototype> PlasticTag = "Plastic";
public override void Initialize()
{
base.Initialize();
@ -550,12 +553,12 @@ namespace Content.Server.Kitchen.EntitySystems
return;
}
if (_tag.HasTag(item, "Metal"))
if (_tag.HasTag(item, MetalTag))
{
malfunctioning = true;
}
if (_tag.HasTag(item, "Plastic"))
if (_tag.HasTag(item, PlasticTag))
{
var junk = Spawn(component.BadRecipeEntityId, Transform(uid).Coordinates);
_container.Insert(junk, component.Storage);

View file

@ -10,6 +10,7 @@ using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Light.EntitySystems
@ -24,6 +25,8 @@ namespace Content.Server.Light.EntitySystems
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly MetaDataSystem _metaData = default!;
private static readonly ProtoId<TagPrototype> TrashTag = "Trash";
public override void Initialize()
{
base.Initialize();
@ -69,7 +72,7 @@ namespace Content.Server.Light.EntitySystems
_metaData.SetEntityName(ent, Loc.GetString(component.SpentName), meta);
_metaData.SetEntityDescription(ent, Loc.GetString(component.SpentDesc), meta);
_tagSystem.AddTag(ent, "Trash");
_tagSystem.AddTag(ent, TrashTag);
UpdateSounds(ent);
UpdateVisualizer(ent);

View file

@ -1,5 +1,3 @@
using Content.Server.Administration.Logs;
using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceLinking.Systems;
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Systems;
@ -9,18 +7,14 @@ using Content.Server.Light.Components;
using Content.Server.Power.Components;
using Content.Shared.Audio;
using Content.Shared.Damage;
using Content.Shared.Database;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DoAfter;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Shared.Light;
using Content.Shared.Light.Components;
using Content.Shared.Popups;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Robust.Shared.Audio.Systems;
using Content.Shared.Damage.Systems;

View file

@ -16,6 +16,8 @@ public sealed class MagicSystem : SharedMagicSystem
[Dependency] private readonly TagSystem _tag = default!;
[Dependency] private readonly SharedMindSystem _mind = default!;
private static readonly ProtoId<TagPrototype> InvalidForSurvivorAntagTag = "InvalidForSurvivorAntag";
public override void Initialize()
{
base.Initialize();
@ -48,8 +50,8 @@ public sealed class MagicSystem : SharedMagicSystem
if (!ev.MakeSurvivorAntagonist)
return;
if (_mind.TryGetMind(ev.Performer, out var mind, out _) && !_tag.HasTag(mind, "InvalidForSurvivorAntag"))
_tag.AddTag(mind, "InvalidForSurvivorAntag");
if (_mind.TryGetMind(ev.Performer, out var mind, out _) && !_tag.HasTag(mind, InvalidForSurvivorAntagTag))
_tag.AddTag(mind, InvalidForSurvivorAntagTag);
EntProtoId survivorRule = "Survivor";

View file

@ -11,6 +11,7 @@ using Content.Shared.MagicMirror;
using Content.Shared.Popups;
using Content.Shared.Tag;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Prototypes;
namespace Content.Server.MagicMirror;
@ -27,6 +28,8 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly TagSystem _tagSystem = default!;
private static readonly ProtoId<TagPrototype> HidesHairTag = "HidesHair";
public override void Initialize()
{
base.Initialize();
@ -59,7 +62,7 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
_popup.PopupEntity(
component.Target == message.Actor
? Loc.GetString("magic-mirror-blocked-by-hat-self")
: Loc.GetString("magic-mirror-blocked-by-hat-self-target"),
: Loc.GetString("magic-mirror-blocked-by-hat-self-target", ("target", Identity.Entity(message.Actor, EntityManager))),
message.Actor,
message.Actor,
PopupType.Medium);
@ -95,7 +98,7 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
}
else
{
_popup.PopupEntity(Loc.GetString("magic-mirror-change-slot-target", ("user", Identity.Name(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium);
_popup.PopupEntity(Loc.GetString("magic-mirror-change-slot-target", ("user", Identity.Entity(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium);
}
component.DoAfter = doAfterId;
@ -175,7 +178,7 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
}
else
{
_popup.PopupEntity(Loc.GetString("magic-mirror-change-color-target", ("user", Identity.Name(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium);
_popup.PopupEntity(Loc.GetString("magic-mirror-change-color-target", ("user", Identity.Entity(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium);
}
component.DoAfter = doAfterId;
@ -253,7 +256,7 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
}
else
{
_popup.PopupEntity(Loc.GetString("magic-mirror-remove-slot-target", ("user", Identity.Name(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium);
_popup.PopupEntity(Loc.GetString("magic-mirror-remove-slot-target", ("user", Identity.Entity(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium);
}
component.DoAfter = doAfterId;
@ -331,7 +334,7 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
}
else
{
_popup.PopupEntity(Loc.GetString("magic-mirror-add-slot-target", ("user", Identity.Name(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium);
_popup.PopupEntity(Loc.GetString("magic-mirror-add-slot-target", ("user", Identity.Entity(message.Actor, EntityManager))), component.Target.Value, component.Target.Value, PopupType.Medium);
}
component.DoAfter = doAfterId;
@ -391,7 +394,7 @@ public sealed class MagicMirrorSystem : SharedMagicMirrorSystem
var slots = _inventory.GetSlotEnumerator((target, inventoryComp), SlotFlags.WITHOUT_POCKET);
while (slots.MoveNext(out var slot))
{
if (slot.ContainedEntity != null && _tagSystem.HasTag(slot.ContainedEntity.Value, "HidesHair"))
if (slot.ContainedEntity != null && _tagSystem.HasTag(slot.ContainedEntity.Value, HidesHairTag))
{
return true;
}

View file

@ -2,8 +2,6 @@ using System.Linq;
using Content.Server.Administration;
using Content.Server.GameTicking;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.EntitySerialization;
@ -19,7 +17,6 @@ namespace Content.Server.Mapping
{
[Dependency] private readonly IEntityManager _entities = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
public string Command => "mapping";
public string Description => Loc.GetString("cmd-mapping-desc");

View file

@ -1,12 +1,9 @@
using System.IO;
using System.IO;
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Content.Shared.Mapping;
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.EntitySerialization;
using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@ -19,7 +16,6 @@ public sealed class MappingManager : IPostInjectInit
{
[Dependency] private readonly IAdminManager _admin = default!;
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IServerNetManager _net = default!;
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;

View file

@ -1,22 +0,0 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Medical.Stethoscope.Components
{
/// <summary>
/// Adds an innate verb when equipped to use a stethoscope.
/// </summary>
[RegisterComponent]
public sealed partial class StethoscopeComponent : Component
{
public bool IsActive = false;
[DataField("delay")]
public float Delay = 2.5f;
[DataField("action", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Action = "ActionStethoscope";
[DataField("actionEntity")] public EntityUid? ActionEntity;
}
}

View file

@ -1,18 +0,0 @@
using System.Threading;
namespace Content.Server.Medical.Components
{
/// <summary>
/// Used to let doctors use the stethoscope on people.
/// </summary>
[RegisterComponent]
public sealed partial class WearingStethoscopeComponent : Component
{
public CancellationTokenSource? CancelToken;
[DataField("delay")]
public float Delay = 2.5f;
public EntityUid Stethoscope = default!;
}
}

View file

@ -1,153 +0,0 @@
using Content.Server.Body.Components;
using Content.Server.Medical.Components;
using Content.Server.Medical.Stethoscope.Components;
using Content.Server.Popups;
using Content.Shared.Actions;
using Content.Shared.Clothing;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.FixedPoint;
using Content.Shared.Medical;
using Content.Shared.Medical.Stethoscope;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Verbs;
using Robust.Shared.Utility;
namespace Content.Server.Medical.Stethoscope
{
public sealed class StethoscopeSystem : EntitySystem
{
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<StethoscopeComponent, ClothingGotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<StethoscopeComponent, ClothingGotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<WearingStethoscopeComponent, GetVerbsEvent<InnateVerb>>(AddStethoscopeVerb);
SubscribeLocalEvent<StethoscopeComponent, GetItemActionsEvent>(OnGetActions);
SubscribeLocalEvent<StethoscopeComponent, StethoscopeActionEvent>(OnStethoscopeAction);
SubscribeLocalEvent<StethoscopeComponent, StethoscopeDoAfterEvent>(OnDoAfter);
}
/// <summary>
/// Add the component the verb event subs to if the equippee is wearing the stethoscope.
/// </summary>
private void OnEquipped(EntityUid uid, StethoscopeComponent component, ref ClothingGotEquippedEvent args)
{
component.IsActive = true;
var wearingComp = EnsureComp<WearingStethoscopeComponent>(args.Wearer);
wearingComp.Stethoscope = uid;
}
private void OnUnequipped(EntityUid uid, StethoscopeComponent component, ref ClothingGotUnequippedEvent args)
{
if (!component.IsActive)
return;
RemComp<WearingStethoscopeComponent>(args.Wearer);
component.IsActive = false;
}
/// <summary>
/// This is raised when someone with WearingStethoscopeComponent requests verbs on an item.
/// It returns if the target is not a mob.
/// </summary>
private void AddStethoscopeVerb(EntityUid uid, WearingStethoscopeComponent component, GetVerbsEvent<InnateVerb> args)
{
if (!args.CanInteract || !args.CanAccess)
return;
if (!HasComp<MobStateComponent>(args.Target))
return;
if (component.CancelToken != null)
return;
if (!TryComp<StethoscopeComponent>(component.Stethoscope, out var stetho))
return;
InnateVerb verb = new()
{
Act = () =>
{
StartListening(component.Stethoscope, uid, args.Target, stetho); // start doafter
},
Text = Loc.GetString("stethoscope-verb"),
Icon = new SpriteSpecifier.Rsi(new ("Clothing/Neck/Misc/stethoscope.rsi"), "icon"),
Priority = 2
};
args.Verbs.Add(verb);
}
private void OnStethoscopeAction(EntityUid uid, StethoscopeComponent component, StethoscopeActionEvent args)
{
StartListening(uid, args.Performer, args.Target, component);
}
private void OnGetActions(EntityUid uid, StethoscopeComponent component, GetItemActionsEvent args)
{
args.AddAction(ref component.ActionEntity, component.Action);
}
// construct the doafter and start it
private void StartListening(EntityUid scope, EntityUid user, EntityUid target, StethoscopeComponent comp)
{
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, comp.Delay, new StethoscopeDoAfterEvent(), scope, target: target, used: scope)
{
NeedHand = true,
BreakOnMove = true,
});
}
private void OnDoAfter(EntityUid uid, StethoscopeComponent component, DoAfterEvent args)
{
if (args.Handled || args.Cancelled || args.Args.Target == null)
return;
ExamineWithStethoscope(args.Args.User, args.Args.Target.Value);
}
/// <summary>
/// Return a value based on the total oxyloss of the target.
/// Could be expanded in the future with reagent effects etc.
/// The loc lines are taken from the goon wiki.
/// </summary>
public void ExamineWithStethoscope(EntityUid user, EntityUid target)
{
// The mob check seems a bit redundant but (1) they could conceivably have lost it since when the doafter started and (2) I need it for .IsDead()
if (!HasComp<RespiratorComponent>(target) || !TryComp<MobStateComponent>(target, out var mobState) || _mobStateSystem.IsDead(target, mobState))
{
_popupSystem.PopupEntity(Loc.GetString("stethoscope-dead"), target, user);
return;
}
if (!TryComp<DamageableComponent>(target, out var damage))
return;
// these should probably get loc'd at some point before a non-english fork accidentally breaks a bunch of stuff that does this
if (!damage.Damage.DamageDict.TryGetValue("Asphyxiation", out var value))
return;
var message = GetDamageMessage(value);
_popupSystem.PopupEntity(Loc.GetString(message), target, user);
}
private string GetDamageMessage(FixedPoint2 totalOxyloss)
{
var msg = (int) totalOxyloss switch
{
< 20 => "stethoscope-normal",
< 60 => "stethoscope-hyper",
< 80 => "stethoscope-irregular",
_ => "stethoscope-fucked"
};
return msg;
}
}
}

View file

@ -85,11 +85,11 @@ public sealed class MindSystem : SharedMindSystem
{
if (base.TryGetMind(user, out mindId, out mind))
{
DebugTools.Assert(_players.GetPlayerData(user).ContentData() is not { } data || data.Mind == mindId);
DebugTools.Assert(!_players.TryGetPlayerData(user, out var playerData) || playerData.ContentData() is not { } data || data.Mind == mindId);
return true;
}
DebugTools.Assert(_players.GetPlayerData(user).ContentData()?.Mind == null);
DebugTools.Assert(!_players.TryGetPlayerData(user, out var pData) || pData.ContentData()?.Mind == null);
return false;
}

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