Merge branch 'master' into icarus

This commit is contained in:
Arthur
2022-06-29 16:50:41 +03:00
594 changed files with 257595 additions and 224722 deletions

View File

@@ -48,9 +48,9 @@ namespace Content.Client.Atmos.EntitySystems
private void OnGridRemoved(GridRemovalEvent ev)
{
if (_tileData.ContainsKey(ev.GridId))
if (_tileData.ContainsKey(ev.EntityUid))
{
_tileData.Remove(ev.GridId);
_tileData.Remove(ev.EntityUid);
}
}

View File

@@ -123,10 +123,7 @@ namespace Content.Client.Atmos.EntitySystems
private void OnGridRemoved(GridRemovalEvent ev)
{
if (_tileData.ContainsKey(ev.GridId))
{
_tileData.Remove(ev.GridId);
}
_tileData.Remove(ev.EntityUid);
}
public bool HasData(EntityUid gridId)

View File

@@ -0,0 +1,8 @@
using Content.Shared.Atmos.Miasma;
using Robust.Shared.GameStates;
namespace Content.Client.Atmos.Miasma;
[NetworkedComponent, RegisterComponent]
public sealed class FliesComponent : SharedFliesComponent
{ }

View File

@@ -0,0 +1,41 @@
using Robust.Client.GameObjects;
using Robust.Shared.Utility;
namespace Content.Client.Atmos.Miasma;
public sealed class FliesSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FliesComponent, ComponentStartup>(FliesAdded);
SubscribeLocalEvent<FliesComponent, ComponentShutdown>(FliesRemoved);
}
private void FliesRemoved(EntityUid uid, FliesComponent component, ComponentShutdown args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (!sprite.LayerMapTryGet(FliesKey.Key, out var layer))
return;
sprite.RemoveLayer(layer);
}
private void FliesAdded(EntityUid uid, FliesComponent component, ComponentStartup args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (sprite.LayerMapTryGet(FliesKey.Key, out var _))
return;
var layer = sprite.AddLayer(new SpriteSpecifier.Rsi(new ResourcePath("Objects/Misc/flies.rsi"), "flies"));
sprite.LayerMapSet(FliesKey.Key, layer);
}
private enum FliesKey
{
Key,
}
}

View File

@@ -18,6 +18,7 @@ using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Audio
@@ -25,15 +26,15 @@ namespace Content.Client.Audio
[UsedImplicitly]
public sealed class BackgroundAudioSystem : EntitySystem
{
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPlayerManager _playMan = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefMan = default!;
[Dependency] private readonly IPlayerManager _playMan = default!;
private readonly AudioParams _ambientParams = new(-10f, 1, "Master", 0, 0, 0, true, 0f);
private readonly AudioParams _lobbyParams = new(-5f, 1, "Master", 0, 0, 0, true, 0f);
@@ -41,6 +42,14 @@ namespace Content.Client.Audio
private IPlayingAudioStream? _ambientStream;
private IPlayingAudioStream? _lobbyStream;
/// <summary>
/// What is currently playing.
/// </summary>
private SoundCollectionPrototype? _playingCollection;
/// <summary>
/// What the ambience has been set to.
/// </summary>
private SoundCollectionPrototype _currentCollection = default!;
private CancellationTokenSource _timerCancelTokenSource = new();
@@ -87,32 +96,36 @@ namespace Content.Client.Audio
private void EntParentChanged(ref EntParentChangedMessage message)
{
if(_playMan.LocalPlayer is null || _playMan.LocalPlayer.ControlledEntity != message.Entity) return;
if (!TryComp<TransformComponent>(message.Entity, out var xform) ||
!_mapManager.TryGetGrid(xform.GridUid, out var grid)) return;
if(_playMan.LocalPlayer is null || _playMan.LocalPlayer.ControlledEntity != message.Entity ||
!_timing.IsFirstTimePredicted) return;
var tileDef = (ContentTileDefinition) _tileDefMan[grid.GetTileRef(xform.Coordinates).Tile.TypeId];
if (!TryComp<TransformComponent>(message.Entity, out var xform)) return;
if(_currentCollection.ID == _spaceAmbience.ID)
// Check if we traversed to grid.
if (_mapManager.TryGetGrid(xform.GridUid, out var grid))
{
if (!tileDef.Sturdy) return;
if (_currentCollection == _stationAmbience) return;
ChangeAmbience(_stationAmbience);
}
else // currently station
else
{
if (tileDef.Sturdy) return;
ChangeAmbience(_spaceAmbience);
}
}
private void ChangeAmbience(SoundCollectionPrototype newAmbience)
{
EndAmbience();
_currentCollection = newAmbience;
if (_currentCollection == newAmbience) return;
_timerCancelTokenSource.Cancel();
_currentCollection = newAmbience;
_timerCancelTokenSource = new();
Timer.Spawn(1500, StartAmbience, _timerCancelTokenSource.Token);
Timer.Spawn(1500, () =>
{
// If we traverse a few times then don't interrupt an existing song.
if (_playingCollection == _currentCollection) return;
EndAmbience();
StartAmbience();
}, _timerCancelTokenSource.Token);
}
private void StateManagerOnStateChanged(StateChangedEventArgs args)
@@ -168,12 +181,14 @@ namespace Content.Client.Audio
{
EndAmbience();
if (!CanPlayCollection(_currentCollection)) return;
_playingCollection = _currentCollection;
var file = _robustRandom.Pick(_currentCollection.PickFiles).ToString();
_ambientStream = SoundSystem.Play(file, Filter.Local(), _ambientParams.WithVolume(_ambientParams.Volume + _configManager.GetCVar(CCVars.AmbienceVolume)));
}
private void EndAmbience()
{
_playingCollection = null;
_ambientStream?.Stop();
_ambientStream = null;
}

View File

@@ -3,7 +3,7 @@
xmlns:userInterface="clr-namespace:Content.Client.UserInterface"
SetSize="600 600"
MinSize="600 600">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Vertical" Margin="5 0 5 0">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'cargo-console-menu-account-name-label'}"
StyleClasses="LabelKeyText" />
@@ -14,7 +14,7 @@
<Label Text="{Loc 'cargo-console-menu-points-label'}"
StyleClasses="LabelKeyText" />
<Label Name="PointsLabel"
Text="0" />
Text="$0" />
</BoxContainer>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'cargo-console-menu-order-capacity-label'}"

View File

@@ -82,7 +82,7 @@ namespace Content.Client.Cargo.UI
{
Product = prototype,
ProductName = { Text = prototype.Name },
PointCost = { Text = prototype.PointCost.ToString() },
PointCost = { Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", prototype.PointCost.ToString())) },
Icon = { Texture = _spriteSystem.Frame0(prototype.Icon) },
};
button.MainButton.OnPressed += args =>
@@ -171,7 +171,7 @@ namespace Content.Client.Cargo.UI
public void UpdateBankData(string name, int points)
{
AccountNameLabel.Text = name;
PointsLabel.Text = points.ToString();
PointsLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", points.ToString()));
}
}
}

View File

@@ -20,7 +20,7 @@
</PanelContainer.PanelOverride>
<Label Name="PointCost"
Access="Public"
MinSize="40 32"
MinSize="52 32"
Align="Right" />
</PanelContainer>
</BoxContainer>

View File

@@ -1,5 +1,4 @@
using Content.Shared.Clothing;
using Content.Shared.Movement.EntitySystems;
namespace Content.Client.Clothing
{

View File

@@ -24,7 +24,8 @@ namespace Content.Client.Computer
{
base.InitializeEntity(entity);
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(entity);
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<SpriteComponent>(entity, out var sprite)) return;
sprite.LayerSetState(Layers.Screen, ScreenState);
if (!string.IsNullOrEmpty(KeyboardState))
@@ -38,7 +39,7 @@ namespace Content.Client.Computer
{
base.OnChangeData(component);
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(component.Owner);
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent<SpriteComponent>(component.Owner, out var sprite)) return;
if (!component.TryGetData(ComputerVisuals.Powered, out bool powered))
{

View File

@@ -40,14 +40,14 @@ namespace Content.Client.Decals
private void OnGridRemoval(GridRemovalEvent ev)
{
DecalRenderIndex.Remove(ev.GridId);
DecalZIndexIndex.Remove(ev.GridId);
DecalRenderIndex.Remove(ev.EntityUid);
DecalZIndexIndex.Remove(ev.EntityUid);
}
private void OnGridInitialize(GridInitializeEvent ev)
{
DecalRenderIndex[ev.GridId] = new();
DecalZIndexIndex[ev.GridId] = new();
DecalRenderIndex[ev.EntityUid] = new();
DecalZIndexIndex[ev.EntityUid] = new();
}
public override void Shutdown()

View File

@@ -70,7 +70,7 @@ namespace Content.Client.Entry
factory.DoAutoRegistrations();
factory.IgnoreMissingComponents();
factory.RegisterClass<SharedResearchConsoleComponent>();
// Do not add to these, they are legacy.
factory.RegisterClass<SharedLatheComponent>();
factory.RegisterClass<SharedSpawnPointComponent>();
factory.RegisterClass<SharedVendingMachineComponent>();
@@ -78,6 +78,7 @@ namespace Content.Client.Entry
factory.RegisterClass<SharedChemMasterComponent>();
factory.RegisterClass<SharedGravityGeneratorComponent>();
factory.RegisterClass<SharedAMEControllerComponent>();
// Do not add to the above, they are legacy
prototypes.RegisterIgnore("accent");
prototypes.RegisterIgnore("material");

View File

@@ -0,0 +1,40 @@
using Content.Shared.Forensics;
using Robust.Client.GameObjects;
namespace Content.Client.Forensics
{
public sealed class ForensicScannerBoundUserInterface : BoundUserInterface
{
private ForensicScannerMenu? _window;
public ForensicScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_window = new ForensicScannerMenu();
_window.OnClose += Close;
_window.Print.OnPressed += _ => Print();
_window.OpenCentered();
}
private void Print()
{
SendMessage(new ForensicScannerPrintMessage());
_window?.Close();
}
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
{
if (_window == null)
return;
if (message is not ForensicScannerUserMessage cast)
return;
_window.Populate(cast);
}
}
}

View File

@@ -0,0 +1,14 @@
<DefaultWindow xmlns="https://spacestation14.io"
Title="{Loc 'forensic-scanner-interface-title'}"
MinSize="250 100"
SetSize="250 100">
<BoxContainer Orientation="Vertical">
<Button Name="Print"
Access="Public"
Disabled="True"
Text="{Loc 'forensic-scanner-interface-print'}" />
<Label
Name="Diagnostics"
Text="{Loc forensic-scanner-interface-no-data}"/>
</BoxContainer>
</DefaultWindow>

View File

@@ -0,0 +1,37 @@
using System.Text;
using Content.Shared.Forensics;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Forensics
{
[GenerateTypedNameReferences]
public sealed partial class ForensicScannerMenu : DefaultWindow
{
public ForensicScannerMenu()
{
RobustXamlLoader.Load(this);
}
public void Populate(ForensicScannerUserMessage msg)
{
Print.Disabled = false;
var text = new StringBuilder();
text.AppendLine(Loc.GetString("forensic-scanner-interface-fingerprints"));
foreach (var fingerprint in msg.Fingerprints)
{
text.AppendLine(fingerprint);
}
text.AppendLine();
text.AppendLine(Loc.GetString("forensic-scanner-interface-fibers"));
foreach (var fiber in msg.Fibers)
{
text.AppendLine(fiber);
}
Diagnostics.Text = text.ToString();
SetSize = (350, 600);
}
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Threading.Tasks;
using Content.Client.Interactable;
using Content.Shared.ActionBlocker;
using Content.Shared.Interaction;
using Robust.Client.Audio.Midi;
using Robust.Client.AutoGenerated;
@@ -132,6 +133,8 @@ namespace Content.Client.Instruments.UI
private bool PlayCheck()
{
// TODO all of these checks should also be done server-side.
var instrumentEnt = _owner.Instrument?.Owner;
var instrument = _owner.Instrument;
@@ -151,8 +154,12 @@ namespace Content.Client.Instruments.UI
if ((instrument.Handheld && (conMan == null
|| conMan.Owner != localPlayer.ControlledEntity))) return false;
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
if (!entSysMan.GetEntitySystem<ActionBlockerSystem>().CanInteract(localPlayer.ControlledEntity.Value, instrumentEnt))
return false;
// We check that we're in range unobstructed just in case.
return EntitySystem.Get<SharedInteractionSystem>().InRangeUnobstructed(localPlayer.ControlledEntity.Value, instrumentEnt.Value);
return entSysMan.GetEntitySystem<SharedInteractionSystem>().InRangeUnobstructed(localPlayer.ControlledEntity.Value, instrumentEnt.Value);
}
private void MidiStopButtonOnPressed(ButtonEventArgs? obj)

View File

@@ -202,8 +202,8 @@ namespace Content.Client.LateJoin
var jobLabel = new Label
{
Text = job.Value != null ?
Loc.GetString("late-join-gui-job-slot-capped", ("jobName", prototype.Name), ("amount", job.Value)) :
Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", prototype.Name))
Loc.GetString("late-join-gui-job-slot-capped", ("jobName", prototype.LocalizedName), ("amount", job.Value)) :
Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", prototype.LocalizedName))
};
jobSelector.AddChild(jobLabel);

View File

@@ -1,3 +1,4 @@
using Content.Client.Movement.Systems;
using Content.Shared.Climbing;
namespace Content.Client.Movement.Components;

View File

@@ -1,4 +1,5 @@
using Content.Shared.Climbing;
using Content.Client.Movement.Systems;
using Content.Shared.Climbing;
namespace Content.Client.Movement.Components;

View File

@@ -4,12 +4,12 @@ using Content.Shared.Climbing;
using Content.Shared.DragDrop;
using Robust.Shared.GameStates;
namespace Content.Client.Movement;
namespace Content.Client.Movement.Systems;
public sealed class ClimbSystem : SharedClimbSystem
{
[Dependency] private readonly InteractionSystem _interactionSystem = default!;
public override void Initialize()
{
base.Initialize();

View File

@@ -0,0 +1,80 @@
using Content.Client.Clothing;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Timing;
namespace Content.Client.Movement.Systems;
public sealed class JetpackSystem : SharedJetpackSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<JetpackComponent, AppearanceChangeEvent>(OnJetpackAppearance);
}
protected override bool CanEnable(JetpackComponent component)
{
// No predicted atmos so you'd have to do a lot of funny to get this working.
return false;
}
private void OnJetpackAppearance(EntityUid uid, JetpackComponent component, ref AppearanceChangeEvent args)
{
args.Component.TryGetData(JetpackVisuals.Enabled, out bool enabled);
var state = "icon" + (enabled ? "-on" : "");
args.Sprite?.LayerSetState(0, state);
if (TryComp<ClothingComponent>(uid, out var clothing))
clothing.EquippedPrefix = enabled ? "on" : null;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
if (!_timing.IsFirstTimePredicted) return;
foreach (var comp in EntityQuery<ActiveJetpackComponent>())
{
comp.Accumulator += frameTime;
if (comp.Accumulator < comp.EffectCooldown) continue;
comp.Accumulator -= comp.EffectCooldown;
CreateParticles(comp.Owner);
}
}
private void CreateParticles(EntityUid uid)
{
var uidXform = Transform(uid);
var coordinates = uidXform.Coordinates;
var gridUid = coordinates.GetGridUid(EntityManager);
if (_mapManager.TryGetGrid(gridUid, out var grid))
{
coordinates = new EntityCoordinates(grid.GridEntityId, grid.WorldToLocal(coordinates.ToMapPos(EntityManager)));
}
else if (uidXform.MapUid != null)
{
coordinates = new EntityCoordinates(uidXform.MapUid.Value, uidXform.WorldPosition);
}
else
{
return;
}
var ent = Spawn("JetpackEffect", coordinates);
var xform = Transform(ent);
xform.Coordinates = coordinates;
}
}

View File

@@ -1,5 +1,5 @@
using Content.Shared.Movement.Components;
using Content.Shared.Movement.EntitySystems;
using Content.Shared.Movement.Systems;
using Content.Shared.Nutrition.Components;
using Robust.Shared.GameObjects;

View File

@@ -134,7 +134,7 @@ public sealed class InteractionOutlineSystem : EntitySystem
}
var inRange = false;
if (localPlayer.ControlledEntity != null && entityToClick != null)
if (localPlayer.ControlledEntity != null && !Deleted(entityToClick))
{
inRange = _interactionSystem.InRangeUnobstructed(localPlayer.ControlledEntity.Value, entityToClick.Value);
}

View File

@@ -1,6 +1,7 @@
using Content.Shared.MobState.Components;
using Content.Shared.Movement;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Systems;
using Content.Shared.Pulling.Components;
using Robust.Client.Player;
using Robust.Shared.Map;

View File

@@ -192,7 +192,7 @@ namespace Content.Client.Preferences.UI
var highPriorityJob = humanoid?.JobPriorities.SingleOrDefault(p => p.Value == JobPriority.High).Key;
if (highPriorityJob != null)
{
var jobName = IoCManager.Resolve<IPrototypeManager>().Index<JobPrototype>(highPriorityJob).Name;
var jobName = IoCManager.Resolve<IPrototypeManager>().Index<JobPrototype>(highPriorityJob).LocalizedName;
description = $"{description}\n{jobName}";
}

View File

@@ -344,7 +344,7 @@ namespace Content.Client.Preferences.UI
var firstCategory = true;
foreach (var job in prototypeManager.EnumeratePrototypes<JobPrototype>().OrderBy(j => j.Name))
foreach (var job in prototypeManager.EnumeratePrototypes<JobPrototype>().OrderBy(j => j.LocalizedName))
{
if(!job.SetPreference) { continue; }
@@ -1033,7 +1033,7 @@ namespace Content.Client.Preferences.UI
Children =
{
icon,
new Label {Text = job.Name, MinSize = (175, 0)},
new Label {Text = job.LocalizedName, MinSize = (175, 0)},
_optionButton
}
});

View File

@@ -21,14 +21,14 @@ namespace Content.Client.Research
if (curState is not TechnologyDatabaseState state) return;
_technologies.Clear();
Technologies.Clear();
var protoManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var techID in state.Technologies)
{
if (!protoManager.TryIndex(techID, out TechnologyPrototype? technology)) continue;
_technologies.Add(technology);
Technologies.Add(technology);
}
OnDatabaseUpdated?.Invoke();

