mirror of
https://github.com/corvax-team/ss14-wl.git
synced 2026-02-14 19:29:57 +01:00
Merge remote-tracking branch 'upstream/master' into master-syndicate
# Conflicts: # Content.Packaging/ServerPackaging.cs # Resources/Prototypes/Accents/word_replacements.yml # Resources/Prototypes/Entities/Clothing/Neck/mantles.yml # Resources/Prototypes/Entities/Structures/Doors/Airlocks/highsec.yml # Resources/Prototypes/Maps/marathon.yml # Resources/Prototypes/Maps/packed.yml # Resources/Prototypes/Roles/Jobs/Engineering/atmospheric_technician.yml # Resources/Prototypes/Roles/Jobs/Security/detective.yml # Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/meta.json # Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/meta.json # Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/meta.json # Resources/Textures/Clothing/Head/Helmets/paramedhelm.rsi/meta.json # Resources/Textures/Clothing/Mask/plaguedoctormask.rsi/meta.json # Resources/Textures/Clothing/Neck/Cloaks/capcloakformal.rsi/meta.json # Resources/Textures/Clothing/Neck/mantles/capmantle.rsi/meta.json # Resources/Textures/Clothing/Neck/mantles/cemantle.rsi/meta.json # Resources/Textures/Clothing/Neck/mantles/cmomantle.rsi/meta.json # Resources/Textures/Clothing/Neck/mantles/hopmantle.rsi/meta.json # Resources/Textures/Clothing/Neck/mantles/hosmantle.rsi/meta.json # Resources/Textures/Clothing/Neck/mantles/rdmantle.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Armor/lingarmor.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/icon-open.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/open-equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/open-inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat.rsi/open-inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/icon-open.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/open-equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/open-inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_chem.rsi/open-inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/icon-open.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_cmo.rsi/open-equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_gene.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_robo.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_robo.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_robo.rsi/inhand-left.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_robo.rsi/inhand-right.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_robo.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_sci.rsi/equipped-OUTERCLOTHING-reptilian.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_sci.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_sci.rsi/open-equipped-OUTERCLOTHING-reptilian.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Coats/labcoat_viro.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/equipped-OUTERCLOTHING.png # Resources/Textures/Clothing/OuterClothing/Coats/rndcoat.rsi/icon.png # Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/Suits/atmos_firesuit.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coat.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatatmos.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcap.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcargo.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatce.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcentcom.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatchem.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatcmo.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatengi.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatgen.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coathop.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coathos.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coathydro.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatjani.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmed.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatmime.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatminer.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatparamed.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatqm.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatrd.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatrobo.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsci.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatsec.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatviro.rsi/meta.json # Resources/Textures/Clothing/OuterClothing/WinterCoats/coatwarden.rsi/meta.json # Resources/Textures/Clothing/Shoes/Boots/combatboots.rsi/meta.json # Resources/Textures/Clothing/Shoes/Specific/bling.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpskirt/atmosf.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpskirt/capformaldress.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpskirt/operative_s.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/atmos.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/capformal.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/centcom_officer.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/centcomformal.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/hosformal.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/journalist.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/operative.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/psychologist.rsi/meta.json # Resources/Textures/Clothing/Uniforms/Jumpsuit/reporter.rsi/meta.json # Resources/Textures/Mobs/Animals/regalrat.rsi/meta.json # Resources/Textures/Objects/Storage/boxes.rsi/meta.json # Resources/Textures/Objects/Weapons/Guns/Battery/antiquelasergun.rsi/meta.json # Resources/Textures/Structures/Doors/Airlocks/Glass/atmospherics.rsi/meta.json # Resources/Textures/Structures/Doors/Airlocks/Standard/atmospherics.rsi/meta.json # Resources/Textures/Structures/Doors/Airlocks/highsec/highsec.rsi/meta.json # Resources/Textures/Structures/Furniture/chairs.rsi/meta.json # Resources/Textures/Structures/Holo/holofan.rsi/meta.json # Resources/Textures/Structures/Holo/wetfloor.rsi/meta.json
This commit is contained in:
15
.github/workflows/check-crlf.yml
vendored
Normal file
15
.github/workflows/check-crlf.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
name: CRLF Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, reopened, synchronize, ready_for_review ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: CRLF Check
|
||||
if: github.event.pull_request.draft == false
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
- name: Check for CRLF
|
||||
run: Tools/check_crlf.py
|
||||
2
.github/workflows/close-master-pr.yml
vendored
2
.github/workflows/close-master-pr.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Close PR's on master
|
||||
name: Close PRs on master
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
13
.github/workflows/labeler-untriaged.yml
vendored
Normal file
13
.github/workflows/labeler-untriaged.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: "Labels: Untriaged"
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
add_label:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions-ecosystem/action-add-labels@v1
|
||||
with:
|
||||
labels: "Status: Untriaged"
|
||||
@@ -2,6 +2,7 @@ using Content.Client.Eui;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.Eui;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||
@@ -14,9 +15,8 @@ public sealed class AdminMessageEui : BaseEui
|
||||
public AdminMessageEui()
|
||||
{
|
||||
_popup = new AdminMessagePopupWindow();
|
||||
_popup.OnAcceptPressed += () => SendMessage(new Accept());
|
||||
_popup.OnDismissPressed += () => SendMessage(new Dismiss());
|
||||
_popup.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
_popup.OnAcceptPressed += () => SendMessage(new Dismiss(true));
|
||||
_popup.OnDismissPressed += () => SendMessage(new Dismiss(false));
|
||||
}
|
||||
|
||||
public override void HandleState(EuiStateBase state)
|
||||
@@ -26,13 +26,17 @@ public sealed class AdminMessageEui : BaseEui
|
||||
return;
|
||||
}
|
||||
|
||||
_popup.SetMessage(s.Message);
|
||||
_popup.SetDetails(s.AdminName, s.AddedOn);
|
||||
_popup.Timer = s.Time;
|
||||
_popup.SetState(s);
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
_popup.OpenCentered();
|
||||
_popup.UserInterfaceManager.WindowRoot.AddChild(_popup);
|
||||
LayoutContainer.SetAnchorPreset(_popup, LayoutContainer.LayoutPreset.Wide);
|
||||
}
|
||||
|
||||
public override void Closed()
|
||||
{
|
||||
_popup.Orphan();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<Control xmlns="https://spacestation14.io" Margin="0 0 0 8">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<RichTextLabel Name="Admin" Margin="0 0 0 4" />
|
||||
<RichTextLabel Name="Message" Margin="2 0 0 0" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminMessagePopupMessage : Control
|
||||
{
|
||||
public AdminMessagePopupMessage(AdminMessageEuiState.Message message)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Admin.SetMessage(FormattedMessage.FromMarkup(Loc.GetString(
|
||||
"admin-notes-message-admin",
|
||||
("admin", message.AdminName),
|
||||
("date", message.AddedOn.ToLocalTime()))));
|
||||
|
||||
Message.SetMessage(message.Text);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,36 @@
|
||||
<ui:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
VerticalExpand="True" HorizontalExpand="True"
|
||||
Title="{Loc admin-notes-message-window-title}"
|
||||
MinSize="600 170">
|
||||
<PanelContainer VerticalExpand="True" HorizontalExpand="True" StyleClasses="BackgroundDark">
|
||||
<ScrollContainer HScrollEnabled="False" VerticalExpand="True" HorizontalExpand="True" Margin="4">
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="10" VerticalAlignment="Bottom">
|
||||
<Label Name="AdminLabel" Text="Loading..." />
|
||||
<RichTextLabel Name="MessageLabel" />
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
<PanelContainer MouseFilter="Stop">
|
||||
<PanelContainer.PanelOverride>
|
||||
<!-- semi-transparent background -->
|
||||
<gfx:StyleBoxFlat BackgroundColor="#000000AA" />
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<Control HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="600">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<RichTextLabel Name="Description" />
|
||||
|
||||
<!-- Contains actual messages -->
|
||||
<ScrollContainer HScrollEnabled="False" Margin="4" VerticalExpand="True" ReturnMeasure="True" MaxHeight="400">
|
||||
<BoxContainer Orientation="Vertical" Name="MessageContainer" Margin="0 2 0 0" />
|
||||
</ScrollContainer>
|
||||
|
||||
<Label Name="WaitLabel" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="DismissButton"
|
||||
Text="{Loc 'admin-notes-message-dismiss'}" />
|
||||
Text="{Loc 'admin-notes-message-dismiss'}"
|
||||
Disabled="True"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenRight" />
|
||||
<Button Name="AcceptButton"
|
||||
Text="{Loc 'admin-notes-message-accept'}"
|
||||
Disabled="True" />
|
||||
Disabled="True"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</Control>
|
||||
</PanelContainer>
|
||||
</ui:FancyWindow>
|
||||
</Control>
|
||||
|
||||
@@ -1,56 +1,65 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Administration.UI.AdminRemarks;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminMessagePopupWindow : FancyWindow
|
||||
public sealed partial class AdminMessagePopupWindow : Control
|
||||
{
|
||||
private float _timer = float.MaxValue;
|
||||
public float Timer
|
||||
{
|
||||
get => _timer;
|
||||
set
|
||||
{
|
||||
WaitLabel.Text = Loc.GetString("admin-notes-message-wait", ("time", MathF.Floor(value)));
|
||||
_timer = value;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action? OnDismissPressed;
|
||||
|
||||
public event Action? OnAcceptPressed;
|
||||
|
||||
public AdminMessagePopupWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
|
||||
AcceptButton.OnPressed += OnAcceptButtonPressed;
|
||||
DismissButton.OnPressed += OnDismissButtonPressed;
|
||||
}
|
||||
|
||||
public void SetMessage(string message)
|
||||
public float Timer
|
||||
{
|
||||
MessageLabel.SetMessage(message);
|
||||
get => _timer;
|
||||
private set
|
||||
{
|
||||
WaitLabel.Text = Loc.GetString("admin-notes-message-wait", ("time", MathF.Floor(value)));
|
||||
_timer = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDetails(string adminName, DateTime addedOn)
|
||||
public void SetState(AdminMessageEuiState state)
|
||||
{
|
||||
AdminLabel.Text = Loc.GetString("admin-notes-message-admin", ("admin", adminName), ("date", addedOn));
|
||||
Timer = (float) state.Time.TotalSeconds;
|
||||
|
||||
MessageContainer.RemoveAllChildren();
|
||||
|
||||
foreach (var message in state.Messages)
|
||||
{
|
||||
MessageContainer.AddChild(new AdminMessagePopupMessage(message));
|
||||
}
|
||||
|
||||
Description.SetMessage(FormattedMessage.FromMarkup(Loc.GetString("admin-notes-message-desc", ("count", state.Messages.Length))));
|
||||
}
|
||||
|
||||
private void OnDismissButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
OnDismissPressed?.Invoke();
|
||||
Close();
|
||||
}
|
||||
|
||||
private void OnAcceptButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
OnAcceptPressed?.Invoke();
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -70,6 +79,7 @@ public sealed partial class AdminMessagePopupWindow : FancyWindow
|
||||
else
|
||||
{
|
||||
AcceptButton.Disabled = false;
|
||||
DismissButton.Disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,6 +256,8 @@ public sealed class AtmosDebugOverlay : Overlay
|
||||
handle.DrawString(_font, pos, $"Map: {data.MapAtmosphere}");
|
||||
pos += offset;
|
||||
handle.DrawString(_font, pos, $"NoGrid: {data.NoGrid}");
|
||||
pos += offset;
|
||||
handle.DrawString(_font, pos, $"Immutable: {data.Immutable}");
|
||||
}
|
||||
|
||||
private void GetGrids(MapId mapId, Box2Rotated box)
|
||||
|
||||
@@ -8,7 +8,6 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -23,7 +22,7 @@ namespace Content.Client.Atmos.Overlays
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
// Gas overlays
|
||||
@@ -79,7 +78,8 @@ namespace Content.Client.Atmos.Overlays
|
||||
var rsi = resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
|
||||
var stateId = animated.RsiState;
|
||||
|
||||
if (!rsi.TryGetState(stateId, out var state)) continue;
|
||||
if (!rsi.TryGetState(stateId, out var state))
|
||||
continue;
|
||||
|
||||
_frames[i] = state.GetFrames(RsiDirection.South);
|
||||
_frameDelays[i] = state.GetDelays();
|
||||
@@ -111,7 +111,8 @@ namespace Content.Client.Atmos.Overlays
|
||||
for (var i = 0; i < _gasCount; i++)
|
||||
{
|
||||
var delays = _frameDelays[i];
|
||||
if (delays.Length == 0) continue;
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _frameCounter[i];
|
||||
_timer[i] += args.DeltaSeconds;
|
||||
@@ -127,7 +128,8 @@ namespace Content.Client.Atmos.Overlays
|
||||
for (var i = 0; i < FireStates; i++)
|
||||
{
|
||||
var delays = _fireFrameDelays[i];
|
||||
if (delays.Length == 0) continue;
|
||||
if (delays.Length == 0)
|
||||
continue;
|
||||
|
||||
var frameCount = _fireFrameCounter[i];
|
||||
_fireTimer[i] += args.DeltaSeconds;
|
||||
@@ -161,26 +163,10 @@ namespace Content.Client.Atmos.Overlays
|
||||
var mapUid = _mapManager.GetMapEntityId(args.MapId);
|
||||
|
||||
if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
|
||||
{
|
||||
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
|
||||
var topRight = args.WorldAABB.TopRight.Ceiled();
|
||||
DrawMapOverlay(drawHandle, args, mapUid, atmos);
|
||||
|
||||
for (var x = bottomLeft.X; x <= topRight.X; x++)
|
||||
{
|
||||
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
|
||||
{
|
||||
var tilePosition = new Vector2(x, y);
|
||||
|
||||
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
|
||||
{
|
||||
var opacity = atmos.OverlayData.Opacity[i];
|
||||
|
||||
if (opacity > 0)
|
||||
args.WorldHandle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// TODO: WorldBounds callback.
|
||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
|
||||
@@ -265,5 +251,41 @@ namespace Content.Client.Atmos.Overlays
|
||||
drawHandle.UseShader(null);
|
||||
drawHandle.SetTransform(Matrix3.Identity);
|
||||
}
|
||||
|
||||
private void DrawMapOverlay(
|
||||
DrawingHandleWorld handle,
|
||||
OverlayDrawArgs args,
|
||||
EntityUid map,
|
||||
MapAtmosphereComponent atmos)
|
||||
{
|
||||
var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
|
||||
|
||||
// map-grid atmospheres get drawn above grids
|
||||
if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
|
||||
return;
|
||||
|
||||
// Normal map atmospheres get drawn below grids
|
||||
if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
|
||||
return;
|
||||
|
||||
var bottomLeft = args.WorldAABB.BottomLeft.Floored();
|
||||
var topRight = args.WorldAABB.TopRight.Ceiled();
|
||||
|
||||
for (var x = bottomLeft.X; x <= topRight.X; x++)
|
||||
{
|
||||
for (var y = bottomLeft.Y; y <= topRight.Y; y++)
|
||||
{
|
||||
var tilePosition = new Vector2(x, y);
|
||||
|
||||
for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
|
||||
{
|
||||
var opacity = atmos.OverlayData.Opacity[i];
|
||||
|
||||
if (opacity > 0)
|
||||
handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Content.Client.Construction
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
|
||||
private readonly Dictionary<int, EntityUid> _ghosts = new();
|
||||
@@ -195,7 +196,7 @@ namespace Content.Client.Construction
|
||||
return false;
|
||||
|
||||
// This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
|
||||
var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager));
|
||||
var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
|
||||
if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ namespace Content.Client.ContextMenu.UI
|
||||
if (_combatMode.IsInCombatMode(args.Session?.AttachedEntity))
|
||||
return false;
|
||||
|
||||
var coords = args.Coordinates.ToMap(_entityManager);
|
||||
var coords = args.Coordinates.ToMap(_entityManager, _xform);
|
||||
|
||||
if (_verbSystem.TryGetEntityMenuEntities(coords, out var entities))
|
||||
OpenRootMenu(entities);
|
||||
|
||||
@@ -22,6 +22,9 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
|
||||
private const string AnimationKey = "disposal_unit_animation";
|
||||
|
||||
private const string DefaultFlushState = "disposal-flush";
|
||||
private const string DefaultChargeState = "disposal-charging";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -101,12 +104,18 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == VisualState.Anchored);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseFlush, state is VisualState.Flushing or VisualState.Charging);
|
||||
|
||||
var chargingState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseCharging, out var chargingLayer)
|
||||
? sprite.LayerGetState(chargingLayer)
|
||||
: new RSI.StateId(DefaultChargeState);
|
||||
|
||||
// This is a transient state so not too worried about replaying in range.
|
||||
if (state == VisualState.Flushing)
|
||||
{
|
||||
if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
|
||||
{
|
||||
var flushState = new RSI.StateId("disposal-flush");
|
||||
var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseFlush, out var flushLayer)
|
||||
? sprite.LayerGetState(flushLayer)
|
||||
: new RSI.StateId(DefaultFlushState);
|
||||
|
||||
// Setup the flush animation to play
|
||||
var anim = new Animation
|
||||
@@ -124,7 +133,7 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
// Return to base state (though, depending on how the unit is
|
||||
// configured we might get an appearance change event telling
|
||||
// us to go to charging state)
|
||||
new AnimationTrackSpriteFlick.KeyFrame("disposal-charging", (float) unit.FlushDelay.TotalSeconds)
|
||||
new AnimationTrackSpriteFlick.KeyFrame(chargingState, (float) unit.FlushDelay.TotalSeconds)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -147,7 +156,7 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
}
|
||||
else if (state == VisualState.Charging)
|
||||
{
|
||||
sprite.LayerSetState(DisposalUnitVisualLayers.BaseFlush, new RSI.StateId("disposal-charging"));
|
||||
sprite.LayerSetState(DisposalUnitVisualLayers.BaseFlush, chargingState);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -104,7 +104,7 @@ namespace Content.Client.Gameplay
|
||||
|
||||
public IEnumerable<EntityUid> GetClickableEntities(EntityCoordinates coordinates)
|
||||
{
|
||||
return GetClickableEntities(coordinates.ToMap(_entityManager));
|
||||
return GetClickableEntities(coordinates.ToMap(_entityManager, _entitySystemManager.GetEntitySystem<SharedTransformSystem>()));
|
||||
}
|
||||
|
||||
public IEnumerable<EntityUid> GetClickableEntities(MapCoordinates coordinates)
|
||||
|
||||
@@ -127,17 +127,16 @@ public sealed partial class MarkingPicker : Control
|
||||
IoCManager.InjectDependencies(this);
|
||||
IoCManager.Instance!.TryResolveType(out _sponsorsManager); // Corvax-Sponsors
|
||||
|
||||
SetupCategoryButtons();
|
||||
CMarkingCategoryButton.OnItemSelected += OnCategoryChange;
|
||||
CMarkingsUnused.OnItemSelected += item =>
|
||||
_selectedUnusedMarking = CMarkingsUnused[item.ItemIndex];
|
||||
|
||||
CMarkingAdd.OnPressed += args =>
|
||||
CMarkingAdd.OnPressed += _ =>
|
||||
MarkingAdd();
|
||||
|
||||
CMarkingsUsed.OnItemSelected += OnUsedMarkingSelected;
|
||||
|
||||
CMarkingRemove.OnPressed += args =>
|
||||
CMarkingRemove.OnPressed += _ =>
|
||||
MarkingRemove();
|
||||
|
||||
CMarkingRankUp.OnPressed += _ => SwapMarkingUp();
|
||||
@@ -149,16 +148,34 @@ public sealed partial class MarkingPicker : Control
|
||||
private void SetupCategoryButtons()
|
||||
{
|
||||
CMarkingCategoryButton.Clear();
|
||||
|
||||
var validCategories = new List<MarkingCategories>();
|
||||
for (var i = 0; i < _markingCategories.Count; i++)
|
||||
{
|
||||
if (_ignoreCategories.Contains(_markingCategories[i]))
|
||||
var category = _markingCategories[i];
|
||||
var markings = GetMarkings(category);
|
||||
if (_ignoreCategories.Contains(category) ||
|
||||
markings.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
CMarkingCategoryButton.AddItem(Loc.GetString($"markings-category-{_markingCategories[i].ToString()}"), i);
|
||||
validCategories.Add(category);
|
||||
CMarkingCategoryButton.AddItem(Loc.GetString($"markings-category-{category.ToString()}"), i);
|
||||
}
|
||||
|
||||
if (validCategories.Contains(_selectedMarkingCategory))
|
||||
{
|
||||
CMarkingCategoryButton.SelectId(_markingCategories.IndexOf(_selectedMarkingCategory));
|
||||
}
|
||||
else if (validCategories.Count > 0)
|
||||
{
|
||||
_selectedMarkingCategory = validCategories[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectedMarkingCategory = MarkingCategories.Chest;
|
||||
}
|
||||
CMarkingCategoryButton.SelectId(_markingCategories.IndexOf(_selectedMarkingCategory));
|
||||
}
|
||||
|
||||
private string GetMarkingName(MarkingPrototype marking) => Loc.GetString($"marking-{marking.ID}");
|
||||
@@ -182,16 +199,21 @@ public sealed partial class MarkingPicker : Control
|
||||
return result;
|
||||
}
|
||||
|
||||
private IReadOnlyDictionary<string, MarkingPrototype> GetMarkings(MarkingCategories category)
|
||||
{
|
||||
return IgnoreSpecies
|
||||
? _markingManager.MarkingsByCategoryAndSex(category, _currentSex)
|
||||
: _markingManager.MarkingsByCategoryAndSpeciesAndSex(category, _currentSpecies, _currentSex);
|
||||
}
|
||||
|
||||
public void Populate(string filter)
|
||||
{
|
||||
SetupCategoryButtons();
|
||||
|
||||
CMarkingsUnused.Clear();
|
||||
_selectedUnusedMarking = null;
|
||||
|
||||
var markings = IgnoreSpecies
|
||||
? _markingManager.MarkingsByCategoryAndSex(_selectedMarkingCategory, _currentSex)
|
||||
: _markingManager.MarkingsByCategoryAndSpeciesAndSex(_selectedMarkingCategory, _currentSpecies, _currentSex);
|
||||
|
||||
var sortedMarkings = markings.Values.Where(m =>
|
||||
var sortedMarkings = GetMarkings(_selectedMarkingCategory).Values.Where(m =>
|
||||
m.ID.ToLower().Contains(filter.ToLower()) ||
|
||||
GetMarkingName(m).ToLower().Contains(filter.ToLower())
|
||||
).OrderBy(p => Loc.GetString(GetMarkingName(p)));
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Content.Client.IconSmoothing
|
||||
var xform = Transform(uid);
|
||||
if (xform.Anchored)
|
||||
{
|
||||
component.LastPosition = _mapManager.TryGetGrid(xform.GridUid, out var grid)
|
||||
component.LastPosition = TryComp<MapGridComponent>(xform.GridUid, out var grid)
|
||||
? (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates))
|
||||
: (null, new Vector2i(0, 0));
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Content.Client.IconSmoothing
|
||||
|
||||
Vector2i pos;
|
||||
|
||||
if (transform.Anchored && _mapManager.TryGetGrid(transform.GridUid, out var grid))
|
||||
if (transform.Anchored && TryComp<MapGridComponent>(transform.GridUid, out var grid))
|
||||
{
|
||||
pos = grid.CoordinatesToTile(transform.Coordinates);
|
||||
}
|
||||
@@ -144,7 +144,7 @@ namespace Content.Client.IconSmoothing
|
||||
if (comp.LastPosition is not (EntityUid gridId, Vector2i oldPos))
|
||||
return;
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridId, out grid))
|
||||
if (!TryComp(gridId, out grid))
|
||||
return;
|
||||
|
||||
pos = oldPos;
|
||||
@@ -206,7 +206,7 @@ namespace Content.Client.IconSmoothing
|
||||
{
|
||||
var directions = DirectionFlag.None;
|
||||
|
||||
if (_mapManager.TryGetGrid(xform.GridUid, out grid))
|
||||
if (TryComp(xform.GridUid, out grid))
|
||||
{
|
||||
var pos = grid.TileIndicesFor(xform.Coordinates);
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace Content.Client.IconSmoothing
|
||||
|
||||
if (xform.Anchored)
|
||||
{
|
||||
if (!_mapManager.TryGetGrid(xform.GridUid, out grid))
|
||||
if (!TryComp(xform.GridUid, out grid))
|
||||
{
|
||||
Log.Error($"Failed to calculate IconSmoothComponent sprite in {uid} because grid {xform.GridUid} was missing.");
|
||||
return;
|
||||
|
||||
@@ -83,7 +83,7 @@ public sealed partial class MappingSystem : EntitySystem
|
||||
if (tileDef is not ContentTileDefinition contentTileDef)
|
||||
return;
|
||||
|
||||
var tileIcon = contentTileDef.IsSpace
|
||||
var tileIcon = contentTileDef.MapAtmosphere
|
||||
? _spaceIcon
|
||||
: new Texture(contentTileDef.Sprite!.Value);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -12,7 +13,6 @@ namespace Content.Client.Movement.Systems;
|
||||
public sealed class JetpackSystem : SharedJetpackSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly ClothingSystem _clothing = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
@@ -75,7 +75,7 @@ public sealed class JetpackSystem : SharedJetpackSystem
|
||||
var coordinates = uidXform.Coordinates;
|
||||
var gridUid = coordinates.GetGridUid(EntityManager);
|
||||
|
||||
if (_mapManager.TryGetGrid(gridUid, out var grid))
|
||||
if (TryComp<MapGridComponent>(gridUid, out var grid))
|
||||
{
|
||||
coordinates = new EntityCoordinates(gridUid.Value, grid.WorldToLocal(coordinates.ToMapPos(EntityManager, _transform)));
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Content.Client.NPC
|
||||
[Dependency] private readonly IResourceCache _cache = default!;
|
||||
[Dependency] private readonly NPCSteeringSystem _steering = default!;
|
||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
public PathfindingDebugMode Modes
|
||||
{
|
||||
@@ -39,7 +40,7 @@ namespace Content.Client.NPC
|
||||
}
|
||||
else if (!overlayManager.HasOverlay<PathfindingOverlay>())
|
||||
{
|
||||
overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem));
|
||||
overlayManager.AddOverlay(new PathfindingOverlay(EntityManager, _eyeManager, _inputManager, _mapManager, _cache, this, _mapSystem, _transformSystem));
|
||||
}
|
||||
|
||||
if ((value & PathfindingDebugMode.Steering) != 0x0)
|
||||
@@ -140,6 +141,7 @@ namespace Content.Client.NPC
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly PathfindingSystem _system;
|
||||
private readonly MapSystem _mapSystem;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace | OverlaySpace.WorldSpace;
|
||||
|
||||
@@ -153,7 +155,8 @@ namespace Content.Client.NPC
|
||||
IMapManager mapManager,
|
||||
IResourceCache cache,
|
||||
PathfindingSystem system,
|
||||
MapSystem mapSystem)
|
||||
MapSystem mapSystem,
|
||||
SharedTransformSystem transformSystem)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_eyeManager = eyeManager;
|
||||
@@ -161,6 +164,7 @@ namespace Content.Client.NPC
|
||||
_mapManager = mapManager;
|
||||
_system = system;
|
||||
_mapSystem = mapSystem;
|
||||
_transformSystem = transformSystem;
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
@@ -480,7 +484,7 @@ namespace Content.Client.NPC
|
||||
if (neighborPoly.NetEntity != poly.GraphUid)
|
||||
{
|
||||
color = Color.Green;
|
||||
var neighborMap = _entManager.GetCoordinates(neighborPoly).ToMap(_entManager);
|
||||
var neighborMap = _entManager.GetCoordinates(neighborPoly).ToMap(_entManager, _transformSystem);
|
||||
|
||||
if (neighborMap.MapId != args.MapId)
|
||||
continue;
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Content.Client.NodeContainer
|
||||
|
||||
|
||||
var xform = _entityManager.GetComponent<TransformComponent>(_entityManager.GetEntity(node.Entity));
|
||||
if (!_mapManager.TryGetGrid(xform.GridUid, out var grid))
|
||||
if (!_entityManager.TryGetComponent<MapGridComponent>(xform.GridUid, out var grid))
|
||||
return;
|
||||
var gridTile = grid.TileIndicesFor(xform.Coordinates);
|
||||
|
||||
@@ -145,7 +145,7 @@ namespace Content.Client.NodeContainer
|
||||
|
||||
foreach (var (gridId, gridDict) in _gridIndex)
|
||||
{
|
||||
var grid = _mapManager.GetGrid(gridId);
|
||||
var grid = _entityManager.GetComponent<MapGridComponent>(gridId);
|
||||
var (_, _, worldMatrix, invMatrix) = _entityManager.GetComponent<TransformComponent>(gridId).GetWorldPositionRotationMatrixWithInv();
|
||||
|
||||
var lCursorBox = invMatrix.TransformBox(cursorBox);
|
||||
|
||||
@@ -403,7 +403,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
Mod1 = mods[0],
|
||||
Mod2 = mods[1],
|
||||
Mod3 = mods[2],
|
||||
Priority = 0,
|
||||
Priority = _currentlyRebinding.Binding?.Priority ?? 0,
|
||||
Type = bindType,
|
||||
CanFocus = key == Keyboard.Key.MouseLeft
|
||||
|| key == Keyboard.Key.MouseRight
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class PopupOverlay : Overlay
|
||||
private readonly PopupSystem _popup;
|
||||
private readonly PopupUIController _controller;
|
||||
private readonly ExamineSystemShared _examine;
|
||||
|
||||
private readonly SharedTransformSystem _transform;
|
||||
private readonly ShaderInstance _shader;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
@@ -35,6 +35,7 @@ public sealed class PopupOverlay : Overlay
|
||||
IUserInterfaceManager uiManager,
|
||||
PopupUIController controller,
|
||||
ExamineSystemShared examine,
|
||||
SharedTransformSystem transform,
|
||||
PopupSystem popup)
|
||||
{
|
||||
_configManager = configManager;
|
||||
@@ -42,6 +43,7 @@ public sealed class PopupOverlay : Overlay
|
||||
_playerMgr = playerMgr;
|
||||
_uiManager = uiManager;
|
||||
_examine = examine;
|
||||
_transform = transform;
|
||||
_popup = popup;
|
||||
_controller = controller;
|
||||
|
||||
@@ -76,7 +78,7 @@ public sealed class PopupOverlay : Overlay
|
||||
|
||||
foreach (var popup in _popup.WorldLabels)
|
||||
{
|
||||
var mapPos = popup.InitialPos.ToMap(_entManager);
|
||||
var mapPos = popup.InitialPos.ToMap(_entManager, _transform);
|
||||
|
||||
if (mapPos.MapId != args.MapId)
|
||||
continue;
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Content.Client.Popups
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public IReadOnlyList<WorldPopupLabel> WorldLabels => _aliveWorldLabels;
|
||||
public IReadOnlyList<CursorPopupLabel> CursorLabels => _aliveCursorLabels;
|
||||
@@ -54,6 +55,7 @@ namespace Content.Client.Popups
|
||||
_uiManager,
|
||||
_uiManager.GetUIController<PopupUIController>(),
|
||||
_examine,
|
||||
_transform,
|
||||
this));
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,12 @@ using Content.Client.Radiation.Systems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Client.Radiation.Overlays;
|
||||
|
||||
public sealed class RadiationDebugOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
private readonly RadiationSystem _radiation;
|
||||
|
||||
@@ -63,7 +62,7 @@ public sealed class RadiationDebugOverlay : Overlay
|
||||
{
|
||||
var gridUid = _entityManager.GetEntity(netGrid);
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridUid, out var grid))
|
||||
if (!_entityManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
|
||||
continue;
|
||||
|
||||
foreach (var (tile, rads) in blockers)
|
||||
@@ -88,7 +87,7 @@ public sealed class RadiationDebugOverlay : Overlay
|
||||
{
|
||||
var gridUid = _entityManager.GetEntity(netGrid);
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridUid, out var grid))
|
||||
if (!_entityManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
|
||||
continue;
|
||||
if (query.TryGetComponent(gridUid, out var trs) && trs.MapID != args.MapId)
|
||||
continue;
|
||||
@@ -127,7 +126,7 @@ public sealed class RadiationDebugOverlay : Overlay
|
||||
{
|
||||
var gridUid = _entityManager.GetEntity(netGrid);
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridUid, out var grid))
|
||||
if (!_entityManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
|
||||
continue;
|
||||
var (destTile, _) = blockers.Last();
|
||||
var destWorld = grid.GridTileToWorldPos(destTile);
|
||||
|
||||
@@ -121,7 +121,10 @@ namespace Content.Client.Radiation.Overlays
|
||||
|
||||
private bool PulseQualifies(EntityUid pulseEntity, MapCoordinates currentEyeLoc)
|
||||
{
|
||||
return _entityManager.GetComponent<TransformComponent>(pulseEntity).MapID == currentEyeLoc.MapId && _entityManager.GetComponent<TransformComponent>(pulseEntity).Coordinates.InRange(_entityManager, EntityCoordinates.FromMap(_entityManager, _entityManager.GetComponent<TransformComponent>(pulseEntity).ParentUid, currentEyeLoc), MaxDist);
|
||||
var transformComponent = _entityManager.GetComponent<TransformComponent>(pulseEntity);
|
||||
var transformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
return transformComponent.MapID == currentEyeLoc.MapId
|
||||
&& transformComponent.Coordinates.InRange(_entityManager, transformSystem, EntityCoordinates.FromMap(transformComponent.ParentUid, currentEyeLoc, transformSystem, _entityManager), MaxDist);
|
||||
}
|
||||
|
||||
private sealed record RadiationShaderInstance(MapCoordinates CurrentMapCoords, float Range, TimeSpan Start, float Duration)
|
||||
|
||||
16
Content.Client/Remotes/EntitySystems/DoorRemoteSystem.cs
Normal file
16
Content.Client/Remotes/EntitySystems/DoorRemoteSystem.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Content.Client.Remote.UI;
|
||||
using Content.Client.Items;
|
||||
using Content.Shared.Remotes.EntitySystems;
|
||||
using Content.Shared.Remotes.Components;
|
||||
|
||||
namespace Content.Client.Remotes.EntitySystems;
|
||||
|
||||
public sealed class DoorRemoteSystem : SharedDoorRemoteSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
Subs.ItemStatus<DoorRemoteComponent>(ent => new DoorRemoteStatusControl(ent));
|
||||
}
|
||||
}
|
||||
46
Content.Client/Remotes/UI/DoorRemoteStatusControl.cs
Normal file
46
Content.Client/Remotes/UI/DoorRemoteStatusControl.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Remotes.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Remote.UI;
|
||||
|
||||
public sealed class DoorRemoteStatusControl : Control
|
||||
{
|
||||
private readonly Entity<DoorRemoteComponent> _entity;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
// set to toggle bolts initially just so that it updates on first pickup of remote
|
||||
private OperatingMode PrevOperatingMode = OperatingMode.placeholderForUiUpdates;
|
||||
|
||||
public DoorRemoteStatusControl(Entity<DoorRemoteComponent> entity)
|
||||
{
|
||||
_entity = entity;
|
||||
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||
AddChild(_label);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
// only updates the UI if any of the details are different than they previously were
|
||||
if (PrevOperatingMode == _entity.Comp.Mode)
|
||||
return;
|
||||
|
||||
PrevOperatingMode = _entity.Comp.Mode;
|
||||
|
||||
// Update current volume and injector state
|
||||
var modeStringLocalized = Loc.GetString(_entity.Comp.Mode switch
|
||||
{
|
||||
OperatingMode.OpenClose => "door-remote-open-close-text",
|
||||
OperatingMode.ToggleBolts => "door-remote-toggle-bolt-text",
|
||||
OperatingMode.ToggleEmergencyAccess => "door-remote-emergency-access-text",
|
||||
_ => "door-remote-invalid-text"
|
||||
});
|
||||
|
||||
_label.SetMarkup(Loc.GetString("door-remote-mode-label", ("modeString", modeStringLocalized)));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace Content.Client.Sandbox
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IPlacementManager _placement = default!;
|
||||
[Dependency] private readonly ContentEyeSystem _contentEye = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private bool _sandboxEnabled;
|
||||
public bool SandboxAllowed { get; private set; }
|
||||
@@ -108,7 +109,7 @@ namespace Content.Client.Sandbox
|
||||
}
|
||||
|
||||
// Try copy tile.
|
||||
if (!_map.TryFindGridAt(coords.ToMap(EntityManager), out _, out var grid) || !grid.TryGetTileRef(coords, out var tileRef))
|
||||
if (!_map.TryFindGridAt(coords.ToMap(EntityManager, _transform), out _, out var grid) || !grid.TryGetTileRef(coords, out var tileRef))
|
||||
return false;
|
||||
|
||||
if (_placement.Eraser)
|
||||
|
||||
@@ -66,11 +66,11 @@ public partial class BaseShuttleControl : MapGridControl
|
||||
|
||||
protected void DrawData(DrawingHandleScreen handle, string text)
|
||||
{
|
||||
var coordsDimensions = handle.GetDimensions(Font, text, UIScale);
|
||||
var coordsDimensions = handle.GetDimensions(Font, text, 1f);
|
||||
const float coordsMargins = 5f;
|
||||
|
||||
handle.DrawString(Font,
|
||||
new Vector2(coordsMargins, Height) - new Vector2(0f, coordsDimensions.Y + coordsMargins),
|
||||
new Vector2(coordsMargins, PixelHeight) - new Vector2(0f, coordsDimensions.Y + coordsMargins),
|
||||
text,
|
||||
Color.FromSrgb(IFFComponent.SelfColor));
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
|
||||
|
||||
var mapTransform = Matrix3.CreateInverseTransform(Offset, Angle.Zero);
|
||||
|
||||
if (beaconsOnly && TryGetBeacon(_beacons, mapTransform, args.RelativePosition, PixelRect, out var foundBeacon, out _))
|
||||
if (beaconsOnly && TryGetBeacon(_beacons, mapTransform, args.RelativePixelPosition, PixelRect, out var foundBeacon, out _))
|
||||
{
|
||||
RequestBeaconFTL?.Invoke(foundBeacon.Entity, _ftlAngle);
|
||||
}
|
||||
@@ -206,7 +206,8 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
|
||||
private List<IMapObject> GetViewportMapObjects(Matrix3 matty, List<IMapObject> mapObjects)
|
||||
{
|
||||
var results = new List<IMapObject>();
|
||||
var viewBox = SizeBox.Scale(1.2f);
|
||||
var enlargement = new Vector2i((int) (16 * UIScale), (int) (16 * UIScale));
|
||||
var viewBox = new UIBox2i(Vector2i.Zero - enlargement, PixelSize + enlargement);
|
||||
|
||||
foreach (var mapObj in mapObjects)
|
||||
{
|
||||
@@ -398,8 +399,8 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
|
||||
|
||||
foreach (var (gridUiPos, iffText) in sendStrings)
|
||||
{
|
||||
var textWidth = handle.GetDimensions(_font, iffText, UIScale);
|
||||
handle.DrawString(_font, gridUiPos + textWidth with { X = -textWidth.X / 2f }, iffText, adjustedColor);
|
||||
var textWidth = handle.GetDimensions(_font, iffText, 1f);
|
||||
handle.DrawString(_font, gridUiPos + textWidth with { X = -textWidth.X / 2f, Y = textWidth.Y * UIScale }, iffText, adjustedColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,7 +588,7 @@ public sealed partial class ShuttleMapControl : BaseShuttleControl
|
||||
|
||||
var distance = (localPos - mousePos).Length();
|
||||
|
||||
if (distance > BeaconSnapRange ||
|
||||
if (distance > BeaconSnapRange * UIScale ||
|
||||
distance > nearestValue)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -42,11 +42,12 @@ public class ActionButtonContainer : GridContainer
|
||||
{
|
||||
var button = new ActionButton(_entity);
|
||||
|
||||
if (keys.TryGetValue(index, out var boundKey))
|
||||
{
|
||||
button.KeyBind = boundKey;
|
||||
if (!keys.TryGetValue(index, out var boundKey))
|
||||
return button;
|
||||
|
||||
var binding = _input.GetKeyBinding(boundKey);
|
||||
button.KeyBind = boundKey;
|
||||
if (_input.TryGetKeyBinding(boundKey, out var binding))
|
||||
{
|
||||
button.Label.Text = binding.GetKeyString();
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ namespace Content.IntegrationTests.Tests.Body
|
||||
metaSys.Update(1.0f);
|
||||
metaSys.Update(1.0f);
|
||||
respSys.Update(2.0f);
|
||||
Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0001));
|
||||
Assert.That(GetMapMoles(), Is.EqualTo(startingMoles).Within(0.0002));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed class FluidSpill
|
||||
var puddleOrigin = new Vector2i(0, 0);
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var grid = mapManager.GetGrid(gridId);
|
||||
var grid = entityManager.GetComponent<MapGridComponent>(gridId);
|
||||
var solution = new Solution("Blood", FixedPoint2.New(100));
|
||||
var tileRef = grid.GetTileRef(puddleOrigin);
|
||||
#pragma warning disable NUnit2045 // Interdependent tests
|
||||
@@ -86,7 +86,7 @@ public sealed class FluidSpill
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var grid = mapManager.GetGrid(gridId);
|
||||
var grid = entityManager.GetComponent<MapGridComponent>(gridId);
|
||||
var puddle = GetPuddleEntity(entityManager, grid, puddleOrigin);
|
||||
|
||||
#pragma warning disable NUnit2045 // Interdependent tests
|
||||
|
||||
@@ -1006,15 +1006,10 @@ public abstract partial class InteractionTest
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
var atmosSystem = SEntMan.System<AtmosphereSystem>();
|
||||
var atmos = SEntMan.EnsureComponent<MapAtmosphereComponent>(target);
|
||||
var moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
moles[(int) Gas.Oxygen] = 21.824779f;
|
||||
moles[(int) Gas.Nitrogen] = 82.10312f;
|
||||
atmosSystem.SetMapAtmosphere(target, false, new GasMixture(2500)
|
||||
{
|
||||
Temperature = 293.15f,
|
||||
Moles = moles,
|
||||
}, atmos);
|
||||
atmosSystem.SetMapAtmosphere(target, false, new GasMixture(moles, Atmospherics.T20C));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public static class ClientPackaging
|
||||
/// <summary>
|
||||
/// Be advised this can be called from server packaging during a HybridACZ build.
|
||||
/// </summary>
|
||||
public static async Task PackageClient(bool skipBuild, IPackageLogger logger)
|
||||
public static async Task PackageClient(bool skipBuild, string configuration, IPackageLogger logger)
|
||||
{
|
||||
logger.Info("Building client...");
|
||||
|
||||
@@ -27,7 +27,7 @@ public static class ClientPackaging
|
||||
{
|
||||
"build",
|
||||
Path.Combine("Content.Client", "Content.Client.csproj"),
|
||||
"-c", "Release",
|
||||
"-c", configuration,
|
||||
"--nologo",
|
||||
"/v:m",
|
||||
"/t:Rebuild",
|
||||
|
||||
@@ -31,6 +31,11 @@ public sealed class CommandLineArgs
|
||||
/// </summary>
|
||||
public bool HybridAcz { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration used for when packaging the server. (Release, Debug, Tools)
|
||||
/// </summary>
|
||||
public string Configuration { get; set; }
|
||||
|
||||
// CommandLineArgs, 3rd of her name.
|
||||
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
|
||||
{
|
||||
@@ -39,6 +44,7 @@ public sealed class CommandLineArgs
|
||||
var skipBuild = false;
|
||||
var wipeRelease = true;
|
||||
var hybridAcz = false;
|
||||
var configuration = "Release";
|
||||
List<string>? platforms = null;
|
||||
|
||||
using var enumerator = args.GetEnumerator();
|
||||
@@ -89,6 +95,16 @@ public sealed class CommandLineArgs
|
||||
platforms ??= new List<string>();
|
||||
platforms.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--configuration")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
Console.WriteLine("No configuration provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
configuration = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
@@ -106,7 +122,7 @@ public sealed class CommandLineArgs
|
||||
return false;
|
||||
}
|
||||
|
||||
parsed = new CommandLineArgs(client.Value, skipBuild, wipeRelease, hybridAcz, platforms);
|
||||
parsed = new CommandLineArgs(client.Value, skipBuild, wipeRelease, hybridAcz, platforms, configuration);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -120,6 +136,7 @@ Options:
|
||||
--no-wipe-release Don't wipe the release folder before creating files.
|
||||
--hybrid-acz Use HybridACZ for server builds.
|
||||
--platform Platform for server builds. Default will output several x64 targets.
|
||||
--configuration Configuration to use for building the server (Release, Debug, Tools). Default is Release.
|
||||
");
|
||||
}
|
||||
|
||||
@@ -128,12 +145,14 @@ Options:
|
||||
bool skipBuild,
|
||||
bool wipeRelease,
|
||||
bool hybridAcz,
|
||||
List<string>? platforms)
|
||||
List<string>? platforms,
|
||||
string configuration)
|
||||
{
|
||||
Client = client;
|
||||
SkipBuild = skipBuild;
|
||||
WipeRelease = wipeRelease;
|
||||
HybridAcz = hybridAcz;
|
||||
Platforms = platforms;
|
||||
Configuration = configuration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ if (!parsed.SkipBuild)
|
||||
|
||||
if (parsed.Client)
|
||||
{
|
||||
await ClientPackaging.PackageClient(parsed.SkipBuild, logger);
|
||||
await ClientPackaging.PackageClient(parsed.SkipBuild, parsed.Configuration, logger);
|
||||
}
|
||||
else
|
||||
{
|
||||
await ServerPackaging.PackageServer(parsed.SkipBuild, parsed.HybridAcz, logger, parsed.Platforms);
|
||||
await ServerPackaging.PackageServer(parsed.SkipBuild, parsed.HybridAcz, logger, parsed.Configuration, parsed.Platforms);
|
||||
}
|
||||
|
||||
void WipeBin()
|
||||
|
||||
@@ -74,8 +74,7 @@ public static class ServerPackaging
|
||||
};
|
||||
|
||||
private static readonly bool UseSecrets = File.Exists(Path.Combine("Secrets", "CorvaxSecrets.sln")); // Corvax-Secrets
|
||||
|
||||
public static async Task PackageServer(bool skipBuild, bool hybridAcz, IPackageLogger logger, List<string>? platforms = null)
|
||||
public static async Task PackageServer(bool skipBuild, bool hybridAcz, IPackageLogger logger, string configuration, List<string>? platforms = null)
|
||||
{
|
||||
if (platforms == null)
|
||||
{
|
||||
@@ -88,7 +87,7 @@ public static class ServerPackaging
|
||||
// Rather than hosting the client ZIP on the watchdog or on a separate server,
|
||||
// Hybrid ACZ uses the ACZ hosting functionality to host it as part of the status host,
|
||||
// which means that features such as automatic UPnP forwarding still work properly.
|
||||
await ClientPackaging.PackageClient(skipBuild, logger);
|
||||
await ClientPackaging.PackageClient(skipBuild, configuration, logger);
|
||||
}
|
||||
|
||||
// Good variable naming right here.
|
||||
@@ -97,13 +96,13 @@ public static class ServerPackaging
|
||||
if (!platforms.Contains(platform.Rid))
|
||||
continue;
|
||||
|
||||
await BuildPlatform(platform, skipBuild, hybridAcz, logger);
|
||||
await BuildPlatform(platform, skipBuild, hybridAcz, configuration, logger);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task BuildPlatform(PlatformReg platform, bool skipBuild, bool hybridAcz, IPackageLogger logger)
|
||||
private static async Task BuildPlatform(PlatformReg platform, bool skipBuild, bool hybridAcz, string configuration, IPackageLogger logger)
|
||||
{
|
||||
logger.Info($"Building project for {platform}...");
|
||||
logger.Info($"Building project for {platform.TargetOs}...");
|
||||
|
||||
if (!skipBuild)
|
||||
{
|
||||
@@ -114,7 +113,7 @@ public static class ServerPackaging
|
||||
{
|
||||
"build",
|
||||
Path.Combine("Content.Server", "Content.Server.csproj"),
|
||||
"-c", "Release",
|
||||
"-c", configuration,
|
||||
"--nologo",
|
||||
"/v:m",
|
||||
$"/p:TargetOs={platform.TargetOs}",
|
||||
@@ -146,7 +145,7 @@ public static class ServerPackaging
|
||||
}
|
||||
// Corvax-Secrets-End
|
||||
|
||||
await PublishClientServer(platform.Rid, platform.TargetOs);
|
||||
await PublishClientServer(platform.Rid, platform.TargetOs, configuration);
|
||||
}
|
||||
|
||||
logger.Info($"Packaging {platform.Rid} server...");
|
||||
@@ -165,7 +164,7 @@ public static class ServerPackaging
|
||||
logger.Info($"Finished packaging server in {sw.Elapsed}");
|
||||
}
|
||||
|
||||
private static async Task PublishClientServer(string runtime, string targetOs)
|
||||
private static async Task PublishClientServer(string runtime, string targetOs, string configuration)
|
||||
{
|
||||
await ProcessHelpers.RunCheck(new ProcessStartInfo
|
||||
{
|
||||
@@ -175,7 +174,7 @@ public static class ServerPackaging
|
||||
"publish",
|
||||
"--runtime", runtime,
|
||||
"--no-self-contained",
|
||||
"-c", "Release",
|
||||
"-c", configuration,
|
||||
$"/p:TargetOs={targetOs}",
|
||||
"/p:FullRelease=True",
|
||||
"/m",
|
||||
|
||||
1768
Content.Server.Database/Migrations/Postgres/20240318022005_AdminMessageDismiss.Designer.cs
generated
Normal file
1768
Content.Server.Database/Migrations/Postgres/20240318022005_AdminMessageDismiss.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AdminMessageDismiss : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "dismissed",
|
||||
table: "admin_messages",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.Sql("UPDATE admin_messages SET dismissed = seen;");
|
||||
|
||||
migrationBuilder.AddCheckConstraint(
|
||||
name: "NotDismissedAndSeen",
|
||||
table: "admin_messages",
|
||||
sql: "NOT dismissed OR seen");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropCheckConstraint(
|
||||
name: "NotDismissedAndSeen",
|
||||
table: "admin_messages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "dismissed",
|
||||
table: "admin_messages");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,10 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
.HasColumnType("uuid")
|
||||
.HasColumnName("deleted_by_id");
|
||||
|
||||
b.Property<bool>("Dismissed")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("dismissed");
|
||||
|
||||
b.Property<DateTime?>("ExpirationTime")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("expiration_time");
|
||||
@@ -232,7 +236,10 @@ namespace Content.Server.Database.Migrations.Postgres
|
||||
b.HasIndex("RoundId")
|
||||
.HasDatabaseName("IX_admin_messages_round_id");
|
||||
|
||||
b.ToTable("admin_messages", (string)null);
|
||||
b.ToTable("admin_messages", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
|
||||
|
||||
1699
Content.Server.Database/Migrations/Sqlite/20240318021959_AdminMessageDismiss.Designer.cs
generated
Normal file
1699
Content.Server.Database/Migrations/Sqlite/20240318021959_AdminMessageDismiss.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AdminMessageDismiss : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "dismissed",
|
||||
table: "admin_messages",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.Sql("UPDATE admin_messages SET dismissed = seen;");
|
||||
|
||||
migrationBuilder.AddCheckConstraint(
|
||||
name: "NotDismissedAndSeen",
|
||||
table: "admin_messages",
|
||||
sql: "NOT dismissed OR seen");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropCheckConstraint(
|
||||
name: "NotDismissedAndSeen",
|
||||
table: "admin_messages");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "dismissed",
|
||||
table: "admin_messages");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,10 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("deleted_by_id");
|
||||
|
||||
b.Property<bool>("Dismissed")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("dismissed");
|
||||
|
||||
b.Property<DateTime?>("ExpirationTime")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("expiration_time");
|
||||
@@ -215,7 +219,10 @@ namespace Content.Server.Database.Migrations.Sqlite
|
||||
b.HasIndex("RoundId")
|
||||
.HasDatabaseName("IX_admin_messages_round_id");
|
||||
|
||||
b.ToTable("admin_messages", (string)null);
|
||||
b.ToTable("admin_messages", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("NotDismissedAndSeen", "NOT dismissed OR seen");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Content.Server.Database.AdminNote", b =>
|
||||
|
||||
@@ -268,6 +268,11 @@ namespace Content.Server.Database
|
||||
.HasPrincipalKey(author => author.UserId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
// A message cannot be "dismissed" without also being "seen".
|
||||
modelBuilder.Entity<AdminMessage>().ToTable(t =>
|
||||
t.HasCheckConstraint("NotDismissedAndSeen",
|
||||
"NOT dismissed OR seen"));
|
||||
|
||||
modelBuilder.Entity<ServerBan>()
|
||||
.HasOne(ban => ban.CreatedBy)
|
||||
.WithMany(author => author.AdminServerBansCreated)
|
||||
@@ -970,6 +975,15 @@ namespace Content.Server.Database
|
||||
[ForeignKey("DeletedBy")] public Guid? DeletedById { get; set; }
|
||||
public Player? DeletedBy { get; set; }
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the message has been seen at least once by the player.
|
||||
/// </summary>
|
||||
public bool Seen { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the message has been dismissed permanently by the player.
|
||||
/// </summary>
|
||||
public bool Dismissed { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,16 @@ public sealed class AdminWhoCommand : IConsoleCommand
|
||||
var adminMgr = IoCManager.Resolve<IAdminManager>();
|
||||
var afk = IoCManager.Resolve<IAfkManager>();
|
||||
|
||||
var seeStealth = true;
|
||||
|
||||
// If null it (hopefully) means it is being called from the console.
|
||||
if (shell.Player != null)
|
||||
{
|
||||
var playerData = adminMgr.GetAdminData(shell.Player);
|
||||
|
||||
seeStealth = playerData != null && playerData.CanStealth();
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
var first = true;
|
||||
foreach (var admin in adminMgr.ActiveAdmins)
|
||||
@@ -30,10 +40,16 @@ public sealed class AdminWhoCommand : IConsoleCommand
|
||||
var adminData = adminMgr.GetAdminData(admin)!;
|
||||
DebugTools.AssertNotNull(adminData);
|
||||
|
||||
if (adminData.Stealth && !seeStealth)
|
||||
continue;
|
||||
|
||||
sb.Append(admin.Name);
|
||||
if (adminData.Title is { } title)
|
||||
sb.Append($": [{title}]");
|
||||
|
||||
if (adminData.Stealth)
|
||||
sb.Append(" (S)");
|
||||
|
||||
if (shell.Player is { } player && adminMgr.HasAdminFlag(player, AdminFlags.Admin))
|
||||
{
|
||||
if (afk.IsAfk(admin))
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
@@ -14,6 +15,7 @@ public sealed class PlayGlobalSoundCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
|
||||
public string Command => "playglobalsound";
|
||||
@@ -95,7 +97,7 @@ public sealed class PlayGlobalSoundCommand : IConsoleCommand
|
||||
{
|
||||
var hint = Loc.GetString("play-global-sound-command-arg-path");
|
||||
|
||||
var options = CompletionHelper.ContentFilePath(args[0], _res);
|
||||
var options = CompletionHelper.AudioFilePath(args[0], _protoManager, _res);
|
||||
|
||||
return CompletionResult.FromHintOptions(options, hint);
|
||||
}
|
||||
|
||||
39
Content.Server/Administration/Commands/StealthminCommand.cs
Normal file
39
Content.Server/Administration/Commands/StealthminCommand.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Shared.Administration;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
[UsedImplicitly]
|
||||
[AdminCommand(AdminFlags.Stealth)]
|
||||
public sealed class StealthminCommand : LocalizedCommands
|
||||
{
|
||||
public override string Command => "stealthmin";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player;
|
||||
if (player == null)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-stealthmin-no-console"));
|
||||
return;
|
||||
}
|
||||
|
||||
var mgr = IoCManager.Resolve<IAdminManager>();
|
||||
|
||||
var adminData = mgr.GetAdminData(player);
|
||||
|
||||
DebugTools.AssertNotNull(adminData);
|
||||
|
||||
if (!adminData!.Stealth)
|
||||
{
|
||||
mgr.Stealth(player);
|
||||
}
|
||||
else
|
||||
{
|
||||
mgr.UnStealth(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Content.Server.Afk;
|
||||
using Robust.Server.DataMetrics;
|
||||
|
||||
namespace Content.Server.Administration.Managers;
|
||||
|
||||
// Handles metrics reporting for active admin count and such.
|
||||
|
||||
public sealed partial class AdminManager
|
||||
{
|
||||
private Dictionary<int, (int active, int afk, int deadminned)>? _adminOnlineCounts;
|
||||
|
||||
private const int SentinelRankId = -1;
|
||||
|
||||
[Dependency] private readonly IMetricsManager _metrics = default!;
|
||||
[Dependency] private readonly IAfkManager _afkManager = default!;
|
||||
[Dependency] private readonly IMeterFactory _meterFactory = default!;
|
||||
|
||||
private void InitializeMetrics()
|
||||
{
|
||||
_metrics.UpdateMetrics += MetricsOnUpdateMetrics;
|
||||
|
||||
var meter = _meterFactory.Create("SS14.AdminManager");
|
||||
|
||||
meter.CreateObservableGauge(
|
||||
"admins_online_count",
|
||||
MeasureAdminCount,
|
||||
null,
|
||||
"The count of online admins");
|
||||
}
|
||||
|
||||
private void MetricsOnUpdateMetrics()
|
||||
{
|
||||
_sawmill.Verbose("Updating metrics");
|
||||
|
||||
var dict = new Dictionary<int, (int active, int afk, int deadminned)>();
|
||||
|
||||
foreach (var (session, reg) in _admins)
|
||||
{
|
||||
var rankId = reg.RankId ?? SentinelRankId;
|
||||
|
||||
ref var counts = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, rankId, out _);
|
||||
|
||||
if (reg.Data.Active)
|
||||
{
|
||||
if (_afkManager.IsAfk(session))
|
||||
counts.afk += 1;
|
||||
else
|
||||
counts.active += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
counts.deadminned += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Neither prometheus-net nor dotnet-counters seem to handle stuff well if we STOP returning measurements.
|
||||
// i.e. if the last admin with a rank disconnects.
|
||||
// So if we have EVER reported a rank, always keep reporting it.
|
||||
if (_adminOnlineCounts != null)
|
||||
{
|
||||
foreach (var rank in _adminOnlineCounts.Keys)
|
||||
{
|
||||
CollectionsMarshal.GetValueRefOrAddDefault(dict, rank, out _);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure "no rank" is always available. Avoid "no data".
|
||||
CollectionsMarshal.GetValueRefOrAddDefault(dict, SentinelRankId, out _);
|
||||
|
||||
_adminOnlineCounts = dict;
|
||||
}
|
||||
|
||||
private IEnumerable<Measurement<int>> MeasureAdminCount()
|
||||
{
|
||||
if (_adminOnlineCounts == null)
|
||||
yield break;
|
||||
|
||||
foreach (var (rank, (active, afk, deadminned)) in _adminOnlineCounts)
|
||||
{
|
||||
yield return new Measurement<int>(
|
||||
active,
|
||||
new KeyValuePair<string, object?>("state", "active"),
|
||||
new KeyValuePair<string, object?>("rank", rank == SentinelRankId ? "none" : rank.ToString()));
|
||||
|
||||
yield return new Measurement<int>(
|
||||
afk,
|
||||
new KeyValuePair<string, object?>("state", "afk"),
|
||||
new KeyValuePair<string, object?>("rank", rank == SentinelRankId ? "none" : rank.ToString()));
|
||||
|
||||
yield return new Measurement<int>(
|
||||
deadminned,
|
||||
new KeyValuePair<string, object?>("state", "deadminned"),
|
||||
new KeyValuePair<string, object?>("rank", rank == SentinelRankId ? "none" : rank.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Administration.Managers
|
||||
{
|
||||
public sealed class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation
|
||||
public sealed partial class AdminManager : IAdminManager, IPostInjectInit, IConGroupControllerImplementation
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
@@ -34,6 +34,7 @@ namespace Content.Server.Administration.Managers
|
||||
[Dependency] private readonly IServerConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private readonly Dictionary<ICommonSession, AdminReg> _admins = new();
|
||||
private readonly HashSet<NetUserId> _promotedPlayers = new();
|
||||
@@ -49,6 +50,8 @@ namespace Content.Server.Administration.Managers
|
||||
private readonly AdminCommandPermissions _commandPermissions = new();
|
||||
private readonly AdminCommandPermissions _toolshedCommandPermissions = new();
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public bool IsAdmin(ICommonSession session, bool includeDeAdmin = false)
|
||||
{
|
||||
return GetAdminData(session, includeDeAdmin) != null;
|
||||
@@ -95,6 +98,44 @@ namespace Content.Server.Administration.Managers
|
||||
UpdateAdminStatus(session);
|
||||
}
|
||||
|
||||
public void Stealth(ICommonSession session)
|
||||
{
|
||||
if (!_admins.TryGetValue(session, out var reg))
|
||||
{
|
||||
throw new ArgumentException($"Player {session} is not an admin");
|
||||
}
|
||||
|
||||
if (reg.Data.Stealth)
|
||||
return;
|
||||
|
||||
var playerData = session.ContentData()!;
|
||||
playerData.Stealthed = true;
|
||||
reg.Data.Stealth = true;
|
||||
|
||||
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-stealthed-message"));
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-de-admin-message", ("exAdminName", session.Name)), AdminFlags.Stealth);
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-enable-stealth", ("stealthAdminName", session.Name)), flagWhitelist: AdminFlags.Stealth);
|
||||
}
|
||||
|
||||
public void UnStealth(ICommonSession session)
|
||||
{
|
||||
if (!_admins.TryGetValue(session, out var reg))
|
||||
{
|
||||
throw new ArgumentException($"Player {session} is not an admin");
|
||||
}
|
||||
|
||||
if (!reg.Data.Stealth)
|
||||
return;
|
||||
|
||||
var playerData = session.ContentData()!;
|
||||
playerData.Stealthed = false;
|
||||
reg.Data.Stealth = false;
|
||||
|
||||
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-unstealthed-message"));
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-re-admin-message", ("newAdminName", session.Name)), flagBlacklist: AdminFlags.Stealth);
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-disable-stealth", ("exStealthAdminName", session.Name)), flagWhitelist: AdminFlags.Stealth);
|
||||
}
|
||||
|
||||
public void ReAdmin(ICommonSession session)
|
||||
{
|
||||
if (!_admins.TryGetValue(session, out var reg))
|
||||
@@ -113,7 +154,16 @@ namespace Content.Server.Administration.Managers
|
||||
plyData.ExplicitlyDeadminned = false;
|
||||
reg.Data.Active = true;
|
||||
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-re-admin-message", ("newAdminName", session.Name)));
|
||||
if (reg.Data.Stealth)
|
||||
{
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-re-admin-message", ("newAdminName", session.Name)));
|
||||
}
|
||||
else
|
||||
{
|
||||
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-stealthed-message"));
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-self-re-admin-message",
|
||||
("newAdminName", session.Name)), flagWhitelist: AdminFlags.Stealth);
|
||||
}
|
||||
|
||||
SendPermsChangedEvent(session);
|
||||
UpdateAdminStatus(session);
|
||||
@@ -165,6 +215,11 @@ namespace Content.Server.Administration.Managers
|
||||
|
||||
_chat.DispatchServerMessage(player, Loc.GetString("admin-manager-admin-permissions-updated-message"));
|
||||
}
|
||||
|
||||
if (player.ContentData()!.Stealthed)
|
||||
{
|
||||
aData.Stealth = true;
|
||||
}
|
||||
}
|
||||
|
||||
SendPermsChangedEvent(player);
|
||||
@@ -181,6 +236,8 @@ namespace Content.Server.Administration.Managers
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = _logManager.GetSawmill("admin");
|
||||
|
||||
_netMgr.RegisterNetMessage<MsgUpdateAdminStatus>();
|
||||
|
||||
// Cache permissions for loaded console commands with the requisite attributes.
|
||||
@@ -234,6 +291,8 @@ namespace Content.Server.Administration.Managers
|
||||
}
|
||||
|
||||
_toolshed.ActivePermissionController = this;
|
||||
|
||||
InitializeMetrics();
|
||||
}
|
||||
|
||||
public void PromoteHost(ICommonSession player)
|
||||
@@ -283,9 +342,19 @@ namespace Content.Server.Administration.Managers
|
||||
}
|
||||
else if (e.NewStatus == SessionStatus.Disconnected)
|
||||
{
|
||||
if (_admins.Remove(e.Session) && _cfg.GetCVar(CCVars.AdminAnnounceLogout))
|
||||
if (_admins.Remove(e.Session, out var reg ) && _cfg.GetCVar(CCVars.AdminAnnounceLogout))
|
||||
{
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-logout-message", ("name", e.Session.Name)));
|
||||
if (reg.Data.Stealth)
|
||||
{
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-logout-message",
|
||||
("name", e.Session.Name)), flagWhitelist: AdminFlags.Stealth);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-logout-message",
|
||||
("name", e.Session.Name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -308,13 +377,27 @@ namespace Content.Server.Administration.Managers
|
||||
|
||||
_admins.Add(session, reg);
|
||||
|
||||
if (session.ContentData()!.Stealthed)
|
||||
reg.Data.Stealth = true;
|
||||
|
||||
if (!session.ContentData()!.ExplicitlyDeadminned)
|
||||
{
|
||||
reg.Data.Active = true;
|
||||
|
||||
if (_cfg.GetCVar(CCVars.AdminAnnounceLogin))
|
||||
{
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-login-message", ("name", session.Name)));
|
||||
if (reg.Data.Stealth)
|
||||
{
|
||||
|
||||
_chat.DispatchServerMessage(session, Loc.GetString("admin-manager-stealthed-message"));
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-login-message",
|
||||
("name", session.Name)), flagWhitelist: AdminFlags.Stealth);
|
||||
}
|
||||
else
|
||||
{
|
||||
_chat.SendAdminAnnouncement(Loc.GetString("admin-manager-admin-login-message",
|
||||
("name", session.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
SendPermsChangedEvent(session);
|
||||
|
||||
@@ -41,6 +41,16 @@ namespace Content.Server.Administration.Managers
|
||||
/// </summary>
|
||||
void ReAdmin(ICommonSession session);
|
||||
|
||||
/// <summary>
|
||||
/// Make admin hidden from adminwho.
|
||||
/// </summary>
|
||||
void Stealth(ICommonSession session);
|
||||
|
||||
/// <summary>
|
||||
/// Unhide admin from adminwho.
|
||||
/// </summary>
|
||||
void UnStealth(ICommonSession session);
|
||||
|
||||
/// <summary>
|
||||
/// Re-loads the permissions of an player in case their admin data changed DB-side.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Database;
|
||||
using Content.Server.EUI;
|
||||
using Content.Shared.Administration.Notes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Eui;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Timing;
|
||||
using static Content.Shared.Administration.Notes.AdminMessageEuiMsg;
|
||||
|
||||
namespace Content.Server.Administration.Notes;
|
||||
@@ -12,32 +14,33 @@ public sealed class AdminMessageEui : BaseEui
|
||||
{
|
||||
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
private readonly float _closeWait;
|
||||
private AdminMessageRecord? _message;
|
||||
private DateTime _startTime;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public AdminMessageEui()
|
||||
private readonly TimeSpan _closeWait;
|
||||
private readonly TimeSpan _endTime;
|
||||
private readonly AdminMessageRecord[] _messages;
|
||||
|
||||
public AdminMessageEui(AdminMessageRecord[] messages)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_closeWait = _cfg.GetCVar(CCVars.MessageWaitTime);
|
||||
_closeWait = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.MessageWaitTime));
|
||||
_endTime = _gameTiming.RealTime + _closeWait;
|
||||
_messages = messages;
|
||||
}
|
||||
|
||||
public void SetMessage(AdminMessageRecord message)
|
||||
public override void Opened()
|
||||
{
|
||||
_message = message;
|
||||
_startTime = DateTime.UtcNow;
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
if (_message == null)
|
||||
return new AdminMessageEuiState(float.MaxValue, "An error has occurred.", string.Empty, DateTime.MinValue);
|
||||
return new AdminMessageEuiState(
|
||||
_closeWait,
|
||||
_message.Message,
|
||||
_message.CreatedBy?.LastSeenUserName ?? "[System]",
|
||||
_message.CreatedAt.UtcDateTime
|
||||
_messages.Select(x => new AdminMessageEuiState.Message(
|
||||
x.Message,
|
||||
x.CreatedBy?.LastSeenUserName ?? Loc.GetString("admin-notes-fallback-admin-name"),
|
||||
x.CreatedAt.UtcDateTime)).ToArray()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -47,15 +50,14 @@ public sealed class AdminMessageEui : BaseEui
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case Accept:
|
||||
if (_message == null)
|
||||
break;
|
||||
// No escape
|
||||
if (DateTime.UtcNow - _startTime >= TimeSpan.FromSeconds(_closeWait))
|
||||
await _notesMan.MarkMessageAsSeen(_message.Id);
|
||||
Close();
|
||||
break;
|
||||
case Dismiss:
|
||||
case Dismiss dismiss:
|
||||
if (_gameTiming.RealTime < _endTime)
|
||||
return;
|
||||
|
||||
foreach (var message in _messages)
|
||||
{
|
||||
await _notesMan.MarkMessageAsSeen(message.Id, dismiss.Permanent);
|
||||
}
|
||||
Close();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -331,9 +331,9 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
|
||||
return await _db.GetMessages(player);
|
||||
}
|
||||
|
||||
public async Task MarkMessageAsSeen(int id)
|
||||
public async Task MarkMessageAsSeen(int id, bool dismissedToo)
|
||||
{
|
||||
await _db.MarkMessageAsSeen(id);
|
||||
await _db.MarkMessageAsSeen(id, dismissedToo);
|
||||
}
|
||||
|
||||
public void PostInject()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Commands;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.EUI;
|
||||
@@ -52,7 +53,7 @@ public sealed class AdminNotesSystem : EntitySystem
|
||||
|
||||
private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus != SessionStatus.Connected)
|
||||
if (e.NewStatus != SessionStatus.InGame)
|
||||
return;
|
||||
|
||||
var messages = await _notes.GetNewMessages(e.Session.UserId);
|
||||
@@ -69,19 +70,11 @@ public sealed class AdminNotesSystem : EntitySystem
|
||||
_chat.SendAdminAlert(Loc.GetString("admin-notes-watchlist", ("player", username), ("message", watchlist.Message)));
|
||||
}
|
||||
|
||||
foreach (var message in messages)
|
||||
{
|
||||
var messageString = Loc.GetString("admin-notes-new-message", ("admin", message.CreatedBy?.LastSeenUserName ?? "[System]"), ("message", message.Message));
|
||||
// Only open the popup if the user hasn't seen it yet
|
||||
if (!message.Seen)
|
||||
{
|
||||
var ui = new AdminMessageEui();
|
||||
_euis.OpenEui(ui, e.Session);
|
||||
ui.SetMessage(message);
|
||||
var messagesToShow = messages.OrderBy(x => x.CreatedAt).Where(x => !x.Dismissed).ToArray();
|
||||
if (messagesToShow.Length == 0)
|
||||
return;
|
||||
|
||||
// Only send the message if they haven't seen it yet
|
||||
_chat.DispatchServerMessage(e.Session, messageString);
|
||||
}
|
||||
}
|
||||
var ui = new AdminMessageEui(messagesToShow);
|
||||
_euis.OpenEui(ui, e.Session);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,5 +45,13 @@ public interface IAdminNotesManager
|
||||
/// <param name="player">Desired player's <see cref="Guid"/></param>
|
||||
/// <returns>All unread messages</returns>
|
||||
Task<List<AdminMessageRecord>> GetNewMessages(Guid player);
|
||||
Task MarkMessageAsSeen(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Mark an admin message as being seen by the target player.
|
||||
/// </summary>
|
||||
/// <param name="id">The database ID of the admin message.</param>
|
||||
/// <param name="dismissedToo">
|
||||
/// If true, the message is "permanently dismissed" and will not be shown to the player again when they join.
|
||||
/// </param>
|
||||
Task MarkMessageAsSeen(int id, bool dismissedToo);
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ public sealed partial class AdminVerbSystem
|
||||
var fixtures = Comp<FixturesComponent>(args.Target);
|
||||
xform.Anchored = false; // Just in case.
|
||||
_physics.SetBodyType(args.Target, BodyType.Dynamic, manager: fixtures, body: physics);
|
||||
_physics.SetBodyStatus(physics, BodyStatus.InAir);
|
||||
_physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir);
|
||||
_physics.WakeBody(args.Target, manager: fixtures, body: physics);
|
||||
|
||||
foreach (var fixture in fixtures.Fixtures.Values)
|
||||
@@ -424,8 +424,8 @@ public sealed partial class AdminVerbSystem
|
||||
|
||||
_physics.SetLinearVelocity(args.Target, _random.NextVector2(1.5f, 1.5f), manager: fixtures, body: physics);
|
||||
_physics.SetAngularVelocity(args.Target, MathF.PI * 12, manager: fixtures, body: physics);
|
||||
_physics.SetLinearDamping(physics, 0f);
|
||||
_physics.SetAngularDamping(physics, 0f);
|
||||
_physics.SetLinearDamping(args.Target, physics, 0f);
|
||||
_physics.SetAngularDamping(args.Target, physics, 0f);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-smite-pinball-description")
|
||||
@@ -444,7 +444,7 @@ public sealed partial class AdminVerbSystem
|
||||
xform.Anchored = false; // Just in case.
|
||||
|
||||
_physics.SetBodyType(args.Target, BodyType.Dynamic, body: physics);
|
||||
_physics.SetBodyStatus(physics, BodyStatus.InAir);
|
||||
_physics.SetBodyStatus(args.Target, physics, BodyStatus.InAir);
|
||||
_physics.WakeBody(args.Target, manager: fixtures, body: physics);
|
||||
|
||||
foreach (var fixture in fixtures.Fixtures.Values)
|
||||
@@ -454,8 +454,8 @@ public sealed partial class AdminVerbSystem
|
||||
|
||||
_physics.SetLinearVelocity(args.Target, _random.NextVector2(8.0f, 8.0f), manager: fixtures, body: physics);
|
||||
_physics.SetAngularVelocity(args.Target, MathF.PI * 12, manager: fixtures, body: physics);
|
||||
_physics.SetLinearDamping(physics, 0f);
|
||||
_physics.SetAngularDamping(physics, 0f);
|
||||
_physics.SetLinearDamping(args.Target, physics, 0f);
|
||||
_physics.SetAngularDamping(args.Target, physics, 0f);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-smite-yeet-description")
|
||||
|
||||
@@ -718,9 +718,21 @@ public sealed partial class AdminVerbSystem
|
||||
Icon = new SpriteSpecifier.Rsi(new("/Textures/Objects/Fun/caps.rsi"), "mag-6"),
|
||||
Act = () =>
|
||||
{
|
||||
_quickDialog.OpenDialog(player, "Set Bullet Amount", $"Amount (max {ballisticAmmo.Capacity}):", (int amount) =>
|
||||
_quickDialog.OpenDialog(player, "Set Bullet Amount", $"Amount (standard {ballisticAmmo.Capacity}):", (string amount) =>
|
||||
{
|
||||
ballisticAmmo.UnspawnedCount = amount;
|
||||
if (!int.TryParse(amount, out var result))
|
||||
return;
|
||||
|
||||
if (result > 0)
|
||||
{
|
||||
ballisticAmmo.UnspawnedCount = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
ballisticAmmo.UnspawnedCount = 0;
|
||||
}
|
||||
|
||||
_gun.UpdateBallisticAppearance(args.Target, ballisticAmmo);
|
||||
});
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Content.Server.Anomaly;
|
||||
/// </summary>
|
||||
public sealed partial class AnomalySystem
|
||||
{
|
||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private void InitializeGenerator()
|
||||
|
||||
@@ -2,11 +2,10 @@ using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Random;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
@@ -16,8 +15,6 @@ namespace Content.Server.Anomaly.Effects;
|
||||
public sealed class GasProducerAnomalySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphere = default!;
|
||||
[Dependency] private readonly TransformSystem _xform = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -55,7 +52,7 @@ public sealed class GasProducerAnomalySystem : EntitySystem
|
||||
{
|
||||
var xform = Transform(uid);
|
||||
|
||||
if (!_map.TryGetGrid(xform.GridUid, out var grid))
|
||||
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
|
||||
return;
|
||||
|
||||
var localpos = xform.Coordinates.Position;
|
||||
|
||||
@@ -3,7 +3,7 @@ using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Commands
|
||||
{
|
||||
@@ -11,7 +11,6 @@ namespace Content.Server.Atmos.Commands
|
||||
public sealed class DeleteGasCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
|
||||
public string Command => "deletegas";
|
||||
public string Description => "Removes all gases from a grid, or just of one type if specified.";
|
||||
@@ -119,7 +118,7 @@ namespace Content.Server.Atmos.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridId, out _))
|
||||
if (!_entManager.TryGetComponent<MapGridComponent>(gridId, out _))
|
||||
{
|
||||
shell.WriteLine($"No grid exists with id {gridId}");
|
||||
return;
|
||||
|
||||
94
Content.Server/Atmos/Commands/SetMapAtmosCommand.cs
Normal file
94
Content.Server/Atmos/Commands/SetMapAtmosCommand.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Atmos.Commands;
|
||||
|
||||
[AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class AddMapAtmosCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
|
||||
private const string _cmd = "cmd-set-map-atmos";
|
||||
public override string Command => "setmapatmos";
|
||||
public override string Description => Loc.GetString($"{_cmd}-desc");
|
||||
public override string Help => Loc.GetString($"{_cmd}-help");
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
{
|
||||
shell.WriteLine(Help);
|
||||
return;
|
||||
}
|
||||
|
||||
int.TryParse(args[0], out var id);
|
||||
var map = _map.GetMapEntityId(new MapId(id));
|
||||
if (!map.IsValid())
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bool.TryParse(args[1], out var space))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-bool", ("arg", args[1])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (space || args.Length < 4)
|
||||
{
|
||||
_entities.RemoveComponent<MapAtmosphereComponent>(map);
|
||||
shell.WriteLine(Loc.GetString($"{_cmd}-removed", ("map", id)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!float.TryParse(args[2], out var temp))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
|
||||
return;
|
||||
}
|
||||
|
||||
var mix = new GasMixture(Atmospherics.CellVolume) {Temperature = Math.Min(temp, Atmospherics.TCMB)};
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
if (args.Length == 3 + i)
|
||||
break;
|
||||
|
||||
if (!float.TryParse(args[3+i], out var moles))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3+i])));
|
||||
return;
|
||||
}
|
||||
|
||||
mix.AdjustMoles(i, moles);
|
||||
}
|
||||
|
||||
var atmos = _entities.EntitySysManager.GetEntitySystem<AtmosphereSystem>();
|
||||
atmos.SetMapAtmosphere(map, space, mix);
|
||||
shell.WriteLine(Loc.GetString($"{_cmd}-updated", ("map", id)));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entities), Loc.GetString($"{_cmd}-hint-map"));
|
||||
|
||||
if (args.Length == 2)
|
||||
return CompletionResult.FromHintOptions(new[]{ "false", "true"}, Loc.GetString($"{_cmd}-hint-space"));
|
||||
|
||||
if (!bool.TryParse(args[1], out var space) || space)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
if (args.Length == 3)
|
||||
return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-temp"));
|
||||
|
||||
var gas = (Gas) args.Length - 4;
|
||||
return CompletionResult.FromHint(Loc.GetString($"{_cmd}-hint-gas" , ("gas", gas.ToString())));
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,62 @@
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
|
||||
namespace Content.Server.Atmos.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, Access(typeof(AirtightSystem))]
|
||||
public sealed partial class AirtightComponent : Component
|
||||
{
|
||||
public (EntityUid Grid, Vector2i Tile) LastPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The directions in which this entity should block airflow, relative to its own reference frame.
|
||||
/// </summary>
|
||||
[DataField("airBlockedDirection", customTypeSerializer: typeof(FlagSerializer<AtmosDirectionFlags>))]
|
||||
public int InitialAirBlockedDirection { get; set; } = (int) AtmosDirection.All;
|
||||
|
||||
/// <summary>
|
||||
/// The directions in which the entity is currently blocking airflow, relative to the grid that the entity is on.
|
||||
/// I.e., this is a variant of <see cref="InitialAirBlockedDirection"/> that takes into account the entity's
|
||||
/// current rotation.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public int CurrentAirBlockedDirection;
|
||||
|
||||
[DataField("airBlocked")]
|
||||
/// <summary>
|
||||
/// Whether the airtight entity is currently blocking airflow.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool AirBlocked { get; set; } = true;
|
||||
|
||||
[DataField("fixVacuum")]
|
||||
/// <summary>
|
||||
/// If true, entities on this tile will attempt to draw air from surrounding tiles when they become unblocked
|
||||
/// and currently have no air. This is generally only required when <see cref="NoAirWhenFullyAirBlocked"/> is
|
||||
/// true, or if the entity is likely to occupy the same tile as another no-air airtight entity.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool FixVacuum { get; set; } = true;
|
||||
// I think fixvacuum exists to ensure that repeatedly closing/opening air-blocking doors doesn't end up
|
||||
// depressurizing a room. However it can also effectively be used as a means of generating gasses for free
|
||||
// TODO ATMOS Mass conservation. Make it actually push/pull air from adjacent tiles instead of destroying & creating,
|
||||
|
||||
|
||||
// TODO ATMOS Do we need these two fields?
|
||||
[DataField("rotateAirBlocked")]
|
||||
public bool RotateAirBlocked { get; set; } = true;
|
||||
|
||||
// TODO ATMOS remove this? What is this even for??
|
||||
[DataField("fixAirBlockedDirectionInitialize")]
|
||||
public bool FixAirBlockedDirectionInitialize { get; set; } = true;
|
||||
|
||||
[DataField("noAirWhenFullyAirBlocked")]
|
||||
/// <summary>
|
||||
/// If true, then the tile that this entity is on will have no air at all if all directions are blocked.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool NoAirWhenFullyAirBlocked { get; set; } = true;
|
||||
|
||||
/// <inheritdoc cref="CurrentAirBlockedDirection"/>
|
||||
[Access(Other = AccessPermissions.ReadWriteExecute)]
|
||||
public AtmosDirection AirBlockedDirection => (AtmosDirection)CurrentAirBlockedDirection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace Content.Server.Atmos.Components
|
||||
[IncludeDataField(customTypeSerializer:typeof(TileAtmosCollectionSerializer))]
|
||||
public Dictionary<Vector2i, TileAtmosphere> Tiles = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
public HashSet<TileAtmosphere> MapTiles = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
public readonly HashSet<TileAtmosphere> ActiveTiles = new(1000);
|
||||
|
||||
@@ -80,7 +83,10 @@ namespace Content.Server.Atmos.Components
|
||||
public readonly HashSet<Vector2i> InvalidatedCoords = new(1000);
|
||||
|
||||
[ViewVariables]
|
||||
public readonly Queue<Vector2i> CurrentRunInvalidatedCoordinates = new();
|
||||
public readonly Queue<TileAtmosphere> CurrentRunInvalidatedTiles = new();
|
||||
|
||||
[ViewVariables]
|
||||
public readonly List<TileAtmosphere> PossiblyDisconnectedTiles = new(100);
|
||||
|
||||
[ViewVariables]
|
||||
public int InvalidatedCoordsCount => InvalidatedCoords.Count;
|
||||
|
||||
@@ -12,12 +12,14 @@ public sealed partial class MapAtmosphereComponent : SharedMapAtmosphereComponen
|
||||
/// <summary>
|
||||
/// The default GasMixture a map will have. Space mixture by default.
|
||||
/// </summary>
|
||||
[DataField("mixture"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public GasMixture? Mixture = GasMixture.SpaceGas;
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public GasMixture Mixture = GasMixture.SpaceGas;
|
||||
|
||||
/// <summary>
|
||||
/// Whether empty tiles will be considered space or not.
|
||||
/// </summary>
|
||||
[DataField("space"), ViewVariables(VVAccess.ReadWrite)]
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Space = true;
|
||||
|
||||
public SharedGasTileOverlaySystem.GasOverlayData Overlay;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Server.Atmos.Components;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
@@ -10,7 +9,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
[UsedImplicitly]
|
||||
public sealed class AirtightSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
||||
|
||||
@@ -86,8 +85,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private bool AirtightMove(Entity<AirtightComponent> ent, ref MoveEvent ev)
|
||||
{
|
||||
var (owner, airtight) = ent;
|
||||
if (!airtight.RotateAirBlocked || airtight.InitialAirBlockedDirection == (int)AtmosDirection.Invalid)
|
||||
return false;
|
||||
|
||||
airtight.CurrentAirBlockedDirection = (int) Rotate((AtmosDirection)airtight.InitialAirBlockedDirection, ev.NewRotation);
|
||||
var pos = airtight.LastPosition;
|
||||
@@ -121,19 +118,16 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (!xform.Anchored || !TryComp(xform.GridUid, out MapGridComponent? grid))
|
||||
return;
|
||||
|
||||
airtight.LastPosition = (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates));
|
||||
InvalidatePosition(airtight.LastPosition.Item1, airtight.LastPosition.Item2, airtight.FixVacuum && !airtight.AirBlocked);
|
||||
var indices = _transform.GetGridTilePositionOrDefault((ent, xform), grid);
|
||||
airtight.LastPosition = (xform.GridUid.Value, indices);
|
||||
InvalidatePosition((xform.GridUid.Value, grid), indices);
|
||||
}
|
||||
|
||||
public void InvalidatePosition(EntityUid gridId, Vector2i pos, bool fixVacuum = false)
|
||||
public void InvalidatePosition(Entity<MapGridComponent?> grid, Vector2i pos)
|
||||
{
|
||||
if (!TryComp(gridId, out MapGridComponent? grid))
|
||||
return;
|
||||
|
||||
var query = EntityManager.GetEntityQuery<AirtightComponent>();
|
||||
_explosionSystem.UpdateAirtightMap(gridId, pos, grid, query);
|
||||
// TODO make atmos system use query
|
||||
_atmosphereSystem.InvalidateTile(gridId, pos);
|
||||
_explosionSystem.UpdateAirtightMap(grid, pos, grid, query);
|
||||
_atmosphereSystem.InvalidateTile(grid.Owner, pos);
|
||||
}
|
||||
|
||||
private AtmosDirection Rotate(AtmosDirection myDirection, Angle myAngle)
|
||||
|
||||
@@ -97,22 +97,20 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private AtmosDebugOverlayData ConvertTileToData(TileAtmosphere? tile)
|
||||
private AtmosDebugOverlayData? ConvertTileToData(TileAtmosphere tile)
|
||||
{
|
||||
if (tile == null)
|
||||
return default;
|
||||
|
||||
return new AtmosDebugOverlayData(
|
||||
tile.GridIndices,
|
||||
tile.Air?.Temperature ?? default,
|
||||
tile.Air?.Moles,
|
||||
tile.PressureDirection,
|
||||
tile.LastPressureDirection,
|
||||
tile.BlockedAirflow,
|
||||
tile.AirtightData.BlockedDirections,
|
||||
tile.ExcitedGroup?.GetHashCode(),
|
||||
tile.Space,
|
||||
false,
|
||||
false);
|
||||
tile.MapAtmosphere,
|
||||
tile.NoGridTile,
|
||||
tile.Air?.Immutable ?? false);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Enumerators;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
public struct AtmosObstructionEnumerator
|
||||
{
|
||||
private AnchoredEntitiesEnumerator _enumerator;
|
||||
private EntityQuery<AirtightComponent> _query;
|
||||
|
||||
public AtmosObstructionEnumerator(AnchoredEntitiesEnumerator enumerator, EntityQuery<AirtightComponent> query)
|
||||
{
|
||||
_enumerator = enumerator;
|
||||
_query = query;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out AirtightComponent? airtight)
|
||||
{
|
||||
if (!_enumerator.MoveNext(out var uid))
|
||||
{
|
||||
airtight = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// No rider, it makes it uglier.
|
||||
// ReSharper disable once ConvertIfStatementToReturnStatement
|
||||
if (!_query.TryGetComponent(uid.Value, out airtight))
|
||||
{
|
||||
// ReSharper disable once TailRecursiveCall
|
||||
return MoveNext(out airtight);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -85,10 +85,10 @@ public partial class AtmosphereSystem
|
||||
return ev.Mixtures!;
|
||||
}
|
||||
|
||||
public void InvalidateTile(EntityUid gridUid, Vector2i tile)
|
||||
public void InvalidateTile(Entity<GridAtmosphereComponent?> entity, Vector2i tile)
|
||||
{
|
||||
var ev = new InvalidateTileMethodEvent(gridUid, tile);
|
||||
RaiseLocalEvent(gridUid, ref ev);
|
||||
if (_atmosQuery.Resolve(entity.Owner, ref entity.Comp, false))
|
||||
entity.Comp.InvalidatedCoords.Add(tile);
|
||||
}
|
||||
|
||||
public GasMixture?[]? GetTileMixtures(EntityUid? gridUid, EntityUid? mapUid, List<Vector2i> tiles, bool excite = false)
|
||||
@@ -176,11 +176,11 @@ public partial class AtmosphereSystem
|
||||
|
||||
public bool IsTileAirBlocked(EntityUid gridUid, Vector2i tile, AtmosDirection directions = AtmosDirection.All, MapGridComponent? mapGridComp = null)
|
||||
{
|
||||
var ev = new IsTileAirBlockedMethodEvent(gridUid, tile, directions, mapGridComp);
|
||||
RaiseLocalEvent(gridUid, ref ev);
|
||||
if (!Resolve(gridUid, ref mapGridComp))
|
||||
return false;
|
||||
|
||||
// If nothing handled the event, it'll default to true.
|
||||
return ev.Result;
|
||||
var data = GetAirtightData(gridUid, mapGridComp, tile);
|
||||
return data.BlockedDirections.IsFlagSet(directions);
|
||||
}
|
||||
|
||||
public bool IsTileSpace(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, MapGridComponent? mapGridComp = null)
|
||||
@@ -231,12 +231,6 @@ public partial class AtmosphereSystem
|
||||
return ev.Result ?? Enumerable.Empty<GasMixture>();
|
||||
}
|
||||
|
||||
public void UpdateAdjacent(EntityUid gridUid, Vector2i tile, MapGridComponent? mapGridComp = null)
|
||||
{
|
||||
var ev = new UpdateAdjacentMethodEvent(gridUid, tile, mapGridComp);
|
||||
RaiseLocalEvent(gridUid, ref ev);
|
||||
}
|
||||
|
||||
public void HotspotExpose(EntityUid gridUid, Vector2i tile, float exposedTemperature, float exposedVolume,
|
||||
EntityUid? sparkSourceUid = null, bool soh = false)
|
||||
{
|
||||
@@ -259,12 +253,6 @@ public partial class AtmosphereSystem
|
||||
return ev.Result;
|
||||
}
|
||||
|
||||
public void FixTileVacuum(EntityUid gridUid, Vector2i tile)
|
||||
{
|
||||
var ev = new FixTileVacuumMethodEvent(gridUid, tile);
|
||||
RaiseLocalEvent(gridUid, ref ev);
|
||||
}
|
||||
|
||||
public void AddPipeNet(EntityUid gridUid, PipeNet pipeNet)
|
||||
{
|
||||
var ev = new AddPipeNetMethodEvent(gridUid, pipeNet);
|
||||
@@ -307,9 +295,6 @@ public partial class AtmosphereSystem
|
||||
[ByRefEvent] private record struct GetAllMixturesMethodEvent
|
||||
(EntityUid Grid, bool Excite = false, IEnumerable<GasMixture>? Mixtures = null, bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct InvalidateTileMethodEvent
|
||||
(EntityUid Grid, Vector2i Tile, bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct GetTileMixturesMethodEvent
|
||||
(EntityUid? GridUid, EntityUid? MapUid, List<Vector2i> Tiles, bool Excite = false, GasMixture?[]? Mixtures = null, bool Handled = false);
|
||||
|
||||
@@ -319,16 +304,6 @@ public partial class AtmosphereSystem
|
||||
[ByRefEvent] private record struct ReactTileMethodEvent
|
||||
(EntityUid GridId, Vector2i Tile, ReactionResult Result = default, bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct IsTileAirBlockedMethodEvent
|
||||
(EntityUid Grid, Vector2i Tile, AtmosDirection Direction = AtmosDirection.All, MapGridComponent? MapGridComponent = null, bool Result = false, bool Handled = false)
|
||||
{
|
||||
/// <summary>
|
||||
/// True if one of the enabled blockers has <see cref="AirtightComponent.NoAirWhenFullyAirBlocked"/>. Note
|
||||
/// that this does not actually check if all directions are blocked.
|
||||
/// </summary>
|
||||
public bool NoAir = false;
|
||||
}
|
||||
|
||||
[ByRefEvent] private record struct IsTileSpaceMethodEvent
|
||||
(EntityUid? Grid, EntityUid? Map, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Result = true, bool Handled = false);
|
||||
|
||||
@@ -339,9 +314,6 @@ public partial class AtmosphereSystem
|
||||
(EntityUid Grid, Vector2i Tile, bool IncludeBlocked, bool Excite,
|
||||
IEnumerable<GasMixture>? Result = null, bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct UpdateAdjacentMethodEvent
|
||||
(EntityUid Grid, Vector2i Tile, MapGridComponent? MapGridComponent = null, bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct HotspotExposeMethodEvent
|
||||
(EntityUid Grid, EntityUid? SparkSourceUid, Vector2i Tile, float ExposedTemperature, float ExposedVolume, bool soh, bool Handled = false);
|
||||
|
||||
@@ -351,9 +323,6 @@ public partial class AtmosphereSystem
|
||||
[ByRefEvent] private record struct IsHotspotActiveMethodEvent
|
||||
(EntityUid Grid, Vector2i Tile, bool Result = false, bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct FixTileVacuumMethodEvent
|
||||
(EntityUid Grid, Vector2i Tile, bool Handled = false);
|
||||
|
||||
[ByRefEvent] private record struct AddPipeNetMethodEvent
|
||||
(EntityUid Grid, PipeNet PipeNet, bool Handled = false);
|
||||
|
||||
|
||||
@@ -92,6 +92,12 @@ public sealed partial class AtmosphereSystem
|
||||
if (tile == null)
|
||||
continue;
|
||||
|
||||
if (!_mapSystem.TryGetTile(gridComp, indices, out var gTile) || gTile.IsEmpty)
|
||||
{
|
||||
gridAtmosphere.Tiles.Remove(indices);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tile.Immutable && !IsTileSpace(euid, transform.MapUid, indices, gridComp))
|
||||
{
|
||||
tile = new GasMixture(tile.Volume) { Temperature = tile.Temperature };
|
||||
|
||||
@@ -72,7 +72,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
var tileSize = excitedGroup.Tiles.Count;
|
||||
|
||||
if (excitedGroup.Disposed) return;
|
||||
if (excitedGroup.Disposed)
|
||||
return;
|
||||
|
||||
if (tileSize == 0)
|
||||
{
|
||||
@@ -98,7 +99,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
foreach (var tile in excitedGroup.Tiles)
|
||||
{
|
||||
if (tile?.Air == null) continue;
|
||||
if (tile?.Air == null)
|
||||
continue;
|
||||
|
||||
tile.Air.CopyFromMutable(combined);
|
||||
InvalidateVisuals(tile.GridIndex, tile.GridIndices);
|
||||
}
|
||||
@@ -106,21 +109,23 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
excitedGroup.BreakdownCooldown = 0;
|
||||
}
|
||||
|
||||
private void ExcitedGroupDismantle(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup, bool unexcite = true)
|
||||
/// <summary>
|
||||
/// This de-activates and removes all tiles in an excited group.
|
||||
/// </summary>
|
||||
private void DeactivateGroupTiles(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
|
||||
{
|
||||
foreach (var tile in excitedGroup.Tiles)
|
||||
{
|
||||
tile.ExcitedGroup = null;
|
||||
|
||||
if (!unexcite)
|
||||
continue;
|
||||
|
||||
RemoveActiveTile(gridAtmosphere, tile);
|
||||
}
|
||||
|
||||
excitedGroup.Tiles.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This removes an excited group without de-activating its tiles.
|
||||
/// </summary>
|
||||
private void ExcitedGroupDispose(GridAtmosphereComponent gridAtmosphere, ExcitedGroup excitedGroup)
|
||||
{
|
||||
if (excitedGroup.Disposed)
|
||||
@@ -129,9 +134,14 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
DebugTools.Assert(gridAtmosphere.ExcitedGroups.Contains(excitedGroup), "Grid Atmosphere does not contain Excited Group!");
|
||||
|
||||
excitedGroup.Disposed = true;
|
||||
|
||||
gridAtmosphere.ExcitedGroups.Remove(excitedGroup);
|
||||
ExcitedGroupDismantle(gridAtmosphere, excitedGroup, false);
|
||||
|
||||
foreach (var tile in excitedGroup.Tiles)
|
||||
{
|
||||
tile.ExcitedGroup = null;
|
||||
}
|
||||
|
||||
excitedGroup.Tiles.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ public sealed partial class AtmosphereSystem
|
||||
private void InitializeGridAtmosphere()
|
||||
{
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, ComponentInit>(OnGridAtmosphereInit);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, ComponentStartup>(OnGridAtmosphereStartup);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, ComponentRemove>(OnAtmosphereRemove);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, GridSplitEvent>(OnGridSplit);
|
||||
|
||||
@@ -22,19 +23,15 @@ public sealed partial class AtmosphereSystem
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, HasAtmosphereMethodEvent>(GridHasAtmosphere);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, IsSimulatedGridMethodEvent>(GridIsSimulated);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, GetAllMixturesMethodEvent>(GridGetAllMixtures);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, InvalidateTileMethodEvent>(GridInvalidateTile);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixtureMethodEvent>(GridGetTileMixture);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, GetTileMixturesMethodEvent>(GridGetTileMixtures);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, ReactTileMethodEvent>(GridReactTile);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, IsTileAirBlockedMethodEvent>(GridIsTileAirBlocked);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, IsTileSpaceMethodEvent>(GridIsTileSpace);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTilesMethodEvent>(GridGetAdjacentTiles);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, GetAdjacentTileMixturesMethodEvent>(GridGetAdjacentTileMixtures);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, UpdateAdjacentMethodEvent>(GridUpdateAdjacent);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, HotspotExposeMethodEvent>(GridHotspotExpose);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, HotspotExtinguishMethodEvent>(GridHotspotExtinguish);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, IsHotspotActiveMethodEvent>(GridIsHotspotActive);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, FixTileVacuumMethodEvent>(GridFixTileVacuum);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, AddPipeNetMethodEvent>(GridAddPipeNet);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, RemovePipeNetMethodEvent>(GridRemovePipeNet);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, AddAtmosDeviceMethodEvent>(GridAddAtmosDevice);
|
||||
@@ -56,22 +53,23 @@ public sealed partial class AtmosphereSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent gridAtmosphere, ComponentInit args)
|
||||
private void OnGridAtmosphereInit(EntityUid uid, GridAtmosphereComponent component, ComponentInit args)
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
EnsureComp<GasTileOverlayComponent>(uid);
|
||||
foreach (var tile in component.Tiles.Values)
|
||||
{
|
||||
tile.GridIndex = uid;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGridAtmosphereStartup(EntityUid uid, GridAtmosphereComponent component, ComponentStartup args)
|
||||
{
|
||||
if (!TryComp(uid, out MapGridComponent? mapGrid))
|
||||
return;
|
||||
|
||||
EnsureComp<GasTileOverlayComponent>(uid);
|
||||
|
||||
foreach (var (indices, tile) in gridAtmosphere.Tiles)
|
||||
{
|
||||
gridAtmosphere.InvalidatedCoords.Add(indices);
|
||||
tile.GridIndex = uid;
|
||||
}
|
||||
|
||||
GridRepopulateTiles((uid, mapGrid, gridAtmosphere));
|
||||
InvalidateAllTiles((uid, mapGrid, component));
|
||||
}
|
||||
|
||||
private void OnGridSplit(EntityUid uid, GridAtmosphereComponent originalGridAtmos, ref GridSplitEvent args)
|
||||
@@ -104,8 +102,7 @@ public sealed partial class AtmosphereSystem
|
||||
continue;
|
||||
|
||||
// Copy a bunch of data over... Not great, maybe put this in TileAtmosphere?
|
||||
newTileAtmosphere.Air = tileAtmosphere.Air?.Clone() ?? null;
|
||||
newTileAtmosphere.MolesArchived = newTileAtmosphere.Air == null ? null : new float[Atmospherics.AdjustedNumberOfGases];
|
||||
newTileAtmosphere.Air = tileAtmosphere.Air?.Clone();
|
||||
newTileAtmosphere.Hotspot = tileAtmosphere.Hotspot;
|
||||
newTileAtmosphere.HeatCapacity = tileAtmosphere.HeatCapacity;
|
||||
newTileAtmosphere.Temperature = tileAtmosphere.Temperature;
|
||||
@@ -170,15 +167,6 @@ public sealed partial class AtmosphereSystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void GridInvalidateTile(EntityUid uid, GridAtmosphereComponent component, ref InvalidateTileMethodEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
component.InvalidatedCoords.Add(args.Tile);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void GridGetTileMixture(EntityUid uid, GridAtmosphereComponent component,
|
||||
ref GetTileMixtureMethodEvent args)
|
||||
{
|
||||
@@ -233,43 +221,6 @@ public sealed partial class AtmosphereSystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void GridIsTileAirBlocked(EntityUid uid, GridAtmosphereComponent component,
|
||||
ref IsTileAirBlockedMethodEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var mapGridComp = args.MapGridComponent;
|
||||
|
||||
if (!Resolve(uid, ref mapGridComp))
|
||||
return;
|
||||
|
||||
var directions = AtmosDirection.Invalid;
|
||||
|
||||
var enumerator = GetObstructingComponentsEnumerator(mapGridComp, args.Tile);
|
||||
|
||||
while (enumerator.MoveNext(out var obstructingComponent))
|
||||
{
|
||||
if (!obstructingComponent.AirBlocked)
|
||||
continue;
|
||||
|
||||
// We set the directions that are air-blocked so far,
|
||||
// as you could have a full obstruction with only 4 directional air blockers.
|
||||
directions |= obstructingComponent.AirBlockedDirection;
|
||||
args.NoAir |= obstructingComponent.NoAirWhenFullyAirBlocked;
|
||||
|
||||
if (directions.IsFlagSet(args.Direction))
|
||||
{
|
||||
args.Result = true;
|
||||
args.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
args.Result = false;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void GridIsTileSpace(EntityUid uid, GridAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
@@ -331,71 +282,58 @@ public sealed partial class AtmosphereSystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void GridUpdateAdjacent(EntityUid uid, GridAtmosphereComponent component,
|
||||
ref UpdateAdjacentMethodEvent args)
|
||||
/// <summary>
|
||||
/// Update array of adjacent tiles and the adjacency flags. Optionally activates all tiles with modified adjacencies.
|
||||
/// </summary>
|
||||
private void UpdateAdjacentTiles(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile,
|
||||
bool activate = false)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var mapGridComp = args.MapGridComponent;
|
||||
|
||||
if (!Resolve(uid, ref mapGridComp))
|
||||
return;
|
||||
|
||||
var xform = Transform(uid);
|
||||
EntityUid? mapUid = _mapManager.MapExists(xform.MapID) ? _mapManager.GetMapEntityId(xform.MapID) : null;
|
||||
|
||||
if (!component.Tiles.TryGetValue(args.Tile, out var tile))
|
||||
return;
|
||||
var uid = ent.Owner;
|
||||
var atmos = ent.Comp1;
|
||||
var blockedDirs = tile.AirtightData.BlockedDirections;
|
||||
if (activate)
|
||||
AddActiveTile(atmos, tile);
|
||||
|
||||
tile.AdjacentBits = AtmosDirection.Invalid;
|
||||
tile.BlockedAirflow = GetBlockedDirections(mapGridComp, tile.GridIndices);
|
||||
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
var adjacentIndices = tile.GridIndices.Offset(direction);
|
||||
|
||||
var otherIndices = tile.GridIndices.Offset(direction);
|
||||
|
||||
if (!component.Tiles.TryGetValue(otherIndices, out var adjacent))
|
||||
TileAtmosphere? adjacent;
|
||||
if (!tile.NoGridTile)
|
||||
{
|
||||
adjacent = new TileAtmosphere(tile.GridIndex, otherIndices,
|
||||
GetTileMixture(null, mapUid, otherIndices),
|
||||
space: IsTileSpace(null, mapUid, otherIndices, mapGridComp));
|
||||
adjacent = GetOrNewTile(uid, atmos, adjacentIndices);
|
||||
}
|
||||
|
||||
var oppositeDirection = direction.GetOpposite();
|
||||
|
||||
adjacent.BlockedAirflow = GetBlockedDirections(mapGridComp, adjacent.GridIndices);
|
||||
|
||||
// Pass in MapGridComponent so we don't have to resolve it for every adjacent direction.
|
||||
var tileBlockedEv = new IsTileAirBlockedMethodEvent(uid, tile.GridIndices, direction, mapGridComp);
|
||||
GridIsTileAirBlocked(uid, component, ref tileBlockedEv);
|
||||
|
||||
var adjacentBlockedEv =
|
||||
new IsTileAirBlockedMethodEvent(uid, adjacent.GridIndices, oppositeDirection, mapGridComp);
|
||||
GridIsTileAirBlocked(uid, component, ref adjacentBlockedEv);
|
||||
|
||||
if (!adjacent.BlockedAirflow.IsFlagSet(oppositeDirection) && !tileBlockedEv.Result)
|
||||
{
|
||||
adjacent.AdjacentBits |= oppositeDirection;
|
||||
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
|
||||
}
|
||||
else
|
||||
{
|
||||
adjacent.AdjacentBits &= ~oppositeDirection;
|
||||
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null;
|
||||
}
|
||||
|
||||
if (!tile.BlockedAirflow.IsFlagSet(direction) && !adjacentBlockedEv.Result)
|
||||
{
|
||||
tile.AdjacentBits |= direction;
|
||||
tile.AdjacentTiles[direction.ToIndex()] = adjacent;
|
||||
}
|
||||
else
|
||||
else if (!atmos.Tiles.TryGetValue(adjacentIndices, out adjacent))
|
||||
{
|
||||
tile.AdjacentBits &= ~direction;
|
||||
tile.AdjacentTiles[direction.ToIndex()] = null;
|
||||
tile.AdjacentTiles[i] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
var adjBlockDirs = adjacent.AirtightData.BlockedDirections;
|
||||
if (activate)
|
||||
AddActiveTile(atmos, adjacent);
|
||||
|
||||
var oppositeDirection = direction.GetOpposite();
|
||||
if (adjBlockDirs.IsFlagSet(oppositeDirection) || blockedDirs.IsFlagSet(direction))
|
||||
{
|
||||
// Adjacency is blocked by some airtight entity.
|
||||
tile.AdjacentBits &= ~direction;
|
||||
adjacent.AdjacentBits &= ~oppositeDirection;
|
||||
tile.AdjacentTiles[i] = null;
|
||||
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No airtight entity in the way.
|
||||
tile.AdjacentBits |= direction;
|
||||
adjacent.AdjacentBits |= oppositeDirection;
|
||||
tile.AdjacentTiles[i] = adjacent;
|
||||
adjacent.AdjacentTiles[oppositeDirection.ToIndex()] = tile;
|
||||
}
|
||||
|
||||
DebugTools.Assert(!(tile.AdjacentBits.IsFlagSet(direction) ^
|
||||
@@ -409,6 +347,16 @@ public sealed partial class AtmosphereSystem
|
||||
tile.MonstermosInfo.CurrentTransferDirection = AtmosDirection.Invalid;
|
||||
}
|
||||
|
||||
private (GasMixture Air, bool IsSpace) GetDefaultMapAtmosphere(MapAtmosphereComponent? map)
|
||||
{
|
||||
if (map == null)
|
||||
return (GasMixture.SpaceGas, true);
|
||||
|
||||
var air = map.Mixture;
|
||||
DebugTools.Assert(air.Immutable);
|
||||
return (air, map.Space);
|
||||
}
|
||||
|
||||
private void GridHotspotExpose(EntityUid uid, GridAtmosphereComponent component, ref HotspotExposeMethodEvent args)
|
||||
{
|
||||
if (args.Handled)
|
||||
@@ -451,54 +399,50 @@ public sealed partial class AtmosphereSystem
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void GridFixTileVacuum(EntityUid uid, GridAtmosphereComponent component, ref FixTileVacuumMethodEvent args)
|
||||
private void GridFixTileVacuum(TileAtmosphere tile)
|
||||
{
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
var adjEv = new GetAdjacentTileMixturesMethodEvent(uid, args.Tile, false, true);
|
||||
GridGetAdjacentTileMixtures(uid, component, ref adjEv);
|
||||
|
||||
if (!adjEv.Handled || !component.Tiles.TryGetValue(args.Tile, out var tile))
|
||||
return;
|
||||
|
||||
if (!TryComp<MapGridComponent>(uid, out var mapGridComp))
|
||||
return;
|
||||
|
||||
var adjacent = adjEv.Result!.ToArray();
|
||||
|
||||
// Return early, let's not cause any funny NaNs or needless vacuums.
|
||||
if (adjacent.Length == 0)
|
||||
return;
|
||||
|
||||
tile.Air = new GasMixture
|
||||
{
|
||||
Volume = GetVolumeForTiles(mapGridComp, 1),
|
||||
Temperature = Atmospherics.T20C
|
||||
};
|
||||
|
||||
tile.MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
DebugTools.AssertNotNull(tile.Air);
|
||||
DebugTools.Assert(tile.Air?.Immutable == false );
|
||||
Array.Clear(tile.MolesArchived);
|
||||
tile.ArchivedCycle = 0;
|
||||
|
||||
var ratio = 1f / adjacent.Length;
|
||||
var count = 0;
|
||||
foreach (var adj in tile.AdjacentTiles)
|
||||
{
|
||||
if (adj?.Air != null)
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
var ratio = 1f / count;
|
||||
var totalTemperature = 0f;
|
||||
|
||||
foreach (var adj in adjacent)
|
||||
foreach (var adj in tile.AdjacentTiles)
|
||||
{
|
||||
if (adj?.Air == null)
|
||||
continue;
|
||||
|
||||
totalTemperature += adj.Temperature;
|
||||
|
||||
// TODO ATMOS. Why is this removing and then re-adding air to the neighbouring tiles?
|
||||
// Is it some rounding issue to do with Atmospherics.GasMinMoles? because otherwise this is just unnecessary.
|
||||
// if we get rid of this, then this could also just add moles and then multiply by ratio at the end, rather
|
||||
// than having to iterate over adjacent tiles twice.
|
||||
|
||||
// Remove a bit of gas from the adjacent ratio...
|
||||
var mix = adj.RemoveRatio(ratio);
|
||||
var mix = adj.Air.RemoveRatio(ratio);
|
||||
|
||||
// And merge it to the new tile air.
|
||||
Merge(tile.Air, mix);
|
||||
|
||||
// Return removed gas to its original mixture.
|
||||
Merge(adj, mix);
|
||||
Merge(adj.Air, mix);
|
||||
}
|
||||
|
||||
// New temperature is the arithmetic mean of the sum of the adjacent temperatures...
|
||||
tile.Air.Temperature = totalTemperature / adjacent.Length;
|
||||
tile.Air.Temperature = totalTemperature / count;
|
||||
}
|
||||
|
||||
private void GridAddPipeNet(EntityUid uid, GridAtmosphereComponent component, ref AddPipeNetMethodEvent args)
|
||||
@@ -547,30 +491,21 @@ public sealed partial class AtmosphereSystem
|
||||
/// <summary>
|
||||
/// Repopulates all tiles on a grid atmosphere.
|
||||
/// </summary>
|
||||
/// <param name="mapGrid">The grid where to get all valid tiles from.</param>
|
||||
/// <param name="gridAtmosphere">The grid atmosphere where the tiles will be repopulated.</param>
|
||||
private void GridRepopulateTiles(Entity<MapGridComponent, GridAtmosphereComponent> grid)
|
||||
public void InvalidateAllTiles(Entity<MapGridComponent?, GridAtmosphereComponent?> entity)
|
||||
{
|
||||
var (uid, mapGrid, gridAtmosphere) = grid;
|
||||
var volume = GetVolumeForTiles(mapGrid, 1);
|
||||
var (uid, grid, atmos) = entity;
|
||||
if (!Resolve(uid, ref grid, ref atmos))
|
||||
return;
|
||||
|
||||
foreach (var tile in mapGrid.GetAllTiles())
|
||||
foreach (var indices in atmos.Tiles.Keys)
|
||||
{
|
||||
if (!gridAtmosphere.Tiles.ContainsKey(tile.GridIndices))
|
||||
gridAtmosphere.Tiles[tile.GridIndices] = new TileAtmosphere(tile.GridUid, tile.GridIndices,
|
||||
new GasMixture(volume) { Temperature = Atmospherics.T20C });
|
||||
|
||||
gridAtmosphere.InvalidatedCoords.Add(tile.GridIndices);
|
||||
atmos.InvalidatedCoords.Add(indices);
|
||||
}
|
||||
|
||||
TryComp(uid, out GasTileOverlayComponent? overlay);
|
||||
|
||||
// Gotta do this afterwards so we can properly update adjacent tiles.
|
||||
foreach (var (position, _) in gridAtmosphere.Tiles.ToArray())
|
||||
var enumerator = _map.GetAllTilesEnumerator(uid, grid);
|
||||
while (enumerator.MoveNext(out var tile))
|
||||
{
|
||||
var ev = new UpdateAdjacentMethodEvent(uid, position);
|
||||
GridUpdateAdjacent(uid, gridAtmosphere, ref ev);
|
||||
InvalidateVisuals(uid, position, overlay);
|
||||
atmos.InvalidatedCoords.Add(tile.Value.GridIndices);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -54,7 +52,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (HasComp<MobStateComponent>(uid) &&
|
||||
TryComp<PhysicsComponent>(uid, out var body))
|
||||
{
|
||||
_physics.SetBodyStatus(body, BodyStatus.OnGround);
|
||||
_physics.SetBodyStatus(uid, body, BodyStatus.OnGround);
|
||||
}
|
||||
|
||||
if (TryComp<FixturesComponent>(uid, out var fixtures))
|
||||
@@ -77,7 +75,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (!TryComp<FixturesComponent>(uid, out var fixtures))
|
||||
return;
|
||||
|
||||
_physics.SetBodyStatus(body, BodyStatus.InAir);
|
||||
_physics.SetBodyStatus(uid, body, BodyStatus.InAir);
|
||||
|
||||
foreach (var (id, fixture) in fixtures.Fixtures)
|
||||
{
|
||||
@@ -96,9 +94,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// TODO ATMOS finish this
|
||||
|
||||
// Don't play the space wind sound on tiles that are on fire...
|
||||
if(tile.PressureDifference > 15 && !tile.Hotspot.Valid)
|
||||
if (tile.PressureDifference > 15 && !tile.Hotspot.Valid)
|
||||
{
|
||||
if(_spaceWindSoundCooldown == 0 && !string.IsNullOrEmpty(SpaceWindSound))
|
||||
if (_spaceWindSoundCooldown == 0 && !string.IsNullOrEmpty(SpaceWindSound))
|
||||
{
|
||||
var coordinates = _mapSystem.ToCenterCoordinates(tile.GridIndex, tile.GridIndices);
|
||||
_audio.PlayPvs(SpaceWindSound, coordinates, AudioParams.Default.WithVariation(0.125f).WithVolume(MathHelper.Clamp(tile.PressureDifference / 10, 10, 100)));
|
||||
@@ -238,7 +236,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// TODO: Technically these directions won't be correct but uhh I'm just here for optimisations buddy not to fix my old bugs.
|
||||
if (throwTarget != EntityCoordinates.Invalid)
|
||||
{
|
||||
var pos = ((throwTarget.ToMap(EntityManager).Position - xform.WorldPosition).Normalized() + dirVec).Normalized();
|
||||
var pos = ((throwTarget.ToMap(EntityManager, _transformSystem).Position - xform.WorldPosition).Normalized() + dirVec).Normalized();
|
||||
_physics.ApplyLinearImpulse(uid, pos * moveForce, body: physics);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
public sealed partial class AtmosphereSystem
|
||||
{
|
||||
private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent? visuals)
|
||||
private void ProcessCell(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, int fireCount, GasTileOverlayComponent visuals)
|
||||
{
|
||||
// Can't process a tile without air
|
||||
if (tile.Air == null)
|
||||
@@ -116,15 +117,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private void Archive(TileAtmosphere tile, int fireCount)
|
||||
{
|
||||
if (tile.Air != null)
|
||||
{
|
||||
tile.Air.Moles.AsSpan().CopyTo(tile.MolesArchived.AsSpan());
|
||||
tile.TemperatureArchived = tile.Air.Temperature;
|
||||
}
|
||||
else
|
||||
{
|
||||
tile.TemperatureArchived = tile.Temperature;
|
||||
}
|
||||
|
||||
tile.TemperatureArchived = tile.Temperature;
|
||||
tile.ArchivedCycle = fireCount;
|
||||
}
|
||||
|
||||
@@ -166,6 +161,12 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// <param name="disposeExcitedGroup">Whether to dispose of the tile's <see cref="ExcitedGroup"/></param>
|
||||
private void RemoveActiveTile(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, bool disposeExcitedGroup = true)
|
||||
{
|
||||
DebugTools.Assert(tile.Excited == gridAtmosphere.ActiveTiles.Contains(tile));
|
||||
DebugTools.Assert(tile.Excited || tile.ExcitedGroup == null);
|
||||
|
||||
if (!tile.Excited)
|
||||
return;
|
||||
|
||||
tile.Excited = false;
|
||||
gridAtmosphere.ActiveTiles.Remove(tile);
|
||||
|
||||
@@ -186,7 +187,6 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (tile.Air == null)
|
||||
return tile.HeatCapacity;
|
||||
|
||||
// Moles archived is not null if air is not null.
|
||||
return GetHeatCapacityCalculation(tile.MolesArchived!, tile.Space);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems;
|
||||
|
||||
@@ -8,10 +10,25 @@ public partial class AtmosphereSystem
|
||||
{
|
||||
private void InitializeMap()
|
||||
{
|
||||
SubscribeLocalEvent<MapAtmosphereComponent, ComponentInit>(OnMapStartup);
|
||||
SubscribeLocalEvent<MapAtmosphereComponent, ComponentRemove>(OnMapRemove);
|
||||
SubscribeLocalEvent<MapAtmosphereComponent, IsTileSpaceMethodEvent>(MapIsTileSpace);
|
||||
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixtureMethodEvent>(MapGetTileMixture);
|
||||
SubscribeLocalEvent<MapAtmosphereComponent, GetTileMixturesMethodEvent>(MapGetTileMixtures);
|
||||
SubscribeLocalEvent<MapAtmosphereComponent, ComponentGetState>(OnMapGetState);
|
||||
SubscribeLocalEvent<GridAtmosphereComponent, EntParentChangedMessage>(OnGridParentChanged);
|
||||
}
|
||||
|
||||
private void OnMapStartup(EntityUid uid, MapAtmosphereComponent component, ComponentInit args)
|
||||
{
|
||||
component.Mixture.MarkImmutable();
|
||||
component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
|
||||
}
|
||||
|
||||
private void OnMapRemove(EntityUid uid, MapAtmosphereComponent component, ComponentRemove args)
|
||||
{
|
||||
if (!TerminatingOrDeleted(uid))
|
||||
RefreshAllGridMapAtmospheres(uid);
|
||||
}
|
||||
|
||||
private void MapIsTileSpace(EntityUid uid, MapAtmosphereComponent component, ref IsTileSpaceMethodEvent args)
|
||||
@@ -28,54 +45,115 @@ public partial class AtmosphereSystem
|
||||
if (args.Handled)
|
||||
return;
|
||||
|
||||
// Clone the mixture, if possible.
|
||||
args.Mixture = component.Mixture?.Clone();
|
||||
args.Mixture = component.Mixture;
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void MapGetTileMixtures(EntityUid uid, MapAtmosphereComponent component, ref GetTileMixturesMethodEvent args)
|
||||
{
|
||||
if (args.Handled || component.Mixture == null)
|
||||
if (args.Handled)
|
||||
return;
|
||||
args.Handled = true;
|
||||
args.Mixtures ??= new GasMixture?[args.Tiles.Count];
|
||||
|
||||
for (var i = 0; i < args.Tiles.Count; i++)
|
||||
{
|
||||
args.Mixtures[i] ??= component.Mixture.Clone();
|
||||
args.Mixtures[i] ??= component.Mixture;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMapGetState(EntityUid uid, MapAtmosphereComponent component, ref ComponentGetState args)
|
||||
{
|
||||
args.State = new MapAtmosphereComponentState(_gasTileOverlaySystem.GetOverlayData(component.Mixture));
|
||||
args.State = new MapAtmosphereComponentState(component.Overlay);
|
||||
}
|
||||
|
||||
public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture, MapAtmosphereComponent? component = null)
|
||||
public void SetMapAtmosphere(EntityUid uid, bool space, GasMixture mixture)
|
||||
{
|
||||
DebugTools.Assert(HasComp<MapComponent>(uid));
|
||||
var component = EnsureComp<MapAtmosphereComponent>(uid);
|
||||
SetMapGasMixture(uid, mixture, component, false);
|
||||
SetMapSpace(uid, space, component, false);
|
||||
RefreshAllGridMapAtmospheres(uid);
|
||||
}
|
||||
|
||||
public void SetMapGasMixture(EntityUid uid, GasMixture mixture, MapAtmosphereComponent? component = null, bool updateTiles = true)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (!mixture.Immutable)
|
||||
{
|
||||
mixture = mixture.Clone();
|
||||
mixture.MarkImmutable();
|
||||
}
|
||||
|
||||
component.Mixture = mixture;
|
||||
component.Overlay = _gasTileOverlaySystem.GetOverlayData(component.Mixture);
|
||||
Dirty(uid, component);
|
||||
if (updateTiles)
|
||||
RefreshAllGridMapAtmospheres(uid);
|
||||
}
|
||||
|
||||
public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null, bool updateTiles = true)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
if (component.Space == space)
|
||||
return;
|
||||
|
||||
component.Space = space;
|
||||
component.Mixture = mixture;
|
||||
Dirty(uid, component);
|
||||
|
||||
if (updateTiles)
|
||||
RefreshAllGridMapAtmospheres(uid);
|
||||
}
|
||||
|
||||
public void SetMapGasMixture(EntityUid uid, GasMixture? mixture, MapAtmosphereComponent? component = null)
|
||||
/// <summary>
|
||||
/// Forces a refresh of all MapAtmosphere tiles on every grid on a map.
|
||||
/// </summary>
|
||||
public void RefreshAllGridMapAtmospheres(EntityUid map)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
return;
|
||||
|
||||
component.Mixture = mixture;
|
||||
Dirty(uid, component);
|
||||
DebugTools.Assert(HasComp<MapComponent>(map));
|
||||
var enumerator = AllEntityQuery<GridAtmosphereComponent, TransformComponent>();
|
||||
while (enumerator.MoveNext(out var grid, out var atmos, out var xform))
|
||||
{
|
||||
if (xform.MapUid == map)
|
||||
RefreshMapAtmosphereTiles((grid, atmos));
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMapSpace(EntityUid uid, bool space, MapAtmosphereComponent? component = null)
|
||||
/// <summary>
|
||||
/// Forces a refresh of all MapAtmosphere tiles on a given grid.
|
||||
/// </summary>
|
||||
private void RefreshMapAtmosphereTiles(Entity<GridAtmosphereComponent?> grid)
|
||||
{
|
||||
if (!Resolve(uid, ref component))
|
||||
if (!Resolve(grid.Owner, ref grid.Comp))
|
||||
return;
|
||||
|
||||
component.Space = space;
|
||||
Dirty(uid, component);
|
||||
var atmos = grid.Comp;
|
||||
foreach (var tile in atmos.MapTiles)
|
||||
{
|
||||
RemoveMapAtmos(atmos, tile);
|
||||
atmos.InvalidatedCoords.Add(tile.GridIndices);
|
||||
}
|
||||
atmos.MapTiles.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles updating map-atmospheres when grids move across maps.
|
||||
/// </summary>
|
||||
private void OnGridParentChanged(Entity<GridAtmosphereComponent> grid, ref EntParentChangedMessage args)
|
||||
{
|
||||
// Do nothing if detaching to nullspace
|
||||
if (!args.Transform.ParentUid.IsValid())
|
||||
return;
|
||||
|
||||
// Avoid doing work if moving from a space-map to another space-map.
|
||||
if (args.OldParent == null
|
||||
|| HasComp<MapAtmosphereComponent>(args.OldParent)
|
||||
|| HasComp<MapAtmosphereComponent>(args.Transform.ParentUid))
|
||||
{
|
||||
RefreshMapAtmosphereTiles((grid, grid));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using Content.Server.Doors.Systems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Random;
|
||||
@@ -27,7 +26,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private readonly TileAtmosphere[] _depressurizeSpaceTiles = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit];
|
||||
private readonly TileAtmosphere[] _depressurizeProgressionOrder = new TileAtmosphere[Atmospherics.MonstermosHardTileLimit * 2];
|
||||
|
||||
private void EqualizePressureInZone(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
|
||||
private void EqualizePressureInZone(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile,
|
||||
int cycleNum)
|
||||
{
|
||||
if (tile.Air == null || (tile.MonstermosInfo.LastCycle >= cycleNum))
|
||||
return; // Already done.
|
||||
@@ -56,7 +58,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return;
|
||||
}
|
||||
|
||||
var (_, mapGrid, gridAtmosphere) = ent;
|
||||
var gridAtmosphere = ent.Comp1;
|
||||
var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
|
||||
var totalMoles = 0f;
|
||||
_equalizeTiles[0] = tile;
|
||||
@@ -91,7 +93,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
// Looks like someone opened an airlock to space!
|
||||
|
||||
ExplosivelyDepressurize(ent, tile, cycleNum, visuals);
|
||||
ExplosivelyDepressurize(ent, tile, cycleNum);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -216,9 +218,13 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
for (var k = 0; k < Atmospherics.Directions; k++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << k);
|
||||
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
||||
if (!otherTile.AdjacentBits.IsFlagSet(direction))
|
||||
continue;
|
||||
|
||||
if (giver.MonstermosInfo.MoleDelta <= 0)
|
||||
break; // We're done here now. Let's not do more work than needed.
|
||||
|
||||
var otherTile2 = otherTile.AdjacentTiles[k];
|
||||
if (giver.MonstermosInfo.MoleDelta <= 0) break; // We're done here now. Let's not do more work than needed.
|
||||
if (otherTile2 == null || otherTile2.MonstermosInfo.LastQueueCycle != queueCycle) continue;
|
||||
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
|
||||
if (otherTile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
|
||||
@@ -332,7 +338,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
for (var i = 0; i < tileCount; i++)
|
||||
{
|
||||
var otherTile = _equalizeTiles[i]!;
|
||||
FinalizeEq(gridAtmosphere, otherTile, visuals);
|
||||
FinalizeEq(gridAtmosphere, otherTile, ent);
|
||||
}
|
||||
|
||||
for (var i = 0; i < tileCount; i++)
|
||||
@@ -341,12 +347,17 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
for (var j = 0; j < Atmospherics.Directions; j++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << j);
|
||||
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
||||
if (!otherTile.AdjacentBits.IsFlagSet(direction))
|
||||
continue;
|
||||
|
||||
var otherTile2 = otherTile.AdjacentTiles[j]!;
|
||||
if (otherTile2.AdjacentBits == 0)
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
|
||||
if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange) continue;
|
||||
if (otherTile2.Air != null && CompareExchange(otherTile2.Air, tile.Air) == GasCompareResult.NoExchange)
|
||||
continue;
|
||||
|
||||
AddActiveTile(gridAtmosphere, otherTile2);
|
||||
break;
|
||||
}
|
||||
@@ -359,7 +370,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
Array.Clear(_equalizeQueue, 0, Atmospherics.MonstermosTileLimit);
|
||||
}
|
||||
|
||||
private void ExplosivelyDepressurize(Entity<MapGridComponent, GridAtmosphereComponent> ent, TileAtmosphere tile, int cycleNum, GasTileOverlayComponent? visuals)
|
||||
private void ExplosivelyDepressurize(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile,
|
||||
int cycleNum)
|
||||
{
|
||||
// Check if explosive depressurization is enabled and if the tile is valid.
|
||||
if (!MonstermosDepressurization || tile.Air == null)
|
||||
@@ -368,7 +382,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
const int limit = Atmospherics.MonstermosHardTileLimit;
|
||||
|
||||
var totalMolesRemoved = 0f;
|
||||
var (owner, mapGrid, gridAtmosphere) = ent;
|
||||
var (owner, gridAtmosphere, visuals, mapGrid, _) = ent;
|
||||
var queueCycle = ++gridAtmosphere.EqualizationQueueCycleControl;
|
||||
|
||||
var tileCount = 0;
|
||||
@@ -388,20 +402,27 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
for (var j = 0; j < Atmospherics.Directions; j++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << j);
|
||||
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
||||
var otherTile2 = otherTile.AdjacentTiles[j];
|
||||
if (otherTile2?.Air == null) continue;
|
||||
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
|
||||
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle) continue;
|
||||
if (otherTile2?.Air == null)
|
||||
continue;
|
||||
|
||||
ConsiderFirelocks((owner, gridAtmosphere), otherTile, otherTile2, visuals, mapGrid);
|
||||
if (otherTile2.MonstermosInfo.LastQueueCycle == queueCycle)
|
||||
continue;
|
||||
|
||||
var direction = (AtmosDirection) (1 << j);
|
||||
DebugTools.Assert(otherTile.AdjacentBits.IsFlagSet(direction));
|
||||
DebugTools.Assert(otherTile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
|
||||
|
||||
ConsiderFirelocks(ent, otherTile, otherTile2);
|
||||
|
||||
// The firelocks might have closed on us.
|
||||
if (!otherTile.AdjacentBits.IsFlagSet(direction)) continue;
|
||||
if (!otherTile.AdjacentBits.IsFlagSet(direction))
|
||||
continue;
|
||||
|
||||
otherTile2.MonstermosInfo = new MonstermosInfo { LastQueueCycle = queueCycle };
|
||||
_depressurizeTiles[tileCount++] = otherTile2;
|
||||
if (tileCount >= limit) break;
|
||||
if (tileCount >= limit)
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -437,13 +458,21 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// Flood fill into this new direction
|
||||
var direction = (AtmosDirection) (1 << j);
|
||||
// Tiles in _depressurizeProgressionOrder cannot have null air.
|
||||
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space) continue;
|
||||
if (!otherTile.AdjacentBits.IsFlagSet(direction) && !otherTile.Space)
|
||||
continue;
|
||||
|
||||
var tile2 = otherTile.AdjacentTiles[j];
|
||||
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle) continue;
|
||||
if (tile2?.MonstermosInfo.LastQueueCycle != queueCycle)
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(tile2.AdjacentBits.IsFlagSet(direction.GetOpposite()));
|
||||
// If flood fill has already reached this tile, continue.
|
||||
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow) continue;
|
||||
if(tile2.Space) continue;
|
||||
if (tile2.MonstermosInfo.LastSlowQueueCycle == queueCycleSlow)
|
||||
continue;
|
||||
|
||||
if(tile2.Space)
|
||||
continue;
|
||||
|
||||
tile2.MonstermosInfo.CurrentTransferDirection = direction.GetOpposite();
|
||||
tile2.MonstermosInfo.CurrentTransferAmount = 0.0f;
|
||||
tile2.PressureSpecificTarget = otherTile.PressureSpecificTarget;
|
||||
@@ -535,7 +564,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
_physics.ApplyAngularImpulse(owner, Vector2Helpers.Cross(tile.GridIndices - gridPhysics.LocalCenter, direction) * totalMolesRemoved, body: gridPhysics);
|
||||
}
|
||||
|
||||
if(tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
|
||||
if (tileCount > 10 && (totalMolesRemoved / tileCount) > 10)
|
||||
_adminLog.Add(LogType.ExplosiveDepressurization, LogImpact.High,
|
||||
$"Explosive depressurization removed {totalMolesRemoved} moles from {tileCount} tiles starting from position {tile.GridIndices:position} on grid ID {tile.GridIndex:grid}");
|
||||
|
||||
@@ -544,36 +573,33 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
Array.Clear(_depressurizeProgressionOrder, 0, Atmospherics.MonstermosHardTileLimit * 2);
|
||||
}
|
||||
|
||||
private void ConsiderFirelocks(Entity<GridAtmosphereComponent> ent, TileAtmosphere tile, TileAtmosphere other, GasTileOverlayComponent? visuals, MapGridComponent mapGrid)
|
||||
private void ConsiderFirelocks(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile,
|
||||
TileAtmosphere other)
|
||||
{
|
||||
var reconsiderAdjacent = false;
|
||||
|
||||
foreach (var entity in mapGrid.GetAnchoredEntities(tile.GridIndices))
|
||||
var mapGrid = ent.Comp3;
|
||||
foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, tile.GridIndices))
|
||||
{
|
||||
if (!TryComp(entity, out FirelockComponent? firelock))
|
||||
continue;
|
||||
|
||||
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
|
||||
if (_firelockQuery.TryGetComponent(entity, out var firelock))
|
||||
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
|
||||
}
|
||||
|
||||
foreach (var entity in mapGrid.GetAnchoredEntities(other.GridIndices))
|
||||
foreach (var entity in _map.GetAnchoredEntities(ent.Owner, mapGrid, other.GridIndices))
|
||||
{
|
||||
if (!TryComp(entity, out FirelockComponent? firelock))
|
||||
continue;
|
||||
|
||||
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
|
||||
if (_firelockQuery.TryGetComponent(entity, out var firelock))
|
||||
reconsiderAdjacent |= _firelockSystem.EmergencyPressureStop(entity, firelock);
|
||||
}
|
||||
|
||||
if (!reconsiderAdjacent)
|
||||
return;
|
||||
|
||||
var (owner, gridAtmosphere) = ent;
|
||||
var tileEv = new UpdateAdjacentMethodEvent(owner, tile.GridIndices);
|
||||
var otherEv = new UpdateAdjacentMethodEvent(owner, other.GridIndices);
|
||||
GridUpdateAdjacent(owner, gridAtmosphere, ref tileEv);
|
||||
GridUpdateAdjacent(owner, gridAtmosphere, ref otherEv);
|
||||
InvalidateVisuals(tile.GridIndex, tile.GridIndices, visuals);
|
||||
InvalidateVisuals(other.GridIndex, other.GridIndices, visuals);
|
||||
UpdateAdjacentTiles(ent, tile);
|
||||
UpdateAdjacentTiles(ent, other);
|
||||
InvalidateVisuals(tile.GridIndex, tile.GridIndices, ent);
|
||||
InvalidateVisuals(other.GridIndex, other.GridIndices, ent);
|
||||
}
|
||||
|
||||
private void FinalizeEq(GridAtmosphereComponent gridAtmosphere, TileAtmosphere tile, GasTileOverlayComponent? visuals)
|
||||
@@ -642,7 +668,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (adj == null)
|
||||
{
|
||||
var nonNull = tile.AdjacentTiles.Where(x => x != null).Count();
|
||||
Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: {tile.Tile}, non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
|
||||
Log.Error($"Encountered null adjacent tile in {nameof(AdjustEqMovement)}. Dir: {direction}, Tile: ({tile.GridIndex}, {tile.GridIndices}), non-null adj count: {nonNull}, Trace: {Environment.StackTrace}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.Piping.Components;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Maps;
|
||||
@@ -8,6 +7,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
@@ -30,118 +30,63 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private int _currentRunAtmosphereIndex;
|
||||
private bool _simulationPaused;
|
||||
|
||||
private readonly List<Entity<GridAtmosphereComponent>> _currentRunAtmosphere = new();
|
||||
private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index)
|
||||
{
|
||||
var tile = atmosphere.Tiles.GetOrNew(index, out var existing);
|
||||
if (existing)
|
||||
return tile;
|
||||
|
||||
atmosphere.InvalidatedCoords.Add(index);
|
||||
tile.GridIndex = owner;
|
||||
tile.GridIndices = index;
|
||||
return tile;
|
||||
}
|
||||
|
||||
private readonly List<Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>> _currentRunAtmosphere = new();
|
||||
|
||||
/// <summary>
|
||||
/// Revalidates all invalid coordinates in a grid atmosphere.
|
||||
/// I.e., process any tiles that have had their airtight blockers modified.
|
||||
/// </summary>
|
||||
/// <param name="ent">The grid atmosphere in question.</param>
|
||||
/// <returns>Whether the process succeeded or got paused due to time constrains.</returns>
|
||||
private bool ProcessRevalidate(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals)
|
||||
private bool ProcessRevalidate(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
|
||||
{
|
||||
var (owner, atmosphere) = ent;
|
||||
if (!atmosphere.ProcessingPaused)
|
||||
if (ent.Comp4.MapUid == null)
|
||||
{
|
||||
atmosphere.CurrentRunInvalidatedCoordinates.Clear();
|
||||
atmosphere.CurrentRunInvalidatedCoordinates.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
|
||||
foreach (var tile in atmosphere.InvalidatedCoords)
|
||||
{
|
||||
atmosphere.CurrentRunInvalidatedCoordinates.Enqueue(tile);
|
||||
}
|
||||
atmosphere.InvalidatedCoords.Clear();
|
||||
Log.Error($"Attempted to process atmosphere on a map-less grid? Grid: {ToPrettyString(ent)}");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!TryComp(owner, out MapGridComponent? mapGridComp))
|
||||
return true;
|
||||
var (uid, atmosphere, visuals, grid, xform) = ent;
|
||||
var volume = GetVolumeForTiles(grid);
|
||||
TryComp(xform.MapUid, out MapAtmosphereComponent? mapAtmos);
|
||||
|
||||
var mapUid = _mapManager.GetMapEntityIdOrThrow(Transform(owner).MapID);
|
||||
if (!atmosphere.ProcessingPaused)
|
||||
{
|
||||
atmosphere.CurrentRunInvalidatedTiles.Clear();
|
||||
atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
|
||||
foreach (var indices in atmosphere.InvalidatedCoords)
|
||||
{
|
||||
var tile = GetOrNewTile(uid, atmosphere, indices);
|
||||
atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile);
|
||||
|
||||
var volume = GetVolumeForTiles(mapGridComp);
|
||||
// Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData.
|
||||
UpdateTileData(ent, mapAtmos, tile);
|
||||
}
|
||||
atmosphere.InvalidatedCoords.Clear();
|
||||
|
||||
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
|
||||
return false;
|
||||
}
|
||||
|
||||
var number = 0;
|
||||
while (atmosphere.CurrentRunInvalidatedCoordinates.TryDequeue(out var indices))
|
||||
while (atmosphere.CurrentRunInvalidatedTiles.TryDequeue(out var tile))
|
||||
{
|
||||
if (!atmosphere.Tiles.TryGetValue(indices, out var tile))
|
||||
{
|
||||
tile = new TileAtmosphere(owner, indices,
|
||||
new GasMixture(volume) { Temperature = Atmospherics.T20C });
|
||||
atmosphere.Tiles[indices] = tile;
|
||||
}
|
||||
|
||||
var airBlockedEv = new IsTileAirBlockedMethodEvent(owner, indices, MapGridComponent:mapGridComp);
|
||||
GridIsTileAirBlocked(owner, atmosphere, ref airBlockedEv);
|
||||
var isAirBlocked = airBlockedEv.Result;
|
||||
|
||||
var oldBlocked = tile.BlockedAirflow;
|
||||
var updateAdjacentEv = new UpdateAdjacentMethodEvent(owner, indices, mapGridComp);
|
||||
GridUpdateAdjacent(owner, atmosphere, ref updateAdjacentEv);
|
||||
|
||||
// Blocked airflow changed, rebuild excited groups!
|
||||
if (tile.Excited && tile.BlockedAirflow != oldBlocked)
|
||||
{
|
||||
RemoveActiveTile(atmosphere, tile);
|
||||
}
|
||||
|
||||
// Call this instead of the grid method as the map has a say on whether the tile is space or not.
|
||||
if ((!mapGridComp.TryGetTileRef(indices, out var t) || t.IsSpace(_tileDefinitionManager)) && !isAirBlocked)
|
||||
{
|
||||
tile.Air = GetTileMixture(null, mapUid, indices);
|
||||
tile.MolesArchived = tile.Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
|
||||
tile.Space = IsTileSpace(null, mapUid, indices, mapGridComp);
|
||||
}
|
||||
else if (isAirBlocked)
|
||||
{
|
||||
if (airBlockedEv.NoAir)
|
||||
{
|
||||
tile.Air = null;
|
||||
tile.MolesArchived = null;
|
||||
tile.ArchivedCycle = 0;
|
||||
tile.LastShare = 0f;
|
||||
tile.Hotspot = new Hotspot();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tile.Air == null && NeedsVacuumFixing(mapGridComp, indices))
|
||||
{
|
||||
var vacuumEv = new FixTileVacuumMethodEvent(owner, indices);
|
||||
GridFixTileVacuum(owner, atmosphere, ref vacuumEv);
|
||||
}
|
||||
|
||||
// Tile used to be space, but isn't anymore.
|
||||
if (tile.Space || (tile.Air?.Immutable ?? false))
|
||||
{
|
||||
tile.Air = null;
|
||||
tile.MolesArchived = null;
|
||||
tile.ArchivedCycle = 0;
|
||||
tile.LastShare = 0f;
|
||||
tile.Space = false;
|
||||
}
|
||||
|
||||
tile.Air ??= new GasMixture(volume){Temperature = Atmospherics.T20C};
|
||||
tile.MolesArchived ??= new float[Atmospherics.AdjustedNumberOfGases];
|
||||
}
|
||||
|
||||
// We activate the tile.
|
||||
AddActiveTile(atmosphere, tile);
|
||||
|
||||
// TODO ATMOS: Query all the contents of this tile (like walls) and calculate the correct thermal conductivity and heat capacity
|
||||
var tileDef = mapGridComp.TryGetTileRef(indices, out var tileRef)
|
||||
? tileRef.GetContentTileDefinition(_tileDefinitionManager)
|
||||
: null;
|
||||
|
||||
tile.ThermalConductivity = tileDef?.ThermalConductivity ?? 0.5f;
|
||||
tile.HeatCapacity = tileDef?.HeatCapacity ?? float.PositiveInfinity;
|
||||
InvalidateVisuals(owner, indices, visuals);
|
||||
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
var otherIndices = indices.Offset(direction);
|
||||
|
||||
if (atmosphere.Tiles.TryGetValue(otherIndices, out var otherTile))
|
||||
AddActiveTile(atmosphere, otherTile);
|
||||
}
|
||||
DebugTools.Assert(atmosphere.Tiles.GetValueOrDefault(tile.GridIndices) == tile);
|
||||
UpdateAdjacentTiles(ent, tile, activate: true);
|
||||
UpdateTileAir(ent, tile, volume);
|
||||
InvalidateVisuals(uid, tile.GridIndices, visuals);
|
||||
|
||||
if (number++ < InvalidCoordinatesLagCheckIterations)
|
||||
continue;
|
||||
@@ -149,12 +94,185 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
number = 0;
|
||||
// Process the rest next time.
|
||||
if (_simulationStopwatch.Elapsed.TotalMilliseconds >= AtmosMaxProcessTime)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrimDisconnectedMapTiles(ent);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method queued a tile and all of its neighbours up for processing by <see cref="TrimDisconnectedMapTiles"/>.
|
||||
/// </summary>
|
||||
public void QueueTileTrim(GridAtmosphereComponent atmos, TileAtmosphere tile)
|
||||
{
|
||||
if (!tile.TrimQueued)
|
||||
{
|
||||
tile.TrimQueued = true;
|
||||
atmos.PossiblyDisconnectedTiles.Add(tile);
|
||||
}
|
||||
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
var indices = tile.GridIndices.Offset(direction);
|
||||
if (atmos.Tiles.TryGetValue(indices, out var adj)
|
||||
&& adj.NoGridTile
|
||||
&& !adj.TrimQueued)
|
||||
{
|
||||
adj.TrimQueued = true;
|
||||
atmos.PossiblyDisconnectedTiles.Add(adj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tiles in a <see cref="GridAtmosphereComponent"/> are either grid-tiles, or they they should be are tiles
|
||||
/// adjacent to grid-tiles that represent the map's atmosphere. This method trims any map-tiles that are no longer
|
||||
/// adjacent to any grid-tiles.
|
||||
/// </summary>
|
||||
private void TrimDisconnectedMapTiles(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
|
||||
{
|
||||
var atmos = ent.Comp1;
|
||||
|
||||
foreach (var tile in atmos.PossiblyDisconnectedTiles)
|
||||
{
|
||||
tile.TrimQueued = false;
|
||||
if (!tile.NoGridTile)
|
||||
continue;
|
||||
|
||||
var connected = false;
|
||||
for (var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var indices = tile.GridIndices.Offset((AtmosDirection) (1 << i));
|
||||
if (_map.TryGetTile(ent.Comp3, indices, out var gridTile) && !gridTile.IsEmpty)
|
||||
{
|
||||
connected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!connected)
|
||||
{
|
||||
RemoveActiveTile(atmos, tile);
|
||||
atmos.Tiles.Remove(tile.GridIndices);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
atmos.PossiblyDisconnectedTiles.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a tile has a corresponding grid-tile, or whether it is a "map" tile. Also checks whether the
|
||||
/// tile should be considered "space"
|
||||
/// </summary>
|
||||
private void UpdateTileData(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
MapAtmosphereComponent? mapAtmos,
|
||||
TileAtmosphere tile)
|
||||
{
|
||||
var idx = tile.GridIndices;
|
||||
bool mapAtmosphere;
|
||||
if (_map.TryGetTile(ent.Comp3, idx, out var gTile) && !gTile.IsEmpty)
|
||||
{
|
||||
var contentDef = (ContentTileDefinition) _tileDefinitionManager[gTile.TypeId];
|
||||
mapAtmosphere = contentDef.MapAtmosphere;
|
||||
tile.ThermalConductivity = contentDef.ThermalConductivity;
|
||||
tile.HeatCapacity = contentDef.HeatCapacity;
|
||||
tile.NoGridTile = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
mapAtmosphere = true;
|
||||
tile.ThermalConductivity = 0.5f;
|
||||
tile.HeatCapacity = float.PositiveInfinity;
|
||||
|
||||
if (!tile.NoGridTile)
|
||||
{
|
||||
tile.NoGridTile = true;
|
||||
|
||||
// This tile just became a non-grid atmos tile.
|
||||
// It, or one of its neighbours, might now be completely disconnected from the grid.
|
||||
QueueTileTrim(ent.Comp1, tile);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAirtightData(ent.Owner, ent.Comp1, ent.Comp3, tile);
|
||||
|
||||
if (mapAtmosphere)
|
||||
{
|
||||
if (!tile.MapAtmosphere)
|
||||
{
|
||||
(tile.Air, tile.Space) = GetDefaultMapAtmosphere(mapAtmos);
|
||||
tile.MapAtmosphere = true;
|
||||
ent.Comp1.MapTiles.Add(tile);
|
||||
}
|
||||
|
||||
DebugTools.AssertNotNull(tile.Air);
|
||||
DebugTools.Assert(tile.Air?.Immutable ?? false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!tile.MapAtmosphere)
|
||||
return;
|
||||
|
||||
// Tile used to be exposed to the map's atmosphere, but isn't anymore.
|
||||
RemoveMapAtmos(ent.Comp1, tile);
|
||||
}
|
||||
|
||||
private void RemoveMapAtmos(GridAtmosphereComponent atmos, TileAtmosphere tile)
|
||||
{
|
||||
DebugTools.Assert(tile.MapAtmosphere);
|
||||
DebugTools.AssertNotNull(tile.Air);
|
||||
DebugTools.Assert(tile.Air?.Immutable ?? false);
|
||||
tile.MapAtmosphere = false;
|
||||
atmos.MapTiles.Remove(tile);
|
||||
tile.Air = null;
|
||||
Array.Clear(tile.MolesArchived);
|
||||
tile.ArchivedCycle = 0;
|
||||
tile.LastShare = 0f;
|
||||
tile.Space = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a grid-tile should have an air mixture, and give it one if it doesn't already have one.
|
||||
/// </summary>
|
||||
private void UpdateTileAir(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent,
|
||||
TileAtmosphere tile,
|
||||
float volume)
|
||||
{
|
||||
if (tile.MapAtmosphere)
|
||||
{
|
||||
DebugTools.AssertNotNull(tile.Air);
|
||||
DebugTools.Assert(tile.Air?.Immutable ?? false);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = tile.AirtightData;
|
||||
var fullyBlocked = data.BlockedDirections == AtmosDirection.All;
|
||||
|
||||
if (fullyBlocked && data.NoAirWhenBlocked)
|
||||
{
|
||||
if (tile.Air == null)
|
||||
return;
|
||||
|
||||
tile.Air = null;
|
||||
Array.Clear(tile.MolesArchived);
|
||||
tile.ArchivedCycle = 0;
|
||||
tile.LastShare = 0f;
|
||||
tile.Hotspot = new Hotspot();
|
||||
return;
|
||||
}
|
||||
|
||||
if (tile.Air != null)
|
||||
return;
|
||||
|
||||
tile.Air = new GasMixture(volume){Temperature = Atmospherics.T20C};
|
||||
|
||||
if (data.FixVacuum)
|
||||
GridFixTileVacuum(tile);
|
||||
}
|
||||
|
||||
private void QueueRunTiles(
|
||||
@@ -170,19 +288,16 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
}
|
||||
|
||||
private bool ProcessTileEqualize(Entity<GridAtmosphereComponent> ent, GasTileOverlayComponent? visuals)
|
||||
private bool ProcessTileEqualize(Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
|
||||
{
|
||||
var (uid, atmosphere) = ent;
|
||||
var atmosphere = ent.Comp1;
|
||||
if (!atmosphere.ProcessingPaused)
|
||||
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
|
||||
|
||||
if (!TryComp(uid, out MapGridComponent? mapGridComp))
|
||||
throw new Exception("Tried to process a grid atmosphere on an entity that isn't a grid!");
|
||||
|
||||
var number = 0;
|
||||
while (atmosphere.CurrentRunTiles.TryDequeue(out var tile))
|
||||
{
|
||||
EqualizePressureInZone((uid, mapGridComp, atmosphere), tile, atmosphere.UpdateCounter, visuals);
|
||||
EqualizePressureInZone(ent, tile, atmosphere.UpdateCounter);
|
||||
|
||||
if (number++ < LagCheckIterations)
|
||||
continue;
|
||||
@@ -198,7 +313,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent? visuals)
|
||||
private bool ProcessActiveTiles(GridAtmosphereComponent atmosphere, GasTileOverlayComponent visuals)
|
||||
{
|
||||
if(!atmosphere.ProcessingPaused)
|
||||
QueueRunTiles(atmosphere.CurrentRunTiles, atmosphere.ActiveTiles);
|
||||
@@ -240,11 +355,11 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
excitedGroup.BreakdownCooldown++;
|
||||
excitedGroup.DismantleCooldown++;
|
||||
|
||||
if(excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
|
||||
if (excitedGroup.BreakdownCooldown > Atmospherics.ExcitedGroupBreakdownCycles)
|
||||
ExcitedGroupSelfBreakdown(gridAtmosphere, excitedGroup);
|
||||
|
||||
else if(excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
|
||||
ExcitedGroupDismantle(gridAtmosphere, excitedGroup);
|
||||
else if (excitedGroup.DismantleCooldown > Atmospherics.ExcitedGroupsDismantleCycles)
|
||||
DeactivateGroupTiles(gridAtmosphere, excitedGroup);
|
||||
// TODO ATMOS. What is the point of this? why is this only de-exciting the group? Shouldn't it also dismantle it?
|
||||
|
||||
if (number++ < LagCheckIterations)
|
||||
continue;
|
||||
@@ -435,10 +550,10 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
_currentRunAtmosphereIndex = 0;
|
||||
_currentRunAtmosphere.Clear();
|
||||
|
||||
var query = EntityQueryEnumerator<GridAtmosphereComponent>();
|
||||
while (query.MoveNext(out var uid, out var grid))
|
||||
var query = EntityQueryEnumerator<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var atmos, out var overlay, out var grid, out var xform ))
|
||||
{
|
||||
_currentRunAtmosphere.Add((uid, grid));
|
||||
_currentRunAtmosphere.Add((uid, atmos, overlay, grid, xform));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,8 +563,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
for (; _currentRunAtmosphereIndex < _currentRunAtmosphere.Count; _currentRunAtmosphereIndex++)
|
||||
{
|
||||
var ent = _currentRunAtmosphere[_currentRunAtmosphereIndex];
|
||||
var (owner, atmosphere) = ent;
|
||||
TryComp(owner, out GasTileOverlayComponent? visuals);
|
||||
var (owner, atmosphere, visuals, grid, xform) = ent;
|
||||
|
||||
if (!TryComp(owner, out TransformComponent? x)
|
||||
|| x.MapUid == null
|
||||
@@ -474,13 +588,14 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
switch (atmosphere.State)
|
||||
{
|
||||
case AtmosphereProcessingState.Revalidate:
|
||||
if (!ProcessRevalidate(ent, visuals))
|
||||
if (!ProcessRevalidate(ent))
|
||||
{
|
||||
atmosphere.ProcessingPaused = true;
|
||||
return;
|
||||
}
|
||||
|
||||
atmosphere.ProcessingPaused = false;
|
||||
|
||||
// Next state depends on whether monstermos equalization is enabled or not.
|
||||
// Note: We do this here instead of on the tile equalization step to prevent ending it early.
|
||||
// Therefore, a change to this CVar might only be applied after that step is over.
|
||||
@@ -489,7 +604,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
: AtmosphereProcessingState.ActiveTiles;
|
||||
continue;
|
||||
case AtmosphereProcessingState.TileEqualize:
|
||||
if (!ProcessTileEqualize(ent, visuals))
|
||||
if (!ProcessTileEqualize(ent))
|
||||
{
|
||||
atmosphere.ProcessingPaused = true;
|
||||
return;
|
||||
@@ -499,7 +614,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
atmosphere.State = AtmosphereProcessingState.ActiveTiles;
|
||||
continue;
|
||||
case AtmosphereProcessingState.ActiveTiles:
|
||||
if (!ProcessActiveTiles(atmosphere, visuals))
|
||||
if (!ProcessActiveTiles(ent, ent))
|
||||
{
|
||||
atmosphere.ProcessingPaused = true;
|
||||
return;
|
||||
@@ -520,7 +635,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
atmosphere.State = AtmosphereProcessingState.HighPressureDelta;
|
||||
continue;
|
||||
case AtmosphereProcessingState.HighPressureDelta:
|
||||
if (!ProcessHighPressureDelta(ent))
|
||||
if (!ProcessHighPressureDelta((ent, ent)))
|
||||
{
|
||||
atmosphere.ProcessingPaused = true;
|
||||
return;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
@@ -12,7 +13,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
for(var i = 0; i < Atmospherics.Directions; i++)
|
||||
{
|
||||
var direction = (AtmosDirection) (1 << i);
|
||||
if (!directions.IsFlagSet(direction)) continue;
|
||||
if (!directions.IsFlagSet(direction))
|
||||
continue;
|
||||
|
||||
var adjacent = tile.AdjacentTiles[direction.ToIndex()];
|
||||
|
||||
@@ -92,7 +94,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
if (tile.Air == null)
|
||||
{
|
||||
if (other.Tile != null)
|
||||
// TODO ATMOS: why does this need to check if a tile exists if it doesn't use the tile?
|
||||
if (TryComp<MapGridComponent>(other.GridIndex, out var grid)
|
||||
&& _mapSystem.TryGetTileRef(other.GridIndex, grid, other.GridIndices, out var _))
|
||||
{
|
||||
TemperatureShareOpenToSolid(other, tile);
|
||||
}
|
||||
|
||||
@@ -41,20 +41,6 @@ public partial class AtmosphereSystem
|
||||
_gasTileOverlaySystem.Invalidate(gridUid, tile, comp);
|
||||
}
|
||||
|
||||
public bool NeedsVacuumFixing(MapGridComponent mapGrid, Vector2i indices)
|
||||
{
|
||||
var value = false;
|
||||
|
||||
var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices);
|
||||
|
||||
while (enumerator.MoveNext(out var airtight))
|
||||
{
|
||||
value |= airtight.FixVacuum;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the volume in liters for a number of tiles, on a specific grid.
|
||||
/// </summary>
|
||||
@@ -66,34 +52,45 @@ public partial class AtmosphereSystem
|
||||
return Atmospherics.CellVolume * mapGrid.TileSize * tiles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all obstructing <see cref="AirtightComponent"/> instances in a specific tile.
|
||||
/// </summary>
|
||||
/// <param name="mapGrid">The grid where to get the tile.</param>
|
||||
/// <param name="tile">The indices of the tile.</param>
|
||||
/// <returns>The enumerator for the airtight components.</returns>
|
||||
public AtmosObstructionEnumerator GetObstructingComponentsEnumerator(MapGridComponent mapGrid, Vector2i tile)
|
||||
{
|
||||
var ancEnumerator = mapGrid.GetAnchoredEntitiesEnumerator(tile);
|
||||
var airQuery = GetEntityQuery<AirtightComponent>();
|
||||
public readonly record struct AirtightData(AtmosDirection BlockedDirections, bool NoAirWhenBlocked,
|
||||
bool FixVacuum);
|
||||
|
||||
var enumerator = new AtmosObstructionEnumerator(ancEnumerator, airQuery);
|
||||
return enumerator;
|
||||
private void UpdateAirtightData(EntityUid uid, GridAtmosphereComponent atmos, MapGridComponent grid, TileAtmosphere tile)
|
||||
{
|
||||
var oldBlocked = tile.AirtightData.BlockedDirections;
|
||||
|
||||
tile.AirtightData = tile.NoGridTile
|
||||
? default
|
||||
: GetAirtightData(uid, grid, tile.GridIndices);
|
||||
|
||||
if (tile.AirtightData.BlockedDirections != oldBlocked && tile.ExcitedGroup != null)
|
||||
ExcitedGroupDispose(atmos, tile.ExcitedGroup);
|
||||
}
|
||||
|
||||
private AtmosDirection GetBlockedDirections(MapGridComponent mapGrid, Vector2i indices)
|
||||
private AirtightData GetAirtightData(EntityUid uid, MapGridComponent grid, Vector2i tile)
|
||||
{
|
||||
var value = AtmosDirection.Invalid;
|
||||
var blockedDirs = AtmosDirection.Invalid;
|
||||
var noAirWhenBlocked = false;
|
||||
var fixVacuum = false;
|
||||
|
||||
var enumerator = GetObstructingComponentsEnumerator(mapGrid, indices);
|
||||
|
||||
while (enumerator.MoveNext(out var airtight))
|
||||
foreach (var ent in _map.GetAnchoredEntities(uid, grid, tile))
|
||||
{
|
||||
if(airtight.AirBlocked)
|
||||
value |= airtight.AirBlockedDirection;
|
||||
if (!_airtightQuery.TryGetComponent(ent, out var airtight))
|
||||
continue;
|
||||
|
||||
fixVacuum |= airtight.FixVacuum;
|
||||
|
||||
if(!airtight.AirBlocked)
|
||||
continue;
|
||||
|
||||
blockedDirs |= airtight.AirBlockedDirection;
|
||||
noAirWhenBlocked |= airtight.NoAirWhenFullyAirBlocked;
|
||||
|
||||
if (blockedDirs == AtmosDirection.All && noAirWhenBlocked && fixVacuum)
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
return new AirtightData(blockedDirs, noAirWhenBlocked, fixVacuum);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Server.Body.Systems;
|
||||
using Content.Server.Fluids.EntitySystems;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -40,6 +41,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
private const float ExposedUpdateDelay = 1f;
|
||||
private float _exposedTimer = 0f;
|
||||
|
||||
private EntityQuery<GridAtmosphereComponent> _atmosQuery;
|
||||
private EntityQuery<AirtightComponent> _airtightQuery;
|
||||
private EntityQuery<FirelockComponent> _firelockQuery;
|
||||
private HashSet<EntityUid> _entSet = new();
|
||||
|
||||
public override void Initialize()
|
||||
@@ -55,6 +59,9 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
InitializeGridAtmosphere();
|
||||
InitializeMap();
|
||||
|
||||
_atmosQuery = GetEntityQuery<GridAtmosphereComponent>();
|
||||
_airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||
_firelockQuery = GetEntityQuery<FirelockComponent>();
|
||||
|
||||
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
|
||||
|
||||
|
||||
@@ -27,8 +27,12 @@ public sealed class AutomaticAtmosSystem : EntitySystem
|
||||
// Also, these calls are surprisingly slow.
|
||||
// TODO: Make tiledefmanager cache the IsSpace property, and turn this lookup-through-two-interfaces into
|
||||
// TODO: a simple array lookup, as tile IDs are likely contiguous, and there's at most 2^16 possibilities anyway.
|
||||
if (!((ev.OldTile.IsSpace(_tileDefinitionManager) && !ev.NewTile.IsSpace(_tileDefinitionManager)) ||
|
||||
(!ev.OldTile.IsSpace(_tileDefinitionManager) && ev.NewTile.IsSpace(_tileDefinitionManager))) ||
|
||||
|
||||
var oldSpace = ev.OldTile.IsSpace(_tileDefinitionManager);
|
||||
var newSpace = ev.NewTile.IsSpace(_tileDefinitionManager);
|
||||
|
||||
if (!(oldSpace && !newSpace ||
|
||||
!oldSpace && newSpace) ||
|
||||
_atmosphereSystem.HasAtmosphere(ev.Entity))
|
||||
return;
|
||||
|
||||
|
||||
@@ -260,13 +260,13 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
var gas = _atmo.GetGas(i);
|
||||
|
||||
if (mixture?.Moles[i] <= UIMinMoles)
|
||||
if (mixture?[i] <= UIMinMoles)
|
||||
continue;
|
||||
|
||||
if (mixture != null)
|
||||
{
|
||||
var gasName = Loc.GetString(gas.Name);
|
||||
gases.Add(new GasEntry(gasName, mixture.Moles[i], gas.Color));
|
||||
gases.Add(new GasEntry(gasName, mixture[i], gas.Color));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
var id = VisibleGasId[i];
|
||||
var gas = _atmosphereSystem.GetGas(id);
|
||||
var moles = mixture?.Moles[id] ?? 0f;
|
||||
var moles = mixture?[id] ?? 0f;
|
||||
ref var opacity = ref data.Opacity[i];
|
||||
|
||||
if (moles < gas.GasMolesVisible)
|
||||
@@ -217,13 +217,13 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
oldData = new GasOverlayData(tile.Hotspot.State, oldData.Opacity);
|
||||
}
|
||||
|
||||
if (tile.Air != null)
|
||||
if (tile is {Air: not null, NoGridTile: false})
|
||||
{
|
||||
for (var i = 0; i < VisibleGasId.Length; i++)
|
||||
{
|
||||
var id = VisibleGasId[i];
|
||||
var gas = _atmosphereSystem.GetGas(id);
|
||||
var moles = tile.Air.Moles[id];
|
||||
var moles = tile.Air[id];
|
||||
ref var oldOpacity = ref oldData.Opacity[i];
|
||||
|
||||
if (moles < gas.GasMolesVisible)
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Server.Atmos.Reactions;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -17,11 +18,13 @@ namespace Content.Server.Atmos
|
||||
{
|
||||
public static GasMixture SpaceGas => new() {Volume = Atmospherics.CellVolume, Temperature = Atmospherics.TCMB, Immutable = true};
|
||||
|
||||
// This must always have a length that is a multiple of 4 for SIMD acceleration.
|
||||
[DataField("moles")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
// No access, to ensure immutable mixtures are never accidentally mutated.
|
||||
[Access(typeof(SharedAtmosphereSystem), typeof(SharedAtmosDebugOverlaySystem), Other = AccessPermissions.None)]
|
||||
[DataField]
|
||||
public float[] Moles = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
|
||||
public float this[int gas] => Moles[gas];
|
||||
|
||||
[DataField("temperature")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
private float _temperature = Atmospherics.TCMB;
|
||||
@@ -59,9 +62,9 @@ namespace Content.Server.Atmos
|
||||
get => _temperature;
|
||||
set
|
||||
{
|
||||
DebugTools.Assert(!float.IsNaN(_temperature));
|
||||
if (Immutable) return;
|
||||
_temperature = MathF.Min(MathF.Max(value, Atmospherics.TCMB), Atmospherics.Tmax);
|
||||
DebugTools.Assert(!float.IsNaN(value));
|
||||
if (!Immutable)
|
||||
_temperature = MathF.Min(MathF.Max(value, Atmospherics.TCMB), Atmospherics.Tmax);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +83,20 @@ namespace Content.Server.Atmos
|
||||
Volume = volume;
|
||||
}
|
||||
|
||||
public GasMixture(float[] moles, float temp, float volume = Atmospherics.CellVolume)
|
||||
{
|
||||
if (moles.Length != Atmospherics.AdjustedNumberOfGases)
|
||||
throw new InvalidOperationException($"Invalid mole array length");
|
||||
|
||||
if (volume < 0)
|
||||
volume = 0;
|
||||
|
||||
DebugTools.Assert(!float.IsNaN(temp));
|
||||
_temperature = temp;
|
||||
Moles = moles;
|
||||
Volume = volume;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void MarkImmutable()
|
||||
{
|
||||
@@ -117,15 +134,16 @@ namespace Content.Server.Atmos
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AdjustMoles(int gasId, float quantity)
|
||||
{
|
||||
if (!Immutable)
|
||||
{
|
||||
if (!float.IsFinite(quantity))
|
||||
throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
|
||||
if (Immutable)
|
||||
return;
|
||||
|
||||
// Clamping is needed because x - x can be negative with floating point numbers. If we don't
|
||||
// clamp here, the caller always has to call GetMoles(), clamp, then SetMoles().
|
||||
Moles[gasId] = MathF.Max(Moles[gasId] + quantity, 0);
|
||||
}
|
||||
if (!float.IsFinite(quantity))
|
||||
throw new ArgumentException($"Invalid quantity \"{quantity}\" specified!", nameof(quantity));
|
||||
|
||||
// Clamping is needed because x - x can be negative with floating point numbers. If we don't
|
||||
// clamp here, the caller always has to call GetMoles(), clamp, then SetMoles().
|
||||
ref var moles = ref Moles[gasId];
|
||||
moles = MathF.Max(moles + quantity, 0);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -163,7 +181,8 @@ namespace Content.Server.Atmos
|
||||
{
|
||||
var moles = Moles[i];
|
||||
var otherMoles = removed.Moles[i];
|
||||
if (moles < Atmospherics.GasMinMoles || float.IsNaN(moles))
|
||||
|
||||
if ((moles < Atmospherics.GasMinMoles || float.IsNaN(moles)) && !Immutable)
|
||||
Moles[i] = 0;
|
||||
|
||||
if (otherMoles < Atmospherics.GasMinMoles || float.IsNaN(otherMoles))
|
||||
@@ -202,6 +221,9 @@ namespace Content.Server.Atmos
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
// ISerializationHooks is obsolete.
|
||||
// TODO add fixed-length-array serializer
|
||||
|
||||
// The arrays MUST have a specific length.
|
||||
Array.Resize(ref Moles, Atmospherics.AdjustedNumberOfGases);
|
||||
}
|
||||
@@ -229,8 +251,12 @@ namespace Content.Server.Atmos
|
||||
|
||||
public bool Equals(GasMixture? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
|
||||
return Moles.SequenceEqual(other.Moles)
|
||||
&& _temperature.Equals(other._temperature)
|
||||
&& ReactionResults.SequenceEqual(other.ReactionResults)
|
||||
@@ -258,11 +284,13 @@ namespace Content.Server.Atmos
|
||||
|
||||
public GasMixture Clone()
|
||||
{
|
||||
if (Immutable)
|
||||
return this;
|
||||
|
||||
var newMixture = new GasMixture()
|
||||
{
|
||||
Moles = (float[])Moles.Clone(),
|
||||
_temperature = _temperature,
|
||||
Immutable = Immutable,
|
||||
Volume = Volume,
|
||||
};
|
||||
return newMixture;
|
||||
|
||||
@@ -3,14 +3,12 @@ using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.EntitySystems;
|
||||
|
||||
public sealed class AtmosPipeAppearanceSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -31,7 +29,7 @@ public sealed class AtmosPipeAppearanceSystem : EntitySystem
|
||||
if (!Resolve(uid, ref appearance, ref container, ref xform, false))
|
||||
return;
|
||||
|
||||
if (!_mapManager.TryGetGrid(xform.GridUid, out var grid))
|
||||
if (!TryComp<MapGridComponent>(xform.GridUid, out var grid))
|
||||
return;
|
||||
|
||||
// get connected entities
|
||||
|
||||
@@ -136,7 +136,7 @@ public sealed class GasCanisterSystem : EntitySystem
|
||||
|
||||
for (int i = 0; i < containedGasArray.Length; i++)
|
||||
{
|
||||
containedGasDict.Add((Gas)i, canister.Air.Moles[i]);
|
||||
containedGasDict.Add((Gas)i, canister.Air[i]);
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.CanisterValve, impact, $"{ToPrettyString(args.Session.AttachedEntity.GetValueOrDefault()):player} set the valve on {ToPrettyString(uid):canister} to {args.Valve:valveState} while it contained [{string.Join(", ", containedGasDict)}]");
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed class GasCondenserSystem : EntitySystem
|
||||
var removed = inlet.Air.Remove(molesToConvert);
|
||||
for (var i = 0; i < Atmospherics.TotalNumberOfGases; i++)
|
||||
{
|
||||
var moles = removed.Moles[i];
|
||||
var moles = removed[i];
|
||||
if (moles <= 0)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -7,15 +7,14 @@ using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Construction.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GasPortableSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
|
||||
@@ -58,7 +57,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
{
|
||||
port = null;
|
||||
|
||||
if (!_mapManager.TryGetGrid(gridId, out var grid))
|
||||
if (!TryComp<MapGridComponent>(gridId, out var grid))
|
||||
return false;
|
||||
|
||||
foreach (var entityUid in grid.GetLocal(coordinates))
|
||||
|
||||
@@ -183,13 +183,7 @@ public sealed partial class TileAtmosCollectionSerializer : ITypeSerializer<Dict
|
||||
target.Clear();
|
||||
foreach (var (key, val) in source)
|
||||
{
|
||||
target.Add(key,
|
||||
new TileAtmosphere(
|
||||
val.GridIndex,
|
||||
val.GridIndices,
|
||||
val.Air?.Clone(),
|
||||
val.Air?.Immutable ?? false,
|
||||
val.Space));
|
||||
target.Add(key, new TileAtmosphere(val));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Maps;
|
||||
@@ -27,6 +28,9 @@ namespace Content.Server.Atmos
|
||||
[ViewVariables]
|
||||
public TileAtmosphere? PressureSpecificTarget { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This is either the pressure difference, or the quantity of moles transferred if monstermos is enabled.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float PressureDifference { get; set; }
|
||||
|
||||
@@ -51,6 +55,10 @@ namespace Content.Server.Atmos
|
||||
[ViewVariables]
|
||||
public readonly TileAtmosphere?[] AdjacentTiles = new TileAtmosphere[Atmospherics.Directions];
|
||||
|
||||
/// <summary>
|
||||
/// Neighbouring tiles to which air can flow. This is a combination of this tile's unblocked direction, and the
|
||||
/// unblocked directions on adjacent tiles.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public AtmosDirection AdjacentBits = AtmosDirection.Invalid;
|
||||
|
||||
@@ -72,10 +80,7 @@ namespace Content.Server.Atmos
|
||||
public EntityUid GridIndex { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public TileRef? Tile => GridIndices.GetTileRef(GridIndex);
|
||||
|
||||
[ViewVariables]
|
||||
public Vector2i GridIndices { get; }
|
||||
public Vector2i GridIndices;
|
||||
|
||||
[ViewVariables]
|
||||
public ExcitedGroup? ExcitedGroup { get; set; }
|
||||
@@ -92,7 +97,7 @@ namespace Content.Server.Atmos
|
||||
public float LastShare;
|
||||
|
||||
[ViewVariables]
|
||||
public float[]? MolesArchived;
|
||||
public readonly float[] MolesArchived = new float[Atmospherics.AdjustedNumberOfGases];
|
||||
|
||||
GasMixture IGasMixtureHolder.Air
|
||||
{
|
||||
@@ -103,8 +108,31 @@ namespace Content.Server.Atmos
|
||||
[ViewVariables]
|
||||
public float MaxFireTemperatureSustained { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, then this tile is directly exposed to the map's atmosphere, either because the grid has no tile at
|
||||
/// this position, or because the tile type is not airtight.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public AtmosDirection BlockedAirflow { get; set; } = AtmosDirection.Invalid;
|
||||
public bool MapAtmosphere;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this tile does not actually exist on the grid, it only exists to represent the map's atmosphere for
|
||||
/// adjacent grid tiles.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool NoGridTile;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this tile is queued for processing in <see cref="GridAtmosphereComponent.PossiblyDisconnectedTiles"/>
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool TrimQueued;
|
||||
|
||||
/// <summary>
|
||||
/// Cached information about airtight entities on this tile. This gets updated anytime a tile gets invalidated
|
||||
/// (i.e., gets added to <see cref="GridAtmosphereComponent.InvalidatedCoords"/>).
|
||||
/// </summary>
|
||||
public AtmosphereSystem.AirtightData AirtightData;
|
||||
|
||||
public TileAtmosphere(EntityUid gridIndex, Vector2i gridIndices, GasMixture? mixture = null, bool immutable = false, bool space = false)
|
||||
{
|
||||
@@ -112,10 +140,24 @@ namespace Content.Server.Atmos
|
||||
GridIndices = gridIndices;
|
||||
Air = mixture;
|
||||
Space = space;
|
||||
MolesArchived = Air != null ? new float[Atmospherics.AdjustedNumberOfGases] : null;
|
||||
|
||||
if(immutable)
|
||||
Air?.MarkImmutable();
|
||||
}
|
||||
|
||||
public TileAtmosphere(TileAtmosphere other)
|
||||
{
|
||||
GridIndex = other.GridIndex;
|
||||
GridIndices = other.GridIndices;
|
||||
Space = other.Space;
|
||||
NoGridTile = other.NoGridTile;
|
||||
MapAtmosphere = other.MapAtmosphere;
|
||||
Air = other.Air?.Clone();
|
||||
Array.Copy(other.MolesArchived, MolesArchived, MolesArchived.Length);
|
||||
}
|
||||
|
||||
public TileAtmosphere()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
Content.Server/Bed/Components/SnoringComponent.cs
Normal file
10
Content.Server/Bed/Components/SnoringComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.Bed.Sleep;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the snoring trait.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SnoringComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
@@ -67,7 +67,10 @@ namespace Content.Server.Bed.Sleep
|
||||
if (TryComp<SleepEmitSoundComponent>(uid, out var sleepSound))
|
||||
{
|
||||
var emitSound = EnsureComp<SpamEmitSoundComponent>(uid);
|
||||
emitSound.Sound = sleepSound.Snore;
|
||||
if (HasComp<SnoringComponent>(uid))
|
||||
{
|
||||
emitSound.Sound = sleepSound.Snore;
|
||||
}
|
||||
emitSound.PlayChance = sleepSound.Chance;
|
||||
emitSound.RollInterval = sleepSound.Interval;
|
||||
emitSound.PopUp = sleepSound.PopUp;
|
||||
|
||||
@@ -77,7 +77,7 @@ public sealed class LungSystem : EntitySystem
|
||||
foreach (var gas in Enum.GetValues<Gas>())
|
||||
{
|
||||
var i = (int) gas;
|
||||
var moles = lung.Air.Moles[i];
|
||||
var moles = lung.Air[i];
|
||||
if (moles <= 0)
|
||||
continue;
|
||||
var reagent = _atmosphereSystem.GasReagents[i];
|
||||
|
||||
@@ -128,9 +128,23 @@ namespace Content.Server.Chat.Managers
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Server message to {player:Player}: {message}");
|
||||
}
|
||||
|
||||
public void SendAdminAnnouncement(string message)
|
||||
public void SendAdminAnnouncement(string message, AdminFlags? flagBlacklist, AdminFlags? flagWhitelist)
|
||||
{
|
||||
var clients = _adminManager.ActiveAdmins.Select(p => p.Channel);
|
||||
var clients = _adminManager.ActiveAdmins.Where(p =>
|
||||
{
|
||||
var adminData = _adminManager.GetAdminData(p);
|
||||
|
||||
DebugTools.AssertNotNull(adminData);
|
||||
|
||||
if (adminData == null)
|
||||
return false;
|
||||
|
||||
if (flagBlacklist != null && adminData.HasFlag(flagBlacklist.Value))
|
||||
return false;
|
||||
|
||||
return flagWhitelist == null || adminData.HasFlag(flagWhitelist.Value);
|
||||
|
||||
}).Select(p => p.Channel);
|
||||
|
||||
var wrappedMessage = Loc.GetString("chat-manager-send-admin-announcement-wrap-message",
|
||||
("adminChannelName", Loc.GetString("chat-manager-admin-channel-name")), ("message", FormattedMessage.EscapeText(message)));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chat;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
@@ -21,7 +22,7 @@ namespace Content.Server.Chat.Managers
|
||||
void TrySendOOCMessage(ICommonSession player, string message, OOCChatType type);
|
||||
|
||||
void SendHookOOC(string sender, string message);
|
||||
void SendAdminAnnouncement(string message);
|
||||
void SendAdminAnnouncement(string message, AdminFlags? flagBlacklist = null, AdminFlags? flagWhitelist = null);
|
||||
void SendAdminAlert(string message);
|
||||
void SendAdminAlert(EntityUid player, string message);
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
[UsedImplicitly]
|
||||
internal sealed class VaporSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
|
||||
[Dependency] private readonly ThrowingSystem _throwing = default!;
|
||||
@@ -64,8 +64,8 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
// Set Move
|
||||
if (EntityManager.TryGetComponent(vapor, out PhysicsComponent? physics))
|
||||
{
|
||||
_physics.SetLinearDamping(physics, 0f);
|
||||
_physics.SetAngularDamping(physics, 0f);
|
||||
_physics.SetLinearDamping(vapor, physics, 0f);
|
||||
_physics.SetAngularDamping(vapor, physics, 0f);
|
||||
|
||||
_throwing.TryThrow(vapor, dir, speed, user: user);
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Content.Server.Chemistry.EntitySystems
|
||||
{
|
||||
vapor.ReactTimer = 0;
|
||||
|
||||
var tile = gridComp.GetTileRef(xform.Coordinates.ToVector2i(EntityManager, _mapManager));
|
||||
var tile = _map.GetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates);
|
||||
foreach (var reagentQuantity in contents.Contents.ToArray())
|
||||
{
|
||||
if (reagentQuantity.Quantity == FixedPoint2.Zero) continue;
|
||||
|
||||
@@ -16,12 +16,15 @@ public sealed partial class ModifyLungGas : ReagentEffect
|
||||
|
||||
public override void Effect(ReagentEffectArgs args)
|
||||
{
|
||||
if (args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung))
|
||||
if (!args.EntityManager.TryGetComponent<LungComponent>(args.OrganEntity, out var lung))
|
||||
return;
|
||||
|
||||
foreach (var (gas, ratio) in _ratios)
|
||||
{
|
||||
foreach (var (gas, ratio) in _ratios)
|
||||
{
|
||||
lung.Air.Moles[(int) gas] += (ratio * args.Quantity.Float()) / Atmospherics.BreathMolesToReagentMultiplier;
|
||||
}
|
||||
var quantity = ratio * args.Quantity.Float() / Atmospherics.BreathMolesToReagentMultiplier;
|
||||
if (quantity < 0)
|
||||
quantity = Math.Max(quantity, -lung.Air[(int)gas]);
|
||||
lung.Air.AdjustMoles(gas, quantity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.Examine;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Construction.Conditions
|
||||
@@ -46,9 +47,18 @@ namespace Content.Server.Construction.Conditions
|
||||
if (transform.GridUid == null)
|
||||
return false;
|
||||
|
||||
var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve<IMapManager>());
|
||||
var transformSys = entityManager.System<SharedTransformSystem>();
|
||||
var indices = transform.Coordinates.ToVector2i(entityManager, IoCManager.Resolve<IMapManager>(), transformSys);
|
||||
var lookup = entityManager.EntitySysManager.GetEntitySystem<EntityLookupSystem>();
|
||||
var entities = indices.GetEntitiesInTile(transform.GridUid.Value, LookupFlags.Approximate | LookupFlags.Static, lookup);
|
||||
|
||||
|
||||
if (!entityManager.TryGetComponent<MapGridComponent>(transform.GridUid.Value, out var grid))
|
||||
return !HasEntity;
|
||||
|
||||
if (!entityManager.System<SharedMapSystem>().TryGetTileRef(transform.GridUid.Value, grid, indices, out var tile))
|
||||
return !HasEntity;
|
||||
|
||||
var entities = tile.GetEntitiesInTile(LookupFlags.Approximate | LookupFlags.Static, lookup);
|
||||
|
||||
foreach (var ent in entities)
|
||||
{
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Construction.Components;
|
||||
using Content.Server.Storage.EntitySystems;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
@@ -15,7 +14,6 @@ using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Storage;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -30,8 +28,7 @@ namespace Content.Server.Construction
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _handsSystem = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookupSystem = default!;
|
||||
[Dependency] private readonly StorageSystem _storageSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
|
||||
// --- WARNING! LEGACY CODE AHEAD! ---
|
||||
// This entire file contains the legacy code for initial construction.
|
||||
@@ -465,7 +462,7 @@ namespace Content.Server.Construction
|
||||
return;
|
||||
}
|
||||
|
||||
var mapPos = location.ToMap(EntityManager);
|
||||
var mapPos = location.ToMap(EntityManager, _transformSystem);
|
||||
var predicate = GetPredicate(constructionPrototype.CanBuildInImpassable, mapPos);
|
||||
|
||||
if (!_interactionSystem.InRangeUnobstructed(user, mapPos, predicate: predicate))
|
||||
|
||||
@@ -111,7 +111,8 @@ public sealed record AdminMessageRecord(
|
||||
bool Deleted,
|
||||
PlayerRecord? DeletedBy,
|
||||
DateTimeOffset? DeletedAt,
|
||||
bool Seen) : IAdminRemarksRecord;
|
||||
bool Seen,
|
||||
bool Dismissed) : IAdminRemarksRecord;
|
||||
|
||||
|
||||
public sealed record PlayerRecord(
|
||||
|
||||
@@ -1151,7 +1151,8 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
entity.Deleted,
|
||||
MakePlayerRecord(entity.DeletedBy),
|
||||
NormalizeDatabaseTime(entity.DeletedAt),
|
||||
entity.Seen);
|
||||
entity.Seen,
|
||||
entity.Dismissed);
|
||||
}
|
||||
|
||||
public async Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
|
||||
@@ -1430,11 +1431,13 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
|
||||
return entities.Select(MakeAdminMessageRecord).ToList();
|
||||
}
|
||||
|
||||
public async Task MarkMessageAsSeen(int id)
|
||||
public async Task MarkMessageAsSeen(int id, bool dismissedToo)
|
||||
{
|
||||
await using var db = await GetDb();
|
||||
var message = await db.DbContext.AdminMessages.SingleAsync(m => m.Id == id);
|
||||
message.Seen = true;
|
||||
if (dismissedToo)
|
||||
message.Dismissed = true;
|
||||
await db.DbContext.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user