mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-15 04:30:57 +01:00
Merge branch 'master' into icarus
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
8
Content.Client/Atmos/Miasma/FliesComponent.cs
Normal file
8
Content.Client/Atmos/Miasma/FliesComponent.cs
Normal 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
|
||||
{ }
|
||||
41
Content.Client/Atmos/Miasma/FliesSystem.cs
Normal file
41
Content.Client/Atmos/Miasma/FliesSystem.cs
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'}"
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</PanelContainer.PanelOverride>
|
||||
<Label Name="PointCost"
|
||||
Access="Public"
|
||||
MinSize="40 32"
|
||||
MinSize="52 32"
|
||||
Align="Right" />
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Movement.EntitySystems;
|
||||
|
||||
namespace Content.Client.Clothing
|
||||
{
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Content.Client/Forensics/ForensicScannerMenu.xaml
Normal file
14
Content.Client/Forensics/ForensicScannerMenu.xaml
Normal 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>
|
||||
37
Content.Client/Forensics/ForensicScannerMenu.xaml.cs
Normal file
37
Content.Client/Forensics/ForensicScannerMenu.xaml.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Movement.Systems;
|
||||
using Content.Shared.Climbing;
|
||||
|
||||
namespace Content.Client.Movement.Components;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Climbing;
|
||||
using Content.Client.Movement.Systems;
|
||||
using Content.Shared.Climbing;
|
||||
|
||||
namespace Content.Client.Movement.Components;
|
||||
|
||||
|
||||
@@ -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();
|
||||
80
Content.Client/Movement/Systems/JetpackSystem.cs
Normal file
80
Content.Client/Movement/Systems/JetpackSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
41
Content.Client/Shuttles/UI/EmergencyConsoleWindow.xaml
Normal file
41
Content.Client/Shuttles/UI/EmergencyConsoleWindow.xaml
Normal 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>
|
||||
93
Content.Client/Shuttles/UI/EmergencyConsoleWindow.xaml.cs
Normal file
93
Content.Client/Shuttles/UI/EmergencyConsoleWindow.xaml.cs
Normal 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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
11
Content.Client/Spawners/TimedDespawnSystem.cs
Normal file
11
Content.Client/Spawners/TimedDespawnSystem.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
11
Content.Server/Atmos/Miasma/FliesComponent.cs
Normal file
11
Content.Server/Atmos/Miasma/FliesComponent.cs
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -27,7 +27,8 @@ public sealed partial class CargoSystem : SharedCargoSystem
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
Cleanup();
|
||||
ShutdownShuttle();
|
||||
CleanupShuttle();
|
||||
}
|
||||
|
||||
private void OnStationInit(StationInitializedEvent ev)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
15
Content.Server/Disease/Server/DiseaseServerComponent.cs
Normal file
15
Content.Server/Disease/Server/DiseaseServerComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
16
Content.Server/Forensics/Components/FiberComponent.cs
Normal file
16
Content.Server/Forensics/Components/FiberComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
12
Content.Server/Forensics/Components/FingerprintComponent.cs
Normal file
12
Content.Server/Forensics/Components/FingerprintComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{}
|
||||
}
|
||||
19
Content.Server/Forensics/Components/ForensicPadComponent.cs
Normal file
19
Content.Server/Forensics/Components/ForensicPadComponent.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
12
Content.Server/Forensics/Components/ForensicsComponent.cs
Normal file
12
Content.Server/Forensics/Components/ForensicsComponent.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
140
Content.Server/Forensics/Systems/ForensicPadSystem.cs
Normal file
140
Content.Server/Forensics/Systems/ForensicPadSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
169
Content.Server/Forensics/Systems/ForensicScannerSystem.cs
Normal file
169
Content.Server/Forensics/Systems/ForensicScannerSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
Content.Server/Forensics/Systems/ForensicsSystem.cs
Normal file
49
Content.Server/Forensics/Systems/ForensicsSystem.cs
Normal 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 ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user