View File

@@ -1,6 +1,5 @@
using Content.Shared.Research.Components;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using static Content.Shared.Research.Components.SharedResearchClientComponent;
namespace Content.Client.Research.UI
{

View File

@@ -1,9 +1,7 @@
using Content.Shared.Research.Components;
using Content.Shared.Research.Prototypes;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using static Content.Shared.Research.Components.SharedResearchConsoleComponent;
namespace Content.Client.Research.UI
{

View File

@@ -0,0 +1,13 @@
using Content.Client.Computer;
using Content.Client.Shuttles.UI;
using Content.Shared.Shuttles.BUIStates;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
namespace Content.Client.Shuttles.BUI;
[UsedImplicitly]
public sealed class EmergencyConsoleBoundUserInterface : ComputerBoundUserInterface<EmergencyConsoleWindow, EmergencyConsoleBoundUserInterfaceState>
{
public EmergencyConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
}

View File

@@ -19,6 +19,15 @@ public sealed class RadarConsoleBoundUserInterface : BoundUserInterface
_window?.OpenCentered();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);

View File

@@ -0,0 +1,17 @@
using Content.Client.Shuttles.Systems;
using Robust.Shared.Console;
namespace Content.Client.Shuttles.Commands;
public sealed class ShowEmergencyShuttleCommand : IConsoleCommand
{
public string Command => "showemergencyshuttle";
public string Description => "Shows the expected position of the emergency shuttle";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var tstalker = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ShuttleSystem>();
tstalker.EnableShuttlePosition ^= true;
shell.WriteLine($"Set emergency shuttle debug to {tstalker.EnableShuttlePosition}");
}
}

View File

@@ -0,0 +1,80 @@
using Content.Shared.Shuttles.Events;
using Content.Shared.Shuttles.Systems;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
namespace Content.Client.Shuttles.Systems;
public sealed partial class ShuttleSystem : SharedShuttleSystem
{
/// <summary>
/// Should we show the expected emergency shuttle position.
/// </summary>
public bool EnableShuttlePosition
{
get => _enableShuttlePosition;
set
{
if (_enableShuttlePosition == value) return;
_enableShuttlePosition = value;
var overlayManager = IoCManager.Resolve<IOverlayManager>();
if (_enableShuttlePosition)
{
_overlay = new EmergencyShuttleOverlay(EntityManager);
overlayManager.AddOverlay(_overlay);
RaiseNetworkEvent(new EmergencyShuttleRequestPositionMessage());
}
else
{
overlayManager.RemoveOverlay(_overlay!);
_overlay = null;
}
}
}
private bool _enableShuttlePosition;
private EmergencyShuttleOverlay? _overlay;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<EmergencyShuttlePositionMessage>(OnShuttlePosMessage);
}
private void OnShuttlePosMessage(EmergencyShuttlePositionMessage ev)
{
if (_overlay == null) return;
_overlay.StationUid = ev.StationUid;
_overlay.Position = ev.Position;
}
}
/// <summary>
/// Shows the expected position of the emergency shuttle. Nothing more.
/// </summary>
public sealed class EmergencyShuttleOverlay : Overlay
{
private IEntityManager _entManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityUid? StationUid;
public Box2? Position;
public EmergencyShuttleOverlay(IEntityManager entManager)
{
_entManager = entManager;
}
protected override void Draw(in OverlayDrawArgs args)
{
if (Position == null || !_entManager.TryGetComponent<TransformComponent>(StationUid, out var xform)) return;
args.WorldHandle.SetTransform(xform.WorldMatrix);
args.WorldHandle.DrawRect(Position.Value, Color.Red.WithAlpha(100));
args.WorldHandle.SetTransform(Matrix3.Identity);
}
}

View File

@@ -0,0 +1,41 @@
<userInterface:FancyWindow xmlns="https://spacestation14.io"
xmlns:userInterface="clr-namespace:Content.Client.UserInterface"
Title="Emergency Shuttle Console"
MinSize="400 400">
<BoxContainer Orientation="Vertical"
Margin="5">
<Label Name="Countdown"
HorizontalAlignment="Center"
Text="00:00"/>
<BoxContainer Name="EngineStatusContainer"
HorizontalAlignment="Center">
<Label HorizontalAlignment="Center"
Text="{Loc 'emergency-shuttle-ui-engines'}"/>
<Label Name="EngineStatus"
HorizontalAlignment="Center"
FontColorOverride="#FFA500"
Text="{Loc 'emergency-shuttle-ui-idle'}"/>
</BoxContainer>
<BoxContainer Name="LaunchContainer">
<Label Text="{Loc 'emergency-shuttle-ui-early-authorize'}"/>
<Button Name="RepealAllButton" Text="{Loc 'emergency-shuttle-ui-repeal-all'}"/>
</BoxContainer>
<!-- Spacer -->
<BoxContainer Name="AuthorizeContainer">
<Button Name="AuthorizeButton"
Text="{Loc 'emergency-shuttle-ui-authorize'}"/>
<Button Name="RepealButton" Text="{Loc 'emergency-shuttle-ui-repeal'}"/>
</BoxContainer>
<BoxContainer Name="AuthorizationsTextContainer">
<Label Text="{Loc 'emergency-shuttle-ui-authorizations'}"/>
<Label Name="AuthorizationCount"
Text=""
HorizontalAlignment="Right"
Align="Right"
HorizontalExpand="True"/>
</BoxContainer>
<!-- Spacer -->
<BoxContainer Name="AuthorizationsContainer" Orientation="Vertical">
</BoxContainer>
</BoxContainer>
</userInterface:FancyWindow>

View File

@@ -0,0 +1,93 @@
using Content.Client.Computer;
using Content.Client.UserInterface;
using Content.Shared.Shuttles.BUIStates;
using Content.Shared.Shuttles.Events;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Timing;
namespace Content.Client.Shuttles.UI;
[GenerateTypedNameReferences]
public sealed partial class EmergencyConsoleWindow : FancyWindow,
IComputerWindow<EmergencyConsoleBoundUserInterfaceState>
{
private readonly IGameTiming _timing;
private TimeSpan? _earlyLaunchTime;
public EmergencyConsoleWindow()
{
_timing = IoCManager.Resolve<IGameTiming>();
RobustXamlLoader.Load(this);
}
public void SetupComputerWindow(ComputerBoundUserInterfaceBase cb)
{
RepealAllButton.OnPressed += args =>
{
OnRepealAllPressed(cb, args);
};
AuthorizeButton.OnPressed += args =>
{
OnAuthorizePressed(cb, args);
};
RepealButton.OnPressed += args =>
{
OnRepealPressed(cb, args);
};
}
private void OnRepealAllPressed(ComputerBoundUserInterfaceBase cb, BaseButton.ButtonEventArgs obj)
{
cb.SendMessage(new EmergencyShuttleRepealAllMessage());
}
private void OnRepealPressed(ComputerBoundUserInterfaceBase cb, BaseButton.ButtonEventArgs obj)
{
cb.SendMessage(new EmergencyShuttleRepealMessage());
}
private void OnAuthorizePressed(ComputerBoundUserInterfaceBase cb, BaseButton.ButtonEventArgs obj)
{
cb.SendMessage(new EmergencyShuttleAuthorizeMessage());
}
public void UpdateState(EmergencyConsoleBoundUserInterfaceState scc)
{
// TODO: Loc and cvar for this.
_earlyLaunchTime = scc.EarlyLaunchTime;
AuthorizationsContainer.DisposeAllChildren();
var remainingAuths = scc.AuthorizationsRequired - scc.Authorizations.Count;
AuthorizationCount.Text = Loc.GetString("emergency-shuttle-ui-remaining", ("remaining", remainingAuths));
foreach (var auth in scc.Authorizations)
{
AuthorizationsContainer.AddChild(new Label
{
Text = auth,
FontColorOverride = Color.Lime,
});
}
}
protected override void Draw(DrawingHandleScreen handle)
{
base.Draw(handle);
if (_earlyLaunchTime == null)
{
Countdown.Text = "00:10";
}
else
{
var remaining = _earlyLaunchTime.Value - _timing.CurTime;
if (remaining < TimeSpan.Zero)
remaining = TimeSpan.Zero;
Countdown.Text = $"{remaining.Minutes:00}:{remaining.Seconds:00}";
}
}
}

View File

@@ -45,7 +45,7 @@ public sealed class RadarControl : Control
/// </summary>
private Dictionary<EntityUid, Control> _iffControls = new();
private Dictionary<EntityUid, Dictionary<EntityUid, DockingInterfaceState>> _docks = new();
private Dictionary<EntityUid, List<DockingInterfaceState>> _docks = new();
public bool ShowIFF { get; set; } = true;
public bool ShowDocks { get; set; } = true;
@@ -88,7 +88,7 @@ public sealed class RadarControl : Control
{
var coordinates = state.Coordinates;
var grid = _docks.GetOrNew(coordinates.EntityId);
grid.Add(state.Entity, state);
grid.Add(state);
}
}
@@ -187,7 +187,7 @@ public sealed class RadarControl : Control
foreach (var grid in _mapManager.FindGridsIntersecting(mapPosition.MapId,
new Box2(mapPosition.Position - RadarRange, mapPosition.Position + RadarRange)))
{
if (grid.Index == ourGridId) continue;
if (grid.GridEntityId == ourGridId) continue;
var gridBody = bodyQuery.GetComponent(grid.GridEntityId);
if (gridBody.Mass < 10f)
@@ -288,8 +288,9 @@ public sealed class RadarControl : Control
if (_docks.TryGetValue(uid, out var docks))
{
foreach (var (ent, state) in docks)
foreach (var state in docks)
{
var ent = state.Entity;
var position = state.Coordinates.Position;
var uiPosition = matrix.Transform(position);

View File

@@ -0,0 +1,11 @@
using Content.Shared.Spawners.EntitySystems;
namespace Content.Client.Spawners;
public sealed class TimedDespawnSystem : SharedTimedDespawnSystem
{
protected override bool CanDelete(EntityUid uid)
{
return uid.IsClientSide();
}
}

View File

@@ -17,19 +17,26 @@ namespace Content.IntegrationTests.Tests
[Test]
public async Task Test()
{
await using var pairTracker = await PoolManager.GetServerClient();
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings()
{
NoClient = true,
});
var server = pairTracker.Pair.Server;
var config = server.ResolveDependency<IConfigurationManager>();
var sysManager = server.ResolveDependency<IEntitySystemManager>();
var ticker = sysManager.GetEntitySystem<GameTicker>();
var roundEndSystem = sysManager.GetEntitySystem<RoundEndSystem>();
var eventCount = 0;
await server.WaitAssertion(() =>
{
var ticker = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>();
ticker.RestartRound();
var config = IoCManager.Resolve<IConfigurationManager>();
config.SetCVar(CCVars.GameLobbyEnabled, true);
config.SetCVar(CCVars.EmergencyShuttleTransitTime, 1f);
config.SetCVar(CCVars.EmergencyShuttleDockTime, 1f);
var roundEndSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<RoundEndSystem>();
roundEndSystem.DefaultCooldownDuration = TimeSpan.FromMilliseconds(250);
roundEndSystem.DefaultCountdownDuration = TimeSpan.FromMilliseconds(500);
roundEndSystem.DefaultRestartRoundDuration = TimeSpan.FromMilliseconds(250);
@@ -41,7 +48,7 @@ namespace Content.IntegrationTests.Tests
bus.SubscribeEvent<RoundEndSystemChangedEvent>(EventSource.Local, this, _ => {
Interlocked.Increment(ref eventCount);
});
var roundEndSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<RoundEndSystem>();
// Press the shuttle call button
roundEndSystem.RequestRoundEnd();
Assert.That(roundEndSystem.ExpectedCountdownEnd, Is.Not.Null, "Shuttle was called, but countdown time was not set");
@@ -55,8 +62,6 @@ namespace Content.IntegrationTests.Tests
await server.WaitAssertion(() =>
{
var roundEndSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<RoundEndSystem>();
Assert.That(roundEndSystem.CanCall(), Is.True, "We waited a while, but the cooldown is not expired");
Assert.That(roundEndSystem.ExpectedCountdownEnd, Is.Not.Null, "We were waiting for the cooldown, but the round also ended");
// Recall the shuttle, which should trigger the cooldown again
@@ -69,7 +74,6 @@ namespace Content.IntegrationTests.Tests
await server.WaitAssertion(() =>
{
var roundEndSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<RoundEndSystem>();
Assert.That(roundEndSystem.CanCall(), Is.True, "We waited a while, but the cooldown is not expired");
// Press the shuttle call button
roundEndSystem.RequestRoundEnd();
@@ -79,7 +83,6 @@ namespace Content.IntegrationTests.Tests
await server.WaitAssertion(() =>
{
var roundEndSystem = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<RoundEndSystem>();
Assert.That(roundEndSystem.CanCall(), Is.True, "We waited a while, but the cooldown is not expired");
Assert.That(roundEndSystem.ExpectedCountdownEnd, Is.Not.Null, "The countdown ended, but we just wanted the cooldown to end");
});
@@ -96,7 +99,6 @@ namespace Content.IntegrationTests.Tests
{
return server.WaitAssertion(() =>
{
var ticker = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GameTicker>();
Assert.That(ticker.RunLevel, Is.EqualTo(level));
});
}

View File

@@ -27,7 +27,7 @@ namespace Content.IntegrationTests.Tests
{
// TODO: Properly find the "main" station grid.
var grid0 = mapManager.GetAllGrids().First();
mapLoader.SaveBlueprint(grid0.Index, "save load save 1.yml");
mapLoader.SaveBlueprint(grid0.GridEntityId, "save load save 1.yml");
var mapId = mapManager.CreateMap();
var grid = mapLoader.LoadBlueprint(mapId, "save load save 1.yml").gridId;
mapLoader.SaveBlueprint(grid!.Value, "save load save 2.yml");

View File

@@ -8,14 +8,11 @@ using Robust.Shared.Prototypes;
namespace Content.Server.AI.Components
{
[RegisterComponent]
[ComponentReference(typeof(IMobMoverComponent))]
[Virtual]
public class AiControllerComponent : Component, IMobMoverComponent, IMoverComponent
public class AiControllerComponent : Component
{
[DataField("logic")] private float _visionRadius = 8.0f;
public bool CanMove { get; set; } = true;
// TODO: Need to ECS a lot more of the AI first before we can ECS this
/// <summary>
/// Whether the AI is actively iterated.
@@ -46,59 +43,6 @@ namespace Content.Server.AI.Components
set => _visionRadius = value;
}
/// <inheritdoc />
protected override void Initialize()
{
base.Initialize();
// This component requires a physics component.
Owner.EnsureComponent<PhysicsComponent>();
}
/// <summary>
/// Movement speed (m/s) that the entity walks, after modifiers
/// </summary>
[ViewVariables]
public float CurrentWalkSpeed
{
get
{
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(Owner, out MovementSpeedModifierComponent? component))
{
return component.CurrentWalkSpeed;
}
return MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
}
}
/// <summary>
/// Movement speed (m/s) that the entity walks, after modifiers
/// </summary>
[ViewVariables]
public float CurrentSprintSpeed
{
get
{
if (IoCManager.Resolve<IEntityManager>().TryGetComponent(Owner, out MovementSpeedModifierComponent? component))
{
return component.CurrentSprintSpeed;
}
return MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
}
}
public Angle LastGridAngle { get => Angle.Zero; set {} }
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public float PushStrength { get; set; } = IMobMoverComponent.PushStrengthDefault;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public float GrabRange { get; set; } = IMobMoverComponent.GrabRangeDefault;
/// <summary>
/// Is the entity Sprinting (running)?
/// </summary>
@@ -111,17 +55,6 @@ namespace Content.Server.AI.Components
[ViewVariables]
public Vector2 VelocityDir { get; set; }
(Vector2 walking, Vector2 sprinting) IMoverComponent.VelocityDir =>
Sprinting ? (Vector2.Zero, VelocityDir) : (VelocityDir, Vector2.Zero);
public EntityCoordinates LastPosition { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public float StepSoundDistance { get; set; }
public void SetVelocityDirection(Direction direction, ushort subTick, bool enabled) { }
public void SetSprinting(ushort subTick, bool walking) { }
public virtual void Update(float frameTime) {}
}
}

View File

@@ -97,7 +97,7 @@ namespace Content.Server.AI.Pathfinding.Accessible
private void GridRemoved(GridRemovalEvent ev)
{
_regions.Remove(ev.GridId);
_regions.Remove(ev.EntityUid);
}
public override void Update(float frameTime)

View File

@@ -47,7 +47,7 @@ namespace Content.Server.AI.Pathfinding.Pathfinders
return null;
}
if (_entityManager.Deleted(_pathfindingArgs.Start.GridIndex))
if (_entityManager.Deleted(_pathfindingArgs.Start.GridUid))
return null;
var frontier = new PriorityQueue<ValueTuple<float, PathfindingNode>>(new PathfindingComparer());

View File

@@ -8,8 +8,10 @@ using Content.Server.CPUJob.JobQueues;
using Content.Shared.Access.Systems;
using Content.Shared.Doors.Components;
using Content.Shared.Interaction;
using Content.Shared.Movement.Components;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Server.AI.Steering
@@ -17,6 +19,7 @@ namespace Content.Server.AI.Steering
public sealed class AiSteeringSystem : EntitySystem
{
// http://www.red3d.com/cwr/papers/1999/gdc99steer.html for a steering overview
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly PathfindingSystem _pathfindingSystem = default!;
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
@@ -120,9 +123,9 @@ namespace Content.Server.AI.Steering
/// <exception cref="InvalidOperationException"></exception>
public void Unregister(EntityUid entity)
{
if (EntityManager.TryGetComponent(entity, out AiControllerComponent? controller))
if (EntityManager.TryGetComponent(entity, out SharedPlayerInputMoverComponent? controller))
{
controller.VelocityDir = Vector2.Zero;
controller.CurTickSprintMovement = Vector2.Zero;
}
if (_pathfindingRequests.TryGetValue(entity, out var request))
@@ -228,6 +231,13 @@ namespace Content.Server.AI.Steering
_listIndex = (_listIndex + 1) % _agentLists.Count;
}
private void SetDirection(SharedPlayerInputMoverComponent component, Vector2 value)
{
component.CurTickSprintMovement = value;
component._lastInputTick = _timing.CurTick;
component._lastInputSubTick = ushort.MaxValue;
}
/// <summary>
/// Go through each steerer and combine their vectors
/// </summary>
@@ -240,7 +250,7 @@ namespace Content.Server.AI.Steering
{
// Main optimisation to be done below is the redundant calls and adding more variables
if (Deleted(entity) ||
!EntityManager.TryGetComponent(entity, out AiControllerComponent? controller) ||
!EntityManager.TryGetComponent(entity, out SharedPlayerInputMoverComponent? controller) ||
!controller.CanMove ||
!TryComp(entity, out TransformComponent? xform) ||
xform.GridUid == null)
@@ -252,13 +262,13 @@ namespace Content.Server.AI.Steering
if (entitySteering != null && (!EntityManager.EntityExists(entitySteering.Target) ? EntityLifeStage.Deleted : EntityManager.GetComponent<MetaDataComponent>(entitySteering.Target).EntityLifeStage) >= EntityLifeStage.Deleted)
{
controller.VelocityDir = Vector2.Zero;
controller.CurTickSprintMovement = Vector2.Zero;
return SteeringStatus.NoPath;
}
if (_mapManager.IsGridPaused(xform.GridUid.Value))
{
controller.VelocityDir = Vector2.Zero;
SetDirection(controller, Vector2.Zero);
return SteeringStatus.Pending;
}
@@ -266,7 +276,7 @@ namespace Content.Server.AI.Steering
// Check if we can even arrive -> Currently only samegrid movement supported
if (xform.GridUid != steeringRequest.TargetGrid.GetGridUid(EntityManager))
{
controller.VelocityDir = Vector2.Zero;
SetDirection(controller, Vector2.Zero);
return SteeringStatus.NoPath;
}
@@ -280,7 +290,7 @@ namespace Content.Server.AI.Steering
_interactionSystem.InRangeUnobstructed(entity, steeringRequest.TargetMap, steeringRequest.ArrivalDistance, popup: true))
{
// TODO: Need cruder LOS checks for ranged weaps
controller.VelocityDir = Vector2.Zero;
SetDirection(controller, Vector2.Zero);
return SteeringStatus.Arrived;
}
@@ -291,7 +301,7 @@ namespace Content.Server.AI.Steering
// If we're really close don't swiggity swoogity back and forth and just wait for the interaction check maybe?
if (steeringRequest.TimeUntilInteractionCheck > 0.0f && targetDistance <= 0.1f)
{
controller.VelocityDir = Vector2.Zero;
SetDirection(controller, Vector2.Zero);
return SteeringStatus.Moving;
}
@@ -305,7 +315,7 @@ namespace Content.Server.AI.Steering
break;
// Currently nothing should be cancelling these except external factors
case TaskCanceledException _:
controller.VelocityDir = Vector2.Zero;
SetDirection(controller, Vector2.Zero);
return SteeringStatus.NoPath;
default:
throw pathRequest.Job.Exception;
@@ -314,7 +324,7 @@ namespace Content.Server.AI.Steering
var path = _pathfindingRequests[entity].Job.Result;
if (path == null || path.Count == 0)
{
controller.VelocityDir = Vector2.Zero;
SetDirection(controller, Vector2.Zero);
return SteeringStatus.NoPath;
}
@@ -335,7 +345,7 @@ namespace Content.Server.AI.Steering
// If the route's empty we could be close and may not need a re-path so we won't check if it is
if (!_paths.ContainsKey(entity) && !_pathfindingRequests.ContainsKey(entity) && targetDistance > 1.5f)
{
controller.VelocityDir = Vector2.Zero;
SetDirection(controller, Vector2.Zero);
RequestPath(entity, steeringRequest);
return SteeringStatus.Pending;
}
@@ -365,14 +375,14 @@ namespace Content.Server.AI.Steering
var nextGrid = NextGrid(entity, steeringRequest);
if (!nextGrid.HasValue)
{
controller.VelocityDir = Vector2.Zero;
SetDirection(controller, Vector2.Zero);
return SteeringStatus.NoPath;
}
// Validate that we can even get to the next grid (could probably just check if we can use nextTile if we're not near the target grid)
if (!_pathfindingSystem.CanTraverse(entity, nextGrid.Value))
{
controller.VelocityDir = Vector2.Zero;
SetDirection(controller, Vector2.Zero);
return SteeringStatus.NoPath;
}
@@ -392,7 +402,7 @@ namespace Content.Server.AI.Steering
// Move towards it
DebugTools.Assert(movementVector != new Vector2(float.NaN, float.NaN));
controller.VelocityDir = movementVector.Normalized;
SetDirection(controller, movementVector.Normalized);
return SteeringStatus.Moving;
}

View File

@@ -16,7 +16,7 @@ namespace Content.Server.AI.Utility.AiLogic
// TODO: Need to split out the IMover stuff for NPC to a generic one that can be used for hoomans as well.
[RegisterComponent]
[ComponentProtoName("UtilityAI")]
[ComponentReference(typeof(AiControllerComponent)), ComponentReference(typeof(IMoverComponent))]
[ComponentReference(typeof(AiControllerComponent))]
public sealed class UtilityAi : AiControllerComponent
{
// TODO: Look at having ParallelOperators (probably no more than that as then you'd have a full-blown BT)

View File

@@ -67,7 +67,7 @@ namespace Content.Server.Access.Systems
_accessSystem.SetAccessToJob(uid, job, extended);
// and also change job title on a card id
_cardSystem.TryChangeJobTitle(uid, job.Name);
_cardSystem.TryChangeJobTitle(uid, job.LocalizedName);
}
}
}

View File

@@ -1,5 +1,6 @@
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Atmos.Miasma;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Disease.Components;
@@ -89,15 +90,13 @@ namespace Content.Server.Administration.Commands
sys.TryModifyBloodLevel(target, bloodStream.BloodSolution.AvailableVolume, bloodStream);
}
if (entMan.HasComponent<JitteringComponent>(target))
{
entMan.RemoveComponent<JitteringComponent>(target);
}
if (entMan.TryGetComponent<DiseaseCarrierComponent>(target, out var carrier))
{
EntitySystem.Get<DiseaseSystem>().CureAllDiseases(target, carrier);
}
entMan.RemoveComponent<JitteringComponent>(target);
entMan.RemoveComponent<RottingComponent>(target);
}
}
}

View File

@@ -1,6 +1,11 @@
using Content.Server.Administration.Managers;
using System.Linq;
using Content.Server.Administration.Managers;
using Content.Shared.Administration;
using Content.Shared.Roles;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;
using Serilog;
namespace Content.Server.Administration.Commands;
@@ -8,8 +13,8 @@ namespace Content.Server.Administration.Commands;
public sealed class RoleBanCommand : IConsoleCommand
{
public string Command => "roleban";
public string Description => "Bans a player from a role";
public string Help => $"Usage: {Command} <name or user ID> <job> <reason> [duration in minutes, leave out or 0 for permanent ban]";
public string Description => Loc.GetString("cmd-roleban-desc");
public string Help => Loc.GetString("cmd-roleban-help");
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
@@ -46,4 +51,25 @@ public sealed class RoleBanCommand : IConsoleCommand
IoCManager.Resolve<RoleBanManager>().CreateJobBan(shell, target, job, reason, minutes);
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
var durOpts = new CompletionOption[]
{
new("0", Loc.GetString("cmd-roleban-hint-duration-1")),
new("1440", Loc.GetString("cmd-roleban-hint-duration-2")),
new("10080", Loc.GetString("cmd-roleban-hint-duration-3")),
};
return args.Length switch
{
1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(),
Loc.GetString("cmd-roleban-hint-1")),
2 => CompletionResult.FromHintOptions(CompletionHelper.PrototypeIDs<JobPrototype>(),
Loc.GetString("cmd-roleban-hint-2")),
3 => CompletionResult.FromHint(Loc.GetString("cmd-roleban-hint-3")),
4 => CompletionResult.FromHintOptions(durOpts, Loc.GetString("cmd-roleban-hint-4")),
_ => CompletionResult.Empty
};
}
}

View File

@@ -1,6 +1,8 @@
using System.Text;
using System.Linq;
using System.Text;
using Content.Server.Database;
using Content.Shared.Administration;
using Robust.Server.Player;
using Robust.Shared.Console;
namespace Content.Server.Administration.Commands;
@@ -9,8 +11,8 @@ namespace Content.Server.Administration.Commands;
public sealed class RoleBanListCommand : IConsoleCommand
{
public string Command => "rolebanlist";
public string Description => "Lists the user's role bans";
public string Help => "Usage: <name or user ID> [include unbanned]";
public string Description => Loc.GetString("cmd-rolebanlist-desc");
public string Help => Loc.GetString("cmd-rolebanlist-help");
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
@@ -88,4 +90,16 @@ public sealed class RoleBanListCommand : IConsoleCommand
shell.WriteLine(bansString.ToString());
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
return args.Length switch
{
1 => CompletionResult.FromHintOptions(CompletionHelper.SessionNames(),
Loc.GetString("cmd-rolebanlist-hint-1")),
2 => CompletionResult.FromHintOptions(CompletionHelper.Booleans,
Loc.GetString("cmd-rolebanlist-hint-2")),
_ => CompletionResult.Empty
};
}
}

View File

@@ -10,8 +10,8 @@ namespace Content.Server.Administration.Commands;
public sealed class RoleUnbanCommand : IConsoleCommand
{
public string Command => "roleunban";
public string Description => "Pardons a player's role ban";
public string Help => $"Usage: {Command} <role ban id>";
public string Description => Loc.GetString("cmd-roleunban-desc");
public string Help => Loc.GetString("cmd-roleunban-help");
public async void Execute(IConsoleShell shell, string argStr, string[] args)
{
@@ -57,4 +57,14 @@ public sealed class RoleUnbanCommand : IConsoleCommand
shell.WriteLine($"Pardoned ban with id {banId}");
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
// Can't think of good way to do hint options for this
return args.Length switch
{
1 => CompletionResult.FromHint(Loc.GetString("cmd-roleunban-hint-1")),
_ => CompletionResult.Empty
};
}
}

View File

@@ -57,5 +57,10 @@ public sealed class AlertLevelDetail
/// The color that this alert level will show in-game in chat.
/// </summary>
[DataField("color")] public Color Color { get; } = Color.White;
/// <summary>
/// How long it takes for the shuttle to arrive when called.
/// </summary>
[DataField("shuttleTime")] public TimeSpan ShuttleTime { get; } = TimeSpan.FromMinutes(5);
}

View File

@@ -173,7 +173,7 @@ public sealed class AlertLevelSystem : EntitySystem
{
if (detail.Sound != null)
{
SoundSystem.Play(detail.Sound.GetSound(), Filter.Broadcast());
SoundSystem.Play(detail.Sound.GetSound(), Filter.Broadcast(), detail.Sound.Params);
}
else
{

View File

@@ -145,6 +145,10 @@ namespace Content.Server.Atmos.Components
if (internals == null) return;
IsConnected = internals.TryConnectTank(Owner);
EntitySystem.Get<SharedActionsSystem>().SetToggled(ToggleAction, IsConnected);
// Couldn't toggle!
if (!IsConnected) return;
_connectStream?.Stop();
if (_connectSound != null)
@@ -158,6 +162,7 @@ namespace Content.Server.Atmos.Components
if (!IsConnected) return;
IsConnected = false;
EntitySystem.Get<SharedActionsSystem>().SetToggled(ToggleAction, false);
GetInternalsComponent(owner)?.DisconnectTank();
_disconnectStream?.Stop();

View File

@@ -71,7 +71,7 @@ namespace Content.Server.Atmos.EntitySystems
private void OnAirtightReAnchor(EntityUid uid, AirtightComponent airtight, ref ReAnchorEvent args)
{
foreach (var gridId in new[] { args.OldGrid, args.GridId })
foreach (var gridId in new[] { args.OldGrid, args.Grid })
{
// Update and invalidate new position.
airtight.LastPosition = (gridId, args.TilePos);

View File

@@ -1,3 +1,4 @@
using System.Linq;
using Content.Server.Administration;
using Content.Server.Atmos.Components;
using Content.Shared.Administration;
@@ -16,7 +17,7 @@ public sealed partial class AtmosphereSystem
// Fix Grid Atmos command.
_consoleHost.RegisterCommand("fixgridatmos",
"Makes every tile on a grid have a roundstart gas mix.",
"fixgridatmos <grid Ids>", FixGridAtmosCommand);
"fixgridatmos <grid Ids>", FixGridAtmosCommand, FixGridAtmosCommandCompletions);
}
private void ShutdownCommands()
@@ -57,11 +58,11 @@ public sealed partial class AtmosphereSystem
mixtures[5].AdjustMoles(Gas.Plasma, Atmospherics.MolesCellGasMiner);
mixtures[5].Temperature = 5000f;
foreach (var gid in args)
foreach (var arg in args)
{
if(!EntityUid.TryParse(gid, out var euid))
if(!EntityUid.TryParse(arg, out var euid))
{
shell.WriteError($"Failed to parse euid '{gid}'.");
shell.WriteError($"Failed to parse euid '{arg}'.");
return;
}
@@ -100,4 +101,19 @@ public sealed partial class AtmosphereSystem
}
}
}
private CompletionResult FixGridAtmosCommandCompletions(IConsoleShell shell, string[] args)
{
MapId? playerMap = null;
if (shell.Player is { AttachedEntity: { } playerEnt })
playerMap = Transform(playerEnt).MapID;
var options = _mapManager.GetAllGrids()
.OrderByDescending(e => playerMap != null && e.ParentMapId == playerMap)
.ThenBy(e => (int) e.ParentMapId)
.ThenBy(e => (int) e.GridEntityId)
.Select(e => new CompletionOption(e.GridEntityId.ToString(), $"{MetaData(e.GridEntityId).EntityName} - Map {e.ParentMapId}"));
return CompletionResult.FromOptions(options);
}
}

View File

@@ -109,10 +109,7 @@ namespace Content.Server.Atmos.EntitySystems
private void OnGridRemoved(GridRemovalEvent ev)
{
if (_overlay.ContainsKey(ev.GridId))
{
_overlay.Remove(ev.GridId);
}
_overlay.Remove(ev.EntityUid);
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)

View File

@@ -0,0 +1,11 @@
using Content.Shared.Atmos.Miasma;
using Robust.Shared.GameStates;
namespace Content.Server.Atmos.Miasma;
[NetworkedComponent, RegisterComponent]
public sealed class FliesComponent : SharedFliesComponent
{
/// Need something to hold the ambient sound, at least until that system becomes more robust
public EntityUid VirtFlies;
}

View File

@@ -2,12 +2,10 @@ using Content.Shared.MobState;
using Content.Shared.Damage;
using Content.Shared.Atmos;
using Content.Server.Atmos.EntitySystems;
using Content.Server.Temperature.Components;
using Content.Server.Temperature.Systems;
using Content.Server.Body.Components;
using Content.Server.Popups;
using Content.Shared.Examine;
using Robust.Shared.Containers;
using Robust.Shared.Player;
namespace Content.Server.Atmos.Miasma
{
@@ -15,8 +13,8 @@ namespace Content.Server.Atmos.Miasma
{
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
/// Feel free to weak this if there are perf concerns
private float UpdateRate = 5f;
public override void Update(float frameTime)
{
@@ -26,18 +24,17 @@ namespace Content.Server.Atmos.Miasma
if (!perishable.Progressing)
continue;
if (TryComp<TemperatureComponent>(perishable.Owner, out var temp) && temp.CurrentTemperature < 274f)
continue;
perishable.DeathAccumulator += frameTime;
if (perishable.DeathAccumulator < perishable.RotAfter.TotalSeconds)
continue;
perishable.RotAccumulator += frameTime;
if (perishable.RotAccumulator < 1f)
if (perishable.RotAccumulator < UpdateRate) // This is where it starts to get noticable on larger animals, no need to run every second
continue;
perishable.RotAccumulator -= 1f;
perishable.RotAccumulator -= UpdateRate;
EnsureComp<FliesComponent>(perishable.Owner);
DamageSpecifier damage = new();
damage.DamageDict.Add("Blunt", 0.3); // Slowly accumulate enough to gib after like half an hour
@@ -49,21 +46,45 @@ namespace Content.Server.Atmos.Miasma
continue;
// We need a way to get the mass of the mob alone without armor etc in the future
float molRate = perishable.MolsPerSecondPerUnitMass * UpdateRate;
var tileMix = _atmosphereSystem.GetTileMixture(Transform(perishable.Owner).Coordinates);
if (tileMix != null)
tileMix.AdjustMoles(Gas.Miasma, perishable.MolsPerSecondPerUnitMass * physics.FixturesMass);
tileMix.AdjustMoles(Gas.Miasma, molRate * physics.FixturesMass);
}
}
public override void Initialize()
{
base.Initialize();
// Core rotting stuff
SubscribeLocalEvent<RottingComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<RottingComponent, OnTemperatureChangeEvent>(OnTempChange);
SubscribeLocalEvent<PerishableComponent, MobStateChangedEvent>(OnMobStateChanged);
SubscribeLocalEvent<PerishableComponent, BeingGibbedEvent>(OnGibbed);
SubscribeLocalEvent<PerishableComponent, ExaminedEvent>(OnExamined);
// Containers
SubscribeLocalEvent<AntiRottingContainerComponent, EntInsertedIntoContainerMessage>(OnEntInserted);
SubscribeLocalEvent<AntiRottingContainerComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
// Fly audiovisual stuff
SubscribeLocalEvent<FliesComponent, ComponentInit>(OnFliesInit);
SubscribeLocalEvent<FliesComponent, ComponentShutdown>(OnFliesShutdown);
}
private void OnShutdown(EntityUid uid, RottingComponent component, ComponentShutdown args)
{
RemComp<FliesComponent>(uid);
if (TryComp<PerishableComponent>(uid, out var perishable))
{
perishable.DeathAccumulator = 0;
perishable.RotAccumulator = 0;
}
}
private void OnTempChange(EntityUid uid, RottingComponent component, OnTemperatureChangeEvent args)
{
bool decompose = (args.CurrentTemperature > 274f);
ToggleDecomposition(uid, decompose);
}
private void OnMobStateChanged(EntityUid uid, PerishableComponent component, MobStateChangedEvent args)
@@ -77,7 +98,7 @@ namespace Content.Server.Atmos.Miasma
if (!TryComp<PhysicsComponent>(uid, out var physics))
return;
if (component.DeathAccumulator <= component.RotAfter.TotalSeconds)
if (!component.Rotting)
return;
var molsToDump = (component.MolsPerSecondPerUnitMass * physics.FixturesMass) * component.DeathAccumulator;
@@ -85,28 +106,69 @@ namespace Content.Server.Atmos.Miasma
if (tileMix != null)
tileMix.AdjustMoles(Gas.Miasma, molsToDump);
// Waste of entities to let these through
foreach (var part in args.GibbedParts)
{
EntityManager.QueueDeleteEntity(part);
}
EntityManager.DeleteEntity(part);
}
private void OnExamined(EntityUid uid, PerishableComponent component, ExaminedEvent args)
{
if (component.DeathAccumulator >= component.RotAfter.TotalSeconds)
if (component.Rotting)
args.PushMarkup(Loc.GetString("miasma-rotting"));
}
/// Containers
private void OnEntInserted(EntityUid uid, AntiRottingContainerComponent component, EntInsertedIntoContainerMessage args)
{
if (TryComp<PerishableComponent>(args.Entity, out var perishable))
perishable.Progressing = false;
ToggleDecomposition(args.Entity, false, perishable);
}
private void OnEntRemoved(EntityUid uid, AntiRottingContainerComponent component, EntRemovedFromContainerMessage args)
{
if (TryComp<PerishableComponent>(args.Entity, out var perishable))
ToggleDecomposition(args.Entity, true, perishable);
}
/// Fly stuff
private void OnFliesInit(EntityUid uid, FliesComponent component, ComponentInit args)
{
component.VirtFlies = EntityManager.SpawnEntity("AmbientSoundSourceFlies", Transform(uid).Coordinates);
Transform(component.VirtFlies).AttachParent(uid);
}
private void OnFliesShutdown(EntityUid uid, FliesComponent component, ComponentShutdown args)
{
EntityManager.DeleteEntity(component.VirtFlies);
}
/// Public functions
public void ToggleDecomposition(EntityUid uid, bool decompose, PerishableComponent? perishable = null)
{
if (!Resolve(uid, ref perishable))
return;
if (decompose == perishable.Progressing) // Saved a few cycles
return;
if (!HasComp<RottingComponent>(uid))
return;
if (!perishable.Rotting)
return;
if (decompose)
{
perishable.Progressing = true;
EnsureComp<FliesComponent>(uid);
return;
}
perishable.Progressing = false;
RemComp<FliesComponent>(uid);
}
}
}

View File

@@ -25,6 +25,8 @@ namespace Content.Server.Atmos.Miasma
/// </summary>
public TimeSpan RotAfter = TimeSpan.FromMinutes(5);
public bool Rotting => (DeathAccumulator > RotAfter.TotalSeconds);
/// <summary>
/// Gasses are released every second.
/// </summary>
@@ -36,6 +38,6 @@ namespace Content.Server.Atmos.Miasma
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("molsPerSecondPerUnitMass")]
public float MolsPerSecondPerUnitMass = 0.0035f;
public float MolsPerSecondPerUnitMass = 0.0025f;
}
}

View File

@@ -5,7 +5,7 @@ using Content.Server.Kitchen.Components;
using Content.Server.Mind.Components;
using Content.Shared.Body.Components;
using Content.Shared.MobState.Components;
using Content.Shared.Movement.EntitySystems;
using Content.Shared.Movement.Events;
using Robust.Shared.Timing;
namespace Content.Server.Body.Systems

View File

@@ -1,5 +1,6 @@
using Content.Server.Buckle.Components;
using Content.Server.Interaction;
using Content.Server.Storage.Components;
using Content.Shared.Buckle;
using Content.Shared.Interaction;
using Content.Shared.Verbs;
@@ -32,6 +33,7 @@ namespace Content.Server.Buckle.Systems
SubscribeLocalEvent<BuckleComponent, InteractHandEvent>(HandleInteractHand);
SubscribeLocalEvent<BuckleComponent, GetVerbsEvent<InteractionVerb>>(AddUnbuckleVerb);
SubscribeLocalEvent<BuckleComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
}
private void AddUnbuckleVerb(EntityUid uid, BuckleComponent component, GetVerbsEvent<InteractionVerb> args)
@@ -133,5 +135,11 @@ namespace Content.Server.Buckle.Systems
buckle.ReAttach(strap);
}
}
public void OnEntityStorageInsertAttempt(EntityUid uid, BuckleComponent comp, InsertIntoEntityStorageAttemptEvent args)
{
if (comp.Buckled)
args.Cancel();
}
}
}

View File

@@ -10,12 +10,14 @@ using Content.Shared.Cargo.BUI;
using Content.Shared.Cargo.Components;
using Content.Shared.Cargo.Events;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.CCVar;
using Content.Shared.Dataset;
using Content.Shared.GameTicking;
using Content.Shared.MobState.Components;
using Robust.Server.GameObjects;
using Robust.Server.Maps;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Random;
@@ -30,6 +32,7 @@ public sealed partial class CargoSystem
* Handles cargo shuttle mechanics, including cargo shuttle consoles.
*/
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IMapLoader _loader = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -50,8 +53,20 @@ public sealed partial class CargoSystem
private int _index;
/// <summary>
/// Whether cargo shuttles are enabled at all. Mainly used to disable cargo shuttle loading for performance reasons locally.
/// </summary>
private bool _enabled;
private void InitializeShuttle()
{
#if !FULL_RELEASE
_configManager.OverrideDefault(CCVars.CargoShuttles, false);
#endif
_enabled = _configManager.GetCVar(CCVars.CargoShuttles);
// Don't want to immediately call this as shuttles will get setup in the natural course of things.
_configManager.OnValueChanged(CCVars.CargoShuttles, SetCargoShuttleEnabled);
SubscribeLocalEvent<CargoShuttleComponent, MoveEvent>(OnCargoShuttleMove);
SubscribeLocalEvent<CargoShuttleConsoleComponent, ComponentStartup>(OnCargoShuttleConsoleStartup);
SubscribeLocalEvent<CargoShuttleConsoleComponent, CargoCallShuttleMessage>(OnCargoShuttleCall);
@@ -66,6 +81,31 @@ public sealed partial class CargoSystem
SubscribeLocalEvent<RoundRestartCleanupEvent>(OnRoundRestart);
}
private void ShutdownShuttle()
{
_configManager.UnsubValueChanged(CCVars.CargoShuttles, SetCargoShuttleEnabled);
}
private void SetCargoShuttleEnabled(bool value)
{
if (_enabled == value) return;
_enabled = value;
if (value)
{
Setup();
foreach (var station in EntityQuery<StationCargoOrderDatabaseComponent>(true))
{
AddShuttle(station);
}
}
else
{
CleanupShuttle();
}
}
#region Cargo Pilot Console
private void OnCargoPilotConsoleOpen(EntityUid uid, CargoPilotConsoleComponent component, AfterActivatableUIOpenEvent args)
@@ -245,31 +285,29 @@ public sealed partial class CargoSystem
{
Setup();
if (CargoMap == null || component.Shuttle != null) return;
if (CargoMap == null ||
component.Shuttle != null ||
component.CargoShuttleProto == null) return;
if (component.CargoShuttleProto != null)
{
var prototype = _protoMan.Index<CargoShuttlePrototype>(component.CargoShuttleProto);
var possibleNames = _protoMan.Index<DatasetPrototype>(prototype.NameDataset).Values;
var name = _random.Pick(possibleNames);
var prototype = _protoMan.Index<CargoShuttlePrototype>(component.CargoShuttleProto);
var possibleNames = _protoMan.Index<DatasetPrototype>(prototype.NameDataset).Values;
var name = _random.Pick(possibleNames);
var (_, gridId) = _loader.LoadBlueprint(CargoMap.Value, prototype.Path.ToString());
var shuttleUid = _mapManager.GetGridEuid(gridId!.Value);
var xform = Transform(shuttleUid);
MetaData(shuttleUid).EntityName = name;
var (_, shuttleUid) = _loader.LoadBlueprint(CargoMap.Value, prototype.Path.ToString());
var xform = Transform(shuttleUid!.Value);
MetaData(shuttleUid!.Value).EntityName = name;
// TODO: Something better like a bounds check.
xform.LocalPosition += 100 * _index;
var comp = EnsureComp<CargoShuttleComponent>(shuttleUid);
comp.Station = component.Owner;
comp.Coordinates = xform.Coordinates;
// TODO: Something better like a bounds check.
xform.LocalPosition += 100 * _index;
var comp = EnsureComp<CargoShuttleComponent>(shuttleUid!.Value);
comp.Station = component.Owner;
comp.Coordinates = xform.Coordinates;
component.Shuttle = shuttleUid;
comp.NextCall = _timing.CurTime + TimeSpan.FromSeconds(comp.Cooldown);
UpdateShuttleCargoConsoles(comp);
_index++;
_sawmill.Info($"Added cargo shuttle to {ToPrettyString(shuttleUid)}");
}
component.Shuttle = shuttleUid;
comp.NextCall = _timing.CurTime + TimeSpan.FromSeconds(comp.Cooldown);
UpdateShuttleCargoConsoles(comp);
_index++;
_sawmill.Info($"Added cargo shuttle to {ToPrettyString(shuttleUid!.Value)}");
}
private void SellPallets(CargoShuttleComponent component, StationBankAccountComponent bank)
@@ -490,10 +528,10 @@ public sealed partial class CargoSystem
private void OnRoundRestart(RoundRestartCleanupEvent ev)
{
Cleanup();
CleanupShuttle();
}
private void Cleanup()
private void CleanupShuttle()
{
if (CargoMap == null || !_mapManager.MapExists(CargoMap.Value))
{
@@ -508,13 +546,20 @@ public sealed partial class CargoSystem
// Shuttle may not have been in the cargo dimension (e.g. on the station map) so need to delete.
foreach (var comp in EntityQuery<CargoShuttleComponent>())
{
if (TryComp<StationCargoOrderDatabaseComponent>(comp.Station, out var station))
{
station.Shuttle = null;
}
QueueDel(comp.Owner);
}
}
private void Setup()
{
if (CargoMap != null && _mapManager.MapExists(CargoMap.Value)) return;
if (!_enabled || CargoMap != null && _mapManager.MapExists(CargoMap.Value))
{
return;
}
// It gets mapinit which is okay... buuutt we still want it paused to avoid power draining.
CargoMap = _mapManager.CreateMap();

View File

@@ -27,7 +27,8 @@ public sealed partial class CargoSystem : SharedCargoSystem
public override void Shutdown()
{
base.Shutdown();
Cleanup();
ShutdownShuttle();
CleanupShuttle();
}
private void OnStationInit(StationInitializedEvent ev)

View File

@@ -68,11 +68,11 @@ public sealed class PricingSystem : EntitySystem
mostValuable.Pop();
});
shell.WriteLine($"Grid {gid} appraised to {value} credits.");
shell.WriteLine($"Grid {gid} appraised to {value} spacebucks.");
shell.WriteLine($"The top most valuable items were:");
foreach (var (price, ent) in mostValuable)
{
shell.WriteLine($"- {ToPrettyString(ent)} @ {price} credits");
shell.WriteLine($"- {ToPrettyString(ent)} @ {price} spacebucks");
}
}
}

View File

@@ -224,12 +224,13 @@ public sealed partial class ChatSystem : SharedChatSystem
#region Private API
private void SendEntitySpeak(EntityUid source, string message, bool hideChat = false)
private void SendEntitySpeak(EntityUid source, string originalMessage, bool hideChat = false)
{
if (!_actionBlocker.CanSpeak(source)) return;
message = TransformSpeech(source, message);
(message, var channel) = GetRadioPrefix(source, message);
var (message, channel) = GetRadioPrefix(source, originalMessage);
message = TransformSpeech(source, message);
if (channel != null)
_listener.PingListeners(source, message, channel);
@@ -241,7 +242,11 @@ public sealed partial class ChatSystem : SharedChatSystem
var ev = new EntitySpokeEvent(message);
RaiseLocalEvent(source, ev);
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Say from {ToPrettyString(source):user}: {message}");
if (originalMessage == message)
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Say from {ToPrettyString(source):user}: {originalMessage}.");
else
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Say from {ToPrettyString(source):user}, original: {originalMessage}, transformed: {message}.");
}
private void SendEntityWhisper(EntityUid source, string message, bool hideChat = false)

View File

@@ -0,0 +1,30 @@
namespace Content.Server.Chemistry.Components;
[RegisterComponent]
public sealed class SolutionSpikerComponent : Component
{
/// <summary>
/// The source solution to take the reagents from in order
/// to spike the other solution container.
/// </summary>
[DataField("sourceSolution")]
public string SourceSolution { get; } = string.Empty;
/// <summary>
/// If spiking with this entity should ignore empty containers or not.
/// </summary>
[DataField("ignoreEmpty")]
public bool IgnoreEmpty { get; }
/// <summary>
/// What should pop up when spiking with this entity.
/// </summary>
[DataField("popup")]
public string Popup { get; } = "spike-solution-generic";
/// <summary>
/// What should pop up when spiking fails because the container was empty.
/// </summary>
[DataField("popupEmpty")]
public string PopupEmpty { get; } = "spike-solution-empty-generic";
}

View File

@@ -96,7 +96,7 @@ public sealed partial class SolutionContainerSystem : EntitySystem
|| !Resolve(uid, ref appearanceComponent, false))
return;
var filledVolumePercent = solution.CurrentVolume.Float() / solution.MaxVolume.Float();
var filledVolumePercent = Math.Min(1.0f, solution.CurrentVolume.Float() / solution.MaxVolume.Float());
appearanceComponent.SetData(SolutionContainerVisuals.VisualState,
new SolutionContainerVisualState(solution.Color, filledVolumePercent));
}

View File

@@ -0,0 +1,94 @@
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Components.SolutionManager;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Popups;
using Content.Shared.Chemistry.Components;
using Content.Shared.FixedPoint;
using Content.Shared.Interaction;
using Robust.Shared.Player;
namespace Content.Server.Chemistry.EntitySystems;
/// <summary>
/// Entity system used to handle when solution containers are 'spiked'
/// with another entity. Triggers the source entity afterwards.
/// Uses refillable solution as the target solution, as that indicates
/// 'easy' refills.
///
/// Examples of spikable entity interactions include pills being dropped into glasses,
/// eggs being cracked into bowls, and so on.
/// </summary>
public sealed class SolutionSpikableSystem : EntitySystem
{
[Dependency] private readonly SolutionContainerSystem _solutionSystem = default!;
[Dependency] private readonly TriggerSystem _triggerSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<RefillableSolutionComponent, InteractUsingEvent>(OnInteractUsing);
}
private void OnInteractUsing(EntityUid uid, RefillableSolutionComponent target, InteractUsingEvent args)
{
TrySpike(args.Used, args.Target, args.User, target);
}
/// <summary>
/// Immediately transfer all reagents from this entity, to the other entity.
/// The source entity will then be acted on by TriggerSystem.
/// </summary>
/// <param name="source">Source of the solution.</param>
/// <param name="target">Target to spike with the solution from source.</param>
/// <param name="user">User spiking the target solution.</param>
private void TrySpike(EntityUid source, EntityUid target, EntityUid user, RefillableSolutionComponent? spikableTarget = null,
SolutionSpikerComponent? spikableSource = null,
SolutionContainerManagerComponent? managerSource = null,
SolutionContainerManagerComponent? managerTarget = null)
{
if (!Resolve(source, ref spikableSource, ref managerSource, false)
|| !Resolve(target, ref spikableTarget, ref managerTarget, false)
|| !_solutionSystem.TryGetRefillableSolution(target, out var targetSolution, managerTarget, spikableTarget)
|| !managerSource.Solutions.TryGetValue(spikableSource.SourceSolution, out var sourceSolution))
{
return;
}
if (targetSolution.CurrentVolume == 0 && !spikableSource.IgnoreEmpty)
{
_popupSystem.PopupEntity(Loc.GetString(spikableSource.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, Filter.Entities(user));
return;
}
if (_solutionSystem.TryMixAndOverflow(target,
targetSolution,
sourceSolution,
targetSolution.MaxVolume,
out var overflow))
{
if (overflow.TotalVolume > 0)
{
RaiseLocalEvent(target, new SolutionSpikeOverflowEvent(overflow));
}
_popupSystem.PopupEntity(Loc.GetString(spikableSource.Popup, ("spiked-entity", target), ("spike-entity", source)), user, Filter.Entities(user));
sourceSolution.RemoveAllSolution();
_triggerSystem.Trigger(source);
}
}
}
public sealed class SolutionSpikeOverflowEvent : HandledEntityEventArgs
{
/// <summary>
/// The solution that's been overflowed from the spike.
/// </summary>
public Solution Overflow { get; }
public SolutionSpikeOverflowEvent(Solution overflow)
{
Overflow = overflow;
}
}

View File

@@ -25,7 +25,7 @@ namespace Content.Server.Chemistry.EntitySystems
private void AddSetTransferVerbs(EntityUid uid, SolutionTransferComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || !component.CanChangeTransferAmount)
if (!args.CanAccess || !args.CanInteract || !component.CanChangeTransferAmount || args.Hands == null)
return;
if (!EntityManager.TryGetComponent<ActorComponent?>(args.User, out var actor))

View File

@@ -1,6 +1,6 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Movement.EntitySystems;
using Content.Shared.Movement.Systems;
using Robust.Shared.Timing;
namespace Content.Server.Chemistry.ReagentEffects

View File

@@ -5,6 +5,7 @@ using Content.Server.Chat;
using Content.Server.Chat.Systems;
using Content.Server.Popups;
using Content.Server.RoundEnd;
using Content.Server.Shuttles.Systems;
using Content.Server.Station.Systems;
using Content.Shared.Access.Components;
using Content.Shared.Access.Systems;
@@ -15,13 +16,14 @@ namespace Content.Server.Communications
{
public sealed class CommunicationsConsoleSystem : EntitySystem
{
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly AlertLevelSystem _alertLevelSystem = default!;
[Dependency] private readonly ChatSystem _chatSystem = default!;
[Dependency] private readonly IdCardSystem _idCardSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly RoundEndSystem _roundEndSystem = default!;
[Dependency] private readonly ShuttleSystem _shuttle = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
private const int MaxMessageLength = 256;
@@ -29,7 +31,7 @@ namespace Content.Server.Communications
{
// All events that refresh the BUI
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertLevelChanged);
SubscribeLocalEvent<CommunicationsConsoleComponent, ComponentInit>((_, comp, _) => UpdateBoundUserInterface(comp));
SubscribeLocalEvent<CommunicationsConsoleComponent, ComponentInit>((_, comp, _) => UpdateCommsConsoleInterface(comp));
SubscribeLocalEvent<RoundEndSystemChangedEvent>(_ => OnGenericBroadcastEvent());
SubscribeLocalEvent<AlertLevelDelayFinishedEvent>(_ => OnGenericBroadcastEvent());
@@ -48,7 +50,7 @@ namespace Content.Server.Communications
if (comp.AlreadyRefreshed) continue;
if (comp.AnnouncementCooldownRemaining <= 0f)
{
UpdateBoundUserInterface(comp);
UpdateCommsConsoleInterface(comp);
comp.AlreadyRefreshed = true;
continue;
}
@@ -65,7 +67,7 @@ namespace Content.Server.Communications
{
foreach (var comp in EntityQuery<CommunicationsConsoleComponent>())
{
UpdateBoundUserInterface(comp);
UpdateCommsConsoleInterface(comp);
}
}
@@ -75,17 +77,32 @@ namespace Content.Server.Communications
/// <param name="args">Alert level changed event arguments</param>
private void OnAlertLevelChanged(AlertLevelChangedEvent args)
{
foreach (var comp in EntityQuery<CommunicationsConsoleComponent>())
foreach (var comp in EntityQuery<CommunicationsConsoleComponent>(true))
{
var entStation = _stationSystem.GetOwningStation(comp.Owner);
if (args.Station == entStation)
{
UpdateBoundUserInterface(comp);
UpdateCommsConsoleInterface(comp);
}
}
}
private void UpdateBoundUserInterface(CommunicationsConsoleComponent comp)
/// <summary>
/// Updates the UI for all comms consoles.
/// </summary>
public void UpdateCommsConsoleInterface()
{
foreach (var comp in EntityQuery<CommunicationsConsoleComponent>())
{
UpdateCommsConsoleInterface(comp);
}
}
/// <summary>
/// Updates the UI for a particular comms console.
/// </summary>
/// <param name="comp"></param>
public void UpdateCommsConsoleInterface(CommunicationsConsoleComponent comp)
{
var uid = comp.Owner;
@@ -144,6 +161,8 @@ namespace Content.Server.Communications
private bool CanCall(CommunicationsConsoleComponent comp)
{
if (_shuttle.EmergencyShuttleArrived) return false;
return comp.CanCallShuttle && _roundEndSystem.CanCall();
}
@@ -189,7 +208,7 @@ namespace Content.Server.Communications
comp.AnnouncementCooldownRemaining = comp.DelayBetweenAnnouncements;
comp.AlreadyRefreshed = false;
UpdateBoundUserInterface(comp);
UpdateCommsConsoleInterface(comp);
// allow admemes with vv
Loc.TryGetString(comp.AnnouncementDisplayName, out var title);
@@ -206,7 +225,7 @@ namespace Content.Server.Communications
private void OnCallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleCallEmergencyShuttleMessage message)
{
if (!comp.CanCallShuttle) return;
if (!CanCall(comp)) return;
if (message.Session.AttachedEntity is not {Valid: true} mob) return;
if (!CanUse(mob, uid))
{
@@ -218,7 +237,7 @@ namespace Content.Server.Communications
private void OnRecallShuttleMessage(EntityUid uid, CommunicationsConsoleComponent comp, CommunicationsConsoleRecallEmergencyShuttleMessage message)
{
if (!comp.CanCallShuttle) return;
if (!CanCall(comp)) return;
if (message.Session.AttachedEntity is not {Valid: true} mob) return;
if (!CanUse(mob, uid))
{

View File

@@ -216,11 +216,11 @@ namespace Content.Server.Cuffs.Components
if (isOwner)
{
SoundSystem.Play(cuff.StartBreakoutSound.GetSound(), Filter.Pvs(Owner), Owner);
SoundSystem.Play(cuff.StartBreakoutSound.GetSound(), Filter.Pvs(Owner, entityManager: _entMan), Owner);
}
else
{
SoundSystem.Play(cuff.StartUncuffSound.GetSound(), Filter.Pvs(Owner), Owner);
SoundSystem.Play(cuff.StartUncuffSound.GetSound(), Filter.Pvs(Owner, entityManager: _entMan), Owner);
}
var uncuffTime = isOwner ? cuff.BreakoutTime : cuff.UncuffTime;

View File

@@ -74,7 +74,7 @@ public sealed class NetworkConfiguratorSystem : EntitySystem
private void TryAddNetworkDevice(EntityUid? targetUid, EntityUid userUid, NetworkConfiguratorComponent configurator, DeviceNetworkComponent? device = null)
{
if (!targetUid.HasValue || !Resolve(targetUid.Value, ref device))
if (!targetUid.HasValue || !Resolve(targetUid.Value, ref device, false))
return;
if (string.IsNullOrEmpty(device.Address))

View File

@@ -16,11 +16,13 @@ using Robust.Shared.Player;
using Robust.Shared.Audio;
using Robust.Shared.Utility;
using Content.Shared.Tools.Components;
using Content.Server.Station.Systems;
namespace Content.Server.Disease
{
/// <summary>
/// Everything that's about disease diangosis and machines is in here
/// </summary>
public sealed class DiseaseDiagnosisSystem : EntitySystem
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
@@ -29,6 +31,8 @@ namespace Content.Server.Disease
[Dependency] private readonly InventorySystem _inventorySystem = default!;
[Dependency] private readonly PaperSystem _paperSystem = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
public override void Initialize()
{
base.Initialize();
@@ -36,9 +40,9 @@ namespace Content.Server.Disease
SubscribeLocalEvent<DiseaseSwabComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<DiseaseDiagnoserComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
SubscribeLocalEvent<DiseaseVaccineCreatorComponent, AfterInteractUsingEvent>(OnAfterInteractUsingVaccine);
/// Visuals
// Visuals
SubscribeLocalEvent<DiseaseMachineComponent, PowerChangedEvent>(OnPowerChanged);
/// Private Events
// Private Events
SubscribeLocalEvent<DiseaseDiagnoserComponent, DiseaseMachineFinishedEvent>(OnDiagnoserFinished);
SubscribeLocalEvent<DiseaseVaccineCreatorComponent, DiseaseMachineFinishedEvent>(OnVaccinatorFinished);
SubscribeLocalEvent<TargetSwabSuccessfulEvent>(OnTargetSwabSuccessful);
@@ -55,26 +59,29 @@ namespace Content.Server.Disease
public override void Update(float frameTime)
{
foreach (var uid in AddQueue)
{
EnsureComp<DiseaseMachineRunningComponent>(uid);
}
AddQueue.Clear();
foreach (var uid in RemoveQueue)
{
RemComp<DiseaseMachineRunningComponent>(uid);
}
RemoveQueue.Clear();
foreach (var (runningComp, diseaseMachine) in EntityQuery<DiseaseMachineRunningComponent, DiseaseMachineComponent>(false))
foreach (var (_, diseaseMachine) in EntityQuery<DiseaseMachineRunningComponent, DiseaseMachineComponent>())
{
if (diseaseMachine.Accumulator < diseaseMachine.Delay)
{
diseaseMachine.Accumulator += frameTime;
return;
}
diseaseMachine.Accumulator += frameTime;
diseaseMachine.Accumulator = 0;
var ev = new DiseaseMachineFinishedEvent(diseaseMachine);
RaiseLocalEvent(diseaseMachine.Owner, ev, false);
RemoveQueue.Enqueue(diseaseMachine.Owner);
while (diseaseMachine.Accumulator >= diseaseMachine.Delay)
{
diseaseMachine.Accumulator -= diseaseMachine.Delay;
var ev = new DiseaseMachineFinishedEvent(diseaseMachine);
RaiseLocalEvent(diseaseMachine.Owner, ev);
RemoveQueue.Enqueue(diseaseMachine.Owner);
}
}
}
@@ -245,7 +252,7 @@ namespace Content.Server.Disease
report.AddMarkup(cureResistLine);
report.PushNewline();
/// Add Cures
// Add Cures
if (disease.Cures.Count == 0)
{
report.AddMarkup(Loc.GetString("diagnoser-no-cures"));
@@ -265,6 +272,17 @@ namespace Content.Server.Disease
return report;
}
public bool ServerHasDisease(DiseaseServerComponent server, DiseasePrototype disease)
{
bool has = false;
foreach (var serverDisease in server.Diseases)
{
if (serverDisease.ID == disease.ID)
has = true;
}
return has;
}
///
/// Appearance stuff
///
@@ -327,18 +345,41 @@ namespace Content.Server.Disease
var isPowered = this.IsPowered(uid, EntityManager);
UpdateAppearance(uid, isPowered, false);
// spawn a piece of paper.
var printed = EntityManager.SpawnEntity(args.Machine.MachineOutput, Transform(uid).Coordinates);
var printed = Spawn(args.Machine.MachineOutput, Transform(uid).Coordinates);
if (!TryComp<PaperComponent>(printed, out var paper))
return;
var reportTitle = string.Empty;
string reportTitle;
FormattedMessage contents = new();
if (args.Machine.Disease != null)
{
reportTitle = Loc.GetString("diagnoser-disease-report", ("disease", args.Machine.Disease.Name));
contents = AssembleDiseaseReport(args.Machine.Disease);
} else
var known = false;
foreach (var server in EntityQuery<DiseaseServerComponent>(true))
{
if (_stationSystem.GetOwningStation(server.Owner) != _stationSystem.GetOwningStation(uid))
continue;
if (ServerHasDisease(server, args.Machine.Disease))
{
known = true;
}
else
{
server.Diseases.Add(args.Machine.Disease);
}
}
if (!known)
{
Spawn("ResearchDisk5000", Transform(uid).Coordinates);
}
}
else
{
reportTitle = Loc.GetString("diagnoser-disease-report-none");
contents.AddMarkup(Loc.GetString("diagnoser-disease-report-none-contents"));
@@ -351,13 +392,13 @@ namespace Content.Server.Disease
/// <summary>
/// Prints a vaccine that will vaccinate
/// against the disease on the inserted swab.
/// <summary>
/// </summary>
private void OnVaccinatorFinished(EntityUid uid, DiseaseVaccineCreatorComponent component, DiseaseMachineFinishedEvent args)
{
UpdateAppearance(uid, this.IsPowered(uid, EntityManager), false);
// spawn a vaccine
var vaxx = EntityManager.SpawnEntity(args.Machine.MachineOutput, Transform(uid).Coordinates);
var vaxx = Spawn(args.Machine.MachineOutput, Transform(uid).Coordinates);
if (!TryComp<DiseaseVaccineComponent>(vaxx, out var vaxxComp))
return;

View File

@@ -0,0 +1,15 @@
using Content.Shared.Disease;
namespace Content.Server.Disease.Components
{
[RegisterComponent]
public sealed class DiseaseServerComponent : Component
{
/// <summary>
/// Which diseases this server has information on.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public List<DiseasePrototype> Diseases = new();
}
}

View File

@@ -3,6 +3,7 @@ using Content.Server.UserInterface;
using Content.Server.Hands.Components;
using Content.Shared.Destructible;
using Content.Shared.Movement;
using Content.Shared.Movement.Events;
using Content.Shared.Verbs;
using Content.Shared.Popups;
using Robust.Server.GameObjects;

View File

@@ -7,6 +7,8 @@ using Content.Server.DoAfter;
using Content.Server.Hands.Components;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Server.Storage.Components;
using Content.Server.Storage.EntitySystems;
using Content.Shared.ActionBlocker;
using Content.Shared.Atmos;
using Content.Shared.Construction.Components;
@@ -18,8 +20,10 @@ using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Item;
using Content.Shared.Movement;
using Content.Shared.Movement.Events;
using Content.Shared.Throwing;
using Content.Shared.Verbs;
using Content.Shared.Storage.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Containers;
@@ -37,6 +41,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
[Dependency] private readonly AtmosphereSystem _atmosSystem = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
[Dependency] private readonly DumpableSystem _dumpableSystem = default!;
private readonly List<DisposalUnitComponent> _activeDisposals = new();
@@ -64,7 +69,7 @@ namespace Content.Server.Disposal.Unit.EntitySystems
// Verbs
SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<InteractionVerb>>(AddInsertVerb);
SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<AlternativeVerb>>(AddFlushEjectVerbs);
SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<AlternativeVerb>>(AddDisposalAltVerbs);
SubscribeLocalEvent<DisposalUnitComponent, GetVerbsEvent<Verb>>(AddClimbInsideVerb);
// Units
@@ -74,25 +79,48 @@ namespace Content.Server.Disposal.Unit.EntitySystems
SubscribeLocalEvent<DisposalUnitComponent, SharedDisposalUnitComponent.UiButtonPressedMessage>(OnUiButtonPressed);
}
private void AddFlushEjectVerbs(EntityUid uid, DisposalUnitComponent component, GetVerbsEvent<AlternativeVerb> args)
private void AddDisposalAltVerbs(EntityUid uid, DisposalUnitComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || component.Container.ContainedEntities.Count == 0)
if (!args.CanAccess || !args.CanInteract)
return;
// Verbs to flush the unit
AlternativeVerb flushVerb = new();
flushVerb.Act = () => Engage(component);
flushVerb.Text = Loc.GetString("disposal-flush-verb-get-data-text");
flushVerb.IconTexture = "/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png";
flushVerb.Priority = 1;
args.Verbs.Add(flushVerb);
// Behavior for if the disposals bin has items in it
if (component.Container.ContainedEntities.Count > 0)
{
// Verbs to flush the unit
AlternativeVerb flushVerb = new();
flushVerb.Act = () => Engage(component);
flushVerb.Text = Loc.GetString("disposal-flush-verb-get-data-text");
flushVerb.IconTexture = "/Textures/Interface/VerbIcons/delete_transparent.svg.192dpi.png";
flushVerb.Priority = 1;
args.Verbs.Add(flushVerb);
// Verb to eject the contents
AlternativeVerb ejectVerb = new()
{
Act = () => TryEjectContents(component),
Category = VerbCategory.Eject,
Text = Loc.GetString("disposal-eject-verb-contents")
};
args.Verbs.Add(ejectVerb);
}
// Behavior if using a trash bag & other dumpable containers
if (args.Using != null
&& TryComp<DumpableComponent>(args.Using.Value, out var dumpable)
&& TryComp<ServerStorageComponent>(args.Using.Value, out var storage)
&& storage.StoredEntities is { Count: > 0 })
{
// Verb to dump held container into disposal unit
AlternativeVerb dumpVerb = new()
{
Act = () => _dumpableSystem.StartDoAfter(args.Using.Value, args.Target, args.User, dumpable, storage),
Text = Loc.GetString("dump-disposal-verb-name", ("unit", args.Target)),
Priority = 2
};
args.Verbs.Add(dumpVerb);
}
// Verb to eject the contents
AlternativeVerb ejectVerb = new();
ejectVerb.Act = () => TryEjectContents(component);
ejectVerb.Category = VerbCategory.Eject;
ejectVerb.Text = Loc.GetString("disposal-eject-verb-contents");
args.Verbs.Add(ejectVerb);
}
private void AddClimbInsideVerb(EntityUid uid, DisposalUnitComponent component, GetVerbsEvent<Verb> args)

View File

@@ -154,7 +154,7 @@ namespace Content.Server.Doors.Components
BoltsDown = newBolts;
SoundSystem.Play(newBolts ? BoltDownSound.GetSound() : BoltUpSound.GetSound(), Filter.Broadcast(), Owner);
SoundSystem.Play(newBolts ? BoltDownSound.GetSound() : BoltUpSound.GetSound(), Filter.Pvs(Owner), Owner);
}
}
}

View File

@@ -18,8 +18,12 @@ using Robust.Shared.Containers;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Player;
using System.Linq;
using System.Threading;
using Content.Server.Tools.Systems;
using Content.Shared.Database;
using Content.Shared.Sound;
using Content.Shared.Tools.Components;
using Content.Shared.Verbs;
namespace Content.Server.Doors.Systems;
@@ -37,6 +41,9 @@ public sealed class DoorSystem : SharedDoorSystem
SubscribeLocalEvent<DoorComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<DoorComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(ConstructionSystem) });
// Mob prying doors
SubscribeLocalEvent<DoorComponent, GetVerbsEvent<AlternativeVerb>>(OnDoorAltVerb);
SubscribeLocalEvent<DoorComponent, PryFinishedEvent>(OnPryFinished);
SubscribeLocalEvent<DoorComponent, PryCancelledEvent>(OnPryCancelled);
SubscribeLocalEvent<DoorComponent, WeldableAttemptEvent>(OnWeldAttempt);
@@ -152,25 +159,43 @@ public sealed class DoorSystem : SharedDoorSystem
SetState(uid, DoorState.Closed, component);
}
private void OnDoorAltVerb(EntityUid uid, DoorComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanInteract || !TryComp<ToolComponent>(args.User, out var tool) || !tool.Qualities.Contains(component.PryingQuality)) return;
args.Verbs.Add(new AlternativeVerb()
{
Text = "Pry door",
Impact = LogImpact.Low,
Act = () => TryPryDoor(uid, args.User, args.User, component, true),
});
}
/// <summary>
/// Pry open a door. This does not check if the user is holding the required tool.
/// </summary>
private bool TryPryDoor(EntityUid target, EntityUid tool, EntityUid user, DoorComponent door)
private bool TryPryDoor(EntityUid target, EntityUid tool, EntityUid user, DoorComponent door, bool force = false)
{
if (door.BeingPried) return false;
if (door.State == DoorState.Welded)
return false;
var canEv = new BeforeDoorPryEvent(user);
RaiseLocalEvent(target, canEv, false);
if (!force)
{
var canEv = new BeforeDoorPryEvent(user);
RaiseLocalEvent(target, canEv, false);
if (canEv.Cancelled)
// mark handled, as airlock component will cancel after generating a pop-up & you don't want to pry a tile
// under a windoor.
return true;
if (canEv.Cancelled)
// mark handled, as airlock component will cancel after generating a pop-up & you don't want to pry a tile
// under a windoor.
return true;
}
var modEv = new DoorGetPryTimeModifierEvent();
RaiseLocalEvent(target, modEv, false);
door.BeingPried = true;
_toolSystem.UseTool(tool, user, target, 0f, modEv.PryTimeModifier * door.PryTime, door.PryingQuality,
new PryFinishedEvent(), new PryCancelledEvent(), target);

View File

@@ -21,7 +21,7 @@ public sealed partial class ExplosionSystem : EntitySystem
var grid = _mapManager.GetGrid(ev.GridId);
Dictionary<Vector2i, NeighborFlag> edges = new();
_gridEdges[ev.GridId] = edges;
_gridEdges[ev.EntityUid] = edges;
foreach (var tileRef in grid.GetAllTiles())
{
@@ -32,8 +32,8 @@ public sealed partial class ExplosionSystem : EntitySystem
private void OnGridRemoved(GridRemovalEvent ev)
{
_airtightMap.Remove(ev.GridId);
_gridEdges.Remove(ev.GridId);
_airtightMap.Remove(ev.EntityUid);
_gridEdges.Remove(ev.EntityUid);
}
/// <summary>

View File

@@ -33,6 +33,17 @@ public sealed class SpillableSystem : EntitySystem
SubscribeLocalEvent<SpillableComponent, LandEvent>(SpillOnLand);
SubscribeLocalEvent<SpillableComponent, GetVerbsEvent<Verb>>(AddSpillVerb);
SubscribeLocalEvent<SpillableComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<SpillableComponent, SolutionSpikeOverflowEvent>(OnSpikeOverflow);
}
private void OnSpikeOverflow(EntityUid uid, SpillableComponent component, SolutionSpikeOverflowEvent args)
{
if (!args.Handled)
{
SpillAt(args.Overflow, Transform(uid).Coordinates, "PuddleSmear");
}
args.Handled = true;
}
private void OnGotEquipped(EntityUid uid, SpillableComponent component, GotEquippedEvent args)

View File

@@ -19,6 +19,8 @@ namespace Content.Server.Foldable
SubscribeLocalEvent<FoldableComponent, StorageOpenAttemptEvent>(OnFoldableOpenAttempt);
SubscribeLocalEvent<FoldableComponent, GetVerbsEvent<AlternativeVerb>>(AddFoldVerb);
SubscribeLocalEvent<FoldableComponent, StoreThisAttemptEvent>(OnStoreThisAttempt);
}
private void OnFoldableOpenAttempt(EntityUid uid, FoldableComponent component, StorageOpenAttemptEvent args)
@@ -86,11 +88,17 @@ namespace Content.Server.Foldable
strap.Enabled = !component.IsFolded;
}
public void OnStoreThisAttempt(EntityUid uid, FoldableComponent comp, StoreThisAttemptEvent args)
{
if (comp.IsFolded)
args.Cancel();
}
#region Verb
private void AddFoldVerb(EntityUid uid, FoldableComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || !CanToggleFold(uid, component))
if (!args.CanAccess || !args.CanInteract || args.Hands == null || !CanToggleFold(uid, component))
return;
AlternativeVerb verb = new()

View File

@@ -0,0 +1,16 @@
namespace Content.Server.Forensics
{
/// <summary>
/// This controls fibers left by gloves on items,
/// which the forensics system uses.
/// </summary>
[RegisterComponent]
public sealed class FiberComponent : Component
{
[DataField("fiberMaterial")]
public string FiberMaterial = "fibers-synthetic";
[DataField("fiberColor")]
public string? FiberColor;
}
}

View File

@@ -0,0 +1,12 @@
namespace Content.Server.Forensics
{
/// <summary>
/// This component is for mobs that leave fingerprints.
/// </summary>
[RegisterComponent]
public sealed class FingerprintComponent : Component
{
[DataField("fingerprint")]
public string? Fingerprint;
}
}

View File

@@ -0,0 +1,10 @@
namespace Content.Server.Forensics
{
/// <summary>
/// This component stops the entity from leaving finger prints,
/// usually so fibres can be left instead.
/// </summary>
[RegisterComponent]
public sealed class FingerprintMaskComponent : Component
{}
}

View File

@@ -0,0 +1,19 @@
using System.Threading;
namespace Content.Server.Forensics
{
/// <summary>
/// Used to take a sample of someone's fingerprints.
/// </summary>
[RegisterComponent]
public sealed class ForensicPadComponent : Component
{
public CancellationTokenSource? CancelToken;
[DataField("scanDelay")]
public float ScanDelay = 3.0f;
public bool Used = false;
public String Sample = string.Empty;
}
}

View File

@@ -0,0 +1,27 @@
using System.Threading;
namespace Content.Server.Forensics
{
[RegisterComponent]
public sealed class ForensicScannerComponent : Component
{
public CancellationTokenSource? CancelToken;
/// <summary>
/// A list of fingerprint GUIDs that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public List<string> Fingerprints = new();
/// <summary>
/// A list of glove fibers that the forensic scanner found from the <see cref="ForensicsComponent"/> on an entity.
/// </summary>
[ViewVariables(VVAccess.ReadOnly)]
public List<string> Fibers = new();
/// <summary>
/// The time (in seconds) that it takes to scan an entity.
/// </summary>
[DataField("scanDelay")]
public float ScanDelay = 3.0f;
}
}

View File

@@ -0,0 +1,12 @@
namespace Content.Server.Forensics
{
[RegisterComponent]
public sealed class ForensicsComponent : Component
{
[DataField("fingerprints")]
public HashSet<string> Fingerprints = new();
[DataField("fibers")]
public HashSet<string> Fibers = new();
}
}

View File

@@ -0,0 +1,140 @@
using System.Threading;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Server.DoAfter;
using Content.Server.Popups;
using Robust.Shared.Player;
namespace Content.Server.Forensics
{
/// <summary>
/// Used to transfer fingerprints from entities to forensic pads.
/// </summary>
public sealed class ForensicPadSystem : EntitySystem
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ForensicPadComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<ForensicPadComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<TargetPadSuccessfulEvent>(OnTargetPadSuccessful);
SubscribeLocalEvent<PadCancelledEvent>(OnPadCancelled);
}
private void OnExamined(EntityUid uid, ForensicPadComponent component, ExaminedEvent args)
{
if (!args.IsInDetailsRange)
return;
if (!component.Used)
{
args.PushMarkup(Loc.GetString("forensic-pad-unused"));
return;
}
args.PushMarkup(Loc.GetString("forensic-pad-sample", ("sample", component.Sample)));
}
private void OnAfterInteract(EntityUid uid, ForensicPadComponent component, AfterInteractEvent args)
{
if (component.CancelToken != null || !args.CanReach || args.Target == null)
return;
if (HasComp<ForensicScannerComponent>(args.Target))
return;
args.Handled = true;
if (component.Used)
{
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-already-used"), args.Target.Value, Filter.Entities(args.User));
return;
}
if (_inventory.TryGetSlotEntity(args.Target.Value, "gloves", out var gloves))
{
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-gloves", ("target", args.Target.Value)), args.Target.Value, Filter.Entities(args.User));
return;
}
if (TryComp<FingerprintComponent>(args.Target, out var fingerprint) && fingerprint.Fingerprint != null)
{
if (args.User != args.Target)
{
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-user", ("target", args.Target.Value)), args.Target.Value, Filter.Entities(args.User));
_popupSystem.PopupEntity(Loc.GetString("forensic-pad-start-scan-target", ("user", args.User)), args.Target.Value, Filter.Entities(args.Target.Value));
}
StartScan(args.User, args.Target.Value, component, fingerprint.Fingerprint);
return;
}
if (TryComp<FiberComponent>(args.Target, out var fiber))
StartScan(args.User, args.Target.Value, component, string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
}
private void StartScan(EntityUid user, EntityUid target, ForensicPadComponent pad, string sample)
{
pad.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, pad.ScanDelay, pad.CancelToken.Token, target: target)
{
BroadcastFinishedEvent = new TargetPadSuccessfulEvent(user, target, pad.Owner, sample),
BroadcastCancelledEvent = new PadCancelledEvent(pad.Owner),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
NeedHand = true
});
}
/// <summary>
/// When the forensic pad is successfully used, take their fingerprint sample and flag the pad as used.
/// </summary>
private void OnTargetPadSuccessful(TargetPadSuccessfulEvent ev)
{
if (!EntityManager.TryGetComponent(ev.Pad, out ForensicPadComponent? component))
return;
component.CancelToken = null;
component.Sample = ev.Sample;
component.Used = true;
}
private void OnPadCancelled(PadCancelledEvent ev)
{
if (!EntityManager.TryGetComponent(ev.Pad, out ForensicPadComponent? component))
return;
component.CancelToken = null;
}
private sealed class PadCancelledEvent : EntityEventArgs
{
public EntityUid Pad;
public PadCancelledEvent(EntityUid pad)
{
Pad = pad;
}
}
private sealed class TargetPadSuccessfulEvent : EntityEventArgs
{
public EntityUid User;
public EntityUid? Target;
public EntityUid Pad;
public string Sample = string.Empty;
public TargetPadSuccessfulEvent(EntityUid user, EntityUid? target, EntityUid pad, string sample)
{
User = user;
Target = target;
Pad = pad;
Sample = sample;
}
}
}
}

View File

@@ -0,0 +1,169 @@
using System.Linq;
using System.Text; // todo: remove this stinky LINQy
using System.Threading;
using Content.Server.DoAfter;
using Content.Server.Paper;
using Content.Server.Popups;
using Content.Shared.Forensics;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
namespace Content.Server.Forensics
{
public sealed class ForensicScannerSystem : EntitySystem
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly PaperSystem _paperSystem = default!;
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ForensicScannerComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<ForensicScannerComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
SubscribeLocalEvent<ForensicScannerComponent, ForensicScannerPrintMessage>(OnPrint);
SubscribeLocalEvent<TargetScanSuccessfulEvent>(OnTargetScanSuccessful);
SubscribeLocalEvent<ScanCancelledEvent>(OnScanCancelled);
}
private void OnScanCancelled(ScanCancelledEvent ev)
{
if (!EntityManager.TryGetComponent(ev.Scanner, out ForensicScannerComponent? scanner))
return;
scanner.CancelToken = null;
}
private void OnTargetScanSuccessful(TargetScanSuccessfulEvent ev)
{
if (!EntityManager.TryGetComponent(ev.Scanner, out ForensicScannerComponent? scanner))
return;
scanner.CancelToken = null;
if (!TryComp<ForensicsComponent>(ev.Target, out var forensics))
return;
scanner.Fingerprints = forensics.Fingerprints.ToList();
scanner.Fibers = forensics.Fibers.ToList();
OpenUserInterface(ev.User, scanner);
}
private void OnAfterInteract(EntityUid uid, ForensicScannerComponent component, AfterInteractEvent args)
{
if (component.CancelToken != null || args.Target == null || !args.CanReach)
return;
component.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, component.ScanDelay, component.CancelToken.Token, target: args.Target)
{
BroadcastFinishedEvent = new TargetScanSuccessfulEvent(args.User, args.Target, component.Owner),
BroadcastCancelledEvent = new ScanCancelledEvent(component.Owner),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
NeedHand = true
});
}
private void OnAfterInteractUsing(EntityUid uid, ForensicScannerComponent component, AfterInteractUsingEvent args)
{
if (args.Handled || !args.CanReach)
return;
if (!TryComp<ForensicPadComponent>(args.Used, out var pad))
return;
foreach (var fiber in component.Fibers)
{
if (fiber == pad.Sample)
{
SoundSystem.Play("/Audio/Machines/Nuke/angry_beep.ogg", Filter.Pvs(uid), uid);
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-match-fiber"), uid, Filter.Entities(args.User));
return;
}
}
foreach (var fingerprint in component.Fingerprints)
{
if (fingerprint == pad.Sample)
{
SoundSystem.Play("/Audio/Machines/Nuke/angry_beep.ogg", Filter.Pvs(uid), uid);
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-match-fingerprint"), uid, Filter.Entities(args.User));
return;
}
}
SoundSystem.Play("/Audio/Machines/airlock_deny.ogg", Filter.Pvs(uid), uid);
_popupSystem.PopupEntity(Loc.GetString("forensic-scanner-match-none"), uid, Filter.Entities(args.User));
}
private void OpenUserInterface(EntityUid user, ForensicScannerComponent component)
{
if (!TryComp<ActorComponent>(user, out var actor))
return;
var ui = _uiSystem.GetUi(component.Owner, ForensicScannerUiKey.Key);
ui.Open(actor.PlayerSession);
ui.SendMessage(new ForensicScannerUserMessage(component.Fingerprints, component.Fibers));
}
private void OnPrint(EntityUid uid, ForensicScannerComponent component, ForensicScannerPrintMessage args)
{
if (!args.Session.AttachedEntity.HasValue || (component.Fibers.Count == 0 && component.Fingerprints.Count == 0)) return;
// spawn a piece of paper.
var printed = EntityManager.SpawnEntity("Paper", Transform(args.Session.AttachedEntity.Value).Coordinates);
_handsSystem.PickupOrDrop(args.Session.AttachedEntity, printed, checkActionBlocker: false);
if (!TryComp<PaperComponent>(printed, out var paper))
return;
MetaData(printed).EntityName = Loc.GetString("forensic-scanner-report-title");
var text = new StringBuilder();
text.AppendLine(Loc.GetString("forensic-scanner-interface-fingerprints"));
foreach (var fingerprint in component.Fingerprints)
{
text.AppendLine(fingerprint);
}
text.AppendLine();
text.AppendLine(Loc.GetString("forensic-scanner-interface-fibers"));
foreach (var fiber in component.Fibers)
{
text.AppendLine(fiber);
}
_paperSystem.SetContent(printed, text.ToString());
}
private sealed class ScanCancelledEvent : EntityEventArgs
{
public EntityUid Scanner;
public ScanCancelledEvent(EntityUid scanner)
{
Scanner = scanner;
}
}
private sealed class TargetScanSuccessfulEvent : EntityEventArgs
{
public EntityUid User;
public EntityUid? Target;
public EntityUid Scanner;
public TargetScanSuccessfulEvent(EntityUid user, EntityUid? target, EntityUid scanner)
{
User = user;
Target = target;
Scanner = scanner;
}
}
}
}

View File

@@ -0,0 +1,49 @@
using Content.Shared.Inventory;
using Content.Shared.Item;
using Robust.Shared.Random;
namespace Content.Server.Forensics
{
public sealed class ForensicsSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
public override void Initialize()
{
SubscribeLocalEvent<FingerprintComponent, UserInteractedWithItemEvent>(OnInteract);
SubscribeLocalEvent<FingerprintComponent, ComponentInit>(OnInit);
}
private void OnInteract(EntityUid uid, FingerprintComponent component, UserInteractedWithItemEvent args)
{
ApplyEvidence(args.User, args.Item);
}
private void OnInit(EntityUid uid, FingerprintComponent component, ComponentInit args)
{
component.Fingerprint = GenerateFingerprint();
}
private string GenerateFingerprint()
{
byte[] fingerprint = new byte[16];
_random.NextBytes(fingerprint);
return Convert.ToHexString(fingerprint);
}
private void ApplyEvidence(EntityUid user, EntityUid target)
{
var component = EnsureComp<ForensicsComponent>(target);
if (_inventory.TryGetSlotEntity(user, "gloves", out var gloves))
{
if (TryComp<FiberComponent>(gloves, out var fiber) && !string.IsNullOrEmpty(fiber.FiberMaterial))
component.Fibers.Add(string.IsNullOrEmpty(fiber.FiberColor) ? Loc.GetString("forensic-fibers", ("material", fiber.FiberMaterial)) : Loc.GetString("forensic-fibers-colored", ("color", fiber.FiberColor), ("material", fiber.FiberMaterial)));
if (HasComp<FingerprintMaskComponent>(gloves))
return;
}
if (TryComp<FingerprintComponent>(user, out var fingerprint))
component.Fingerprints.Add(fingerprint.Fingerprint ?? "");
}
}
}

View File

@@ -65,7 +65,7 @@ namespace Content.Server.GameTicking.Commands
var jobPrototype = _prototypeManager.Index<JobPrototype>(id);
if(stationJobs.TryGetJobSlot(station, jobPrototype, out var slots) == false || slots == 0)
{
shell.WriteLine($"{jobPrototype.Name} has no available slots.");
shell.WriteLine($"{jobPrototype.LocalizedName} has no available slots.");
return;
}
ticker.MakeJoinGame(player, station, id);

View File

@@ -54,7 +54,7 @@ namespace Content.Server.GameTicking
InitializeLobbyMusic();
InitializeLobbyBackground();
InitializeGamePreset();
DebugTools.Assert(_prototypeManager.Index<JobPrototype>(FallbackOverflowJob).Name == Loc.GetString(FallbackOverflowJobName),
DebugTools.Assert(_prototypeManager.Index<JobPrototype>(FallbackOverflowJob).Name == FallbackOverflowJobName,
"Overflow role does not have the correct name!");
InitializeGameRules();

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Server.CharacterAppearance.Components;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules.Configurations;
@@ -193,9 +193,14 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
foreach (var uid in _stationSystem.Stations)
{
var grid = Comp<IMapGridComponent>(uid).Grid;
aabb = aabb?.Union(grid.WorldAABB) ?? grid.WorldAABB;
if (TryComp<StationDataComponent>(uid, out var stationData))
{
foreach (var grid in stationData.Grids)
{
if (TryComp<IMapGridComponent>(grid, out var gridComp))
aabb = aabb?.Union(gridComp.Grid.WorldAABB) ?? gridComp.Grid.WorldAABB;
}
}
}
if (aabb != null)
@@ -204,12 +209,12 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
minRadius = MathF.Max(aabb.Value.Width, aabb.Value.Height);
}
var (_, gridId) = _mapLoader.LoadBlueprint(GameTicker.DefaultMap, map, new MapLoadOptions
var (_, gridUid) = _mapLoader.LoadBlueprint(GameTicker.DefaultMap, map, new MapLoadOptions
{
Offset = center + MathF.Max(minRadius, minRadius) + 1000f,
});
if (!gridId.HasValue)
if (!gridUid.HasValue)
{
Logger.ErrorS("NUKEOPS", $"Gridid was null when loading \"{map}\", aborting.");
foreach (var session in operatives)
@@ -219,7 +224,6 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
return;
}
var gridUid = _mapManager.GetGridEuid(gridId.Value);
// TODO: Loot table or something
var commanderGear = _prototypeManager.Index<StartingGearPrototype>("SyndicateCommanderGearFull");
var starterGear = _prototypeManager.Index<StartingGearPrototype>("SyndicateOperativeGearFull");
@@ -239,7 +243,7 @@ public sealed class NukeopsRuleSystem : GameRuleSystem
if (spawns.Count == 0)
{
spawns.Add(EntityManager.GetComponent<TransformComponent>(gridUid).Coordinates);
spawns.Add(EntityManager.GetComponent<TransformComponent>(gridUid.Value).Coordinates);
Logger.WarningS("nukies", $"Fell back to default spawn for nukies!");
}

View File

@@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using Content.Server.Cargo.Systems;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking.Rules.Configurations;
@@ -171,7 +171,7 @@ public sealed class PiratesRuleSystem : GameRuleSystem
return;
}
_pirateShip = _mapManager.GetGridEuid(gridId.Value);
_pirateShip = gridId.Value;
// TODO: Loot table or something
var pirateGear = _prototypeManager.Index<StartingGearPrototype>("PirateGear"); // YARRR

View File

@@ -4,6 +4,7 @@ using Content.Server.Ghost.Components;
using Content.Server.Mind;
using Content.Server.Mind.Components;
using Content.Server.Players;
using Content.Server.Storage.Components;
using Content.Server.Visible;
using Content.Server.Warps;
using Content.Shared.Actions;
@@ -11,7 +12,7 @@ using Content.Shared.Examine;
using Content.Shared.Follower;
using Content.Shared.Ghost;
using Content.Shared.MobState.Components;
using Content.Shared.Movement.EntitySystems;
using Content.Shared.Movement.Events;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Server.Player;
@@ -51,6 +52,7 @@ namespace Content.Server.Ghost
SubscribeNetworkEvent<GhostWarpToTargetRequestEvent>(OnGhostWarpToTargetRequest);
SubscribeLocalEvent<GhostComponent, BooActionEvent>(OnActionPerform);
SubscribeLocalEvent<GhostComponent, InsertIntoEntityStorageAttemptEvent>(OnEntityStorageInsertAttempt);
}
private void OnActionPerform(EntityUid uid, GhostComponent component, BooActionEvent args)
{
@@ -265,5 +267,10 @@ namespace Content.Server.Ghost
return players;
}
public void OnEntityStorageInsertAttempt(EntityUid uid, GhostComponent comp, InsertIntoEntityStorageAttemptEvent args)
{
args.Cancel();
}
}
}

View File

@@ -8,8 +8,10 @@ using Content.Server.Materials;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Server.Research;
using Content.Server.Stack;
using Content.Server.UserInterface;
using Content.Shared.Research.Components;
using Robust.Server.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Player;
@@ -274,14 +276,16 @@ namespace Content.Server.Lathe
case LatheServerSelectionMessage _:
if (!TryComp(uid, out ResearchClientComponent? researchClient)) return;
researchClient.OpenUserInterface(message.Session);
IoCManager.Resolve<IEntitySystemManager>()
.GetEntitySystem<UserInterfaceSystem>()
.TryOpen(uid, ResearchClientUiKey.Key, message.Session);
break;
case LatheServerSyncMessage _:
if (!TryComp(uid, out TechnologyDatabaseComponent? database)
|| !TryComp(uid, out ProtolatheDatabaseComponent? protoDatabase)) return;
if (database.SyncWithServer())
if (IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<ResearchSystem>().SyncWithServer(database))
protoDatabase.Sync();
break;

View File

@@ -16,6 +16,7 @@ using Content.Shared.Doors.Systems;
using Content.Shared.Interaction.Events;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Content.Shared.Spawners.Components;
using Content.Shared.Storage;
using Robust.Shared.Audio;
using Robust.Shared.Map;

View File

@@ -14,6 +14,7 @@ using Content.Shared.DragDrop;
using Content.Shared.Interaction;
using Content.Shared.MobState.Components;
using Content.Shared.Movement;
using Content.Shared.Movement.Events;
using Content.Shared.Preferences;
using Content.Shared.Verbs;
using Robust.Shared.Containers;

View File

@@ -69,7 +69,7 @@ namespace Content.Server.Morgue
private void AddCremateVerb(EntityUid uid, CrematoriumEntityStorageComponent component, GetVerbsEvent<AlternativeVerb> args)
{
if (!args.CanAccess || !args.CanInteract || component.Cooking || component.Open)
if (!args.CanAccess || !args.CanInteract || args.Hands == null || component.Cooking || component.Open )
return;
AlternativeVerb verb = new();

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