Merge remote-tracking branch 'space-station-14/master' into 21-04-2024-cleaning-up
# Conflicts: # Content.IntegrationTests/Tests/PostMapInitTest.cs # Resources/Prototypes/Maps/Pools/default.yml # Resources/Prototypes/Maps/europa.yml # Resources/Prototypes/Recipes/Lathes/electronics.yml # Resources/Textures/Interface/Nano/item_status_left.svg.96dpi.png # Resources/Textures/Interface/Nano/item_status_middle.svg.96dpi.png # Resources/Textures/Interface/Nano/item_status_right.svg.96dpi.png
This commit is contained in:
@@ -9,7 +9,7 @@ indent_style = space
|
||||
tab_width = 4
|
||||
|
||||
# New line preferences
|
||||
end_of_line = crlf:suggestion
|
||||
#end_of_line = crlf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
@@ -104,7 +104,6 @@ csharp_preferred_modifier_order = public, private, protected, internal, new, abs
|
||||
|
||||
# 'using' directive preferences
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_style_namespace_declarations = file_scoped:suggestion
|
||||
|
||||
#### C# Formatting Rules ####
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -46,7 +46,7 @@ public class MapLoadBenchmark
|
||||
PoolManager.Shutdown();
|
||||
}
|
||||
|
||||
public static readonly string[] MapsSource = { "Empty", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry" };
|
||||
public static readonly string[] MapsSource = { "Empty", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry", "Oasis" };
|
||||
|
||||
[ParamsSource(nameof(MapsSource))]
|
||||
public string Map;
|
||||
|
||||
@@ -126,12 +126,15 @@ namespace Content.Client.Administration.Managers
|
||||
|
||||
public AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false)
|
||||
{
|
||||
return uid == _player.LocalEntity ? _adminData : null;
|
||||
if (uid == _player.LocalEntity && (_adminData?.Active ?? includeDeAdmin))
|
||||
return _adminData;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AdminData? GetAdminData(ICommonSession session, bool includeDeAdmin = false)
|
||||
{
|
||||
if (_player.LocalUser == session.UserId)
|
||||
if (_player.LocalUser == session.UserId && (_adminData?.Active ?? includeDeAdmin))
|
||||
return _adminData;
|
||||
|
||||
return null;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Content.Client.Administration.UI.CustomControls;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
@@ -11,6 +12,7 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -32,8 +34,11 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
// This is less efficient than just holding a reference to the root control and enumerating children, but you
|
||||
// have to know how the controls are nested, which makes the code more complicated.
|
||||
private readonly List<CheckBox> _roleCheckboxes = new();
|
||||
private readonly ISawmill _banpanelSawmill;
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private enum TabNumbers
|
||||
{
|
||||
@@ -65,6 +70,7 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_banpanelSawmill = _logManager.GetSawmill("admin.banpanel");
|
||||
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
|
||||
PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged();
|
||||
PlayerCheckbox.OnPressed += _ =>
|
||||
@@ -104,6 +110,11 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
};
|
||||
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
|
||||
|
||||
IpCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanIpBanDefault);
|
||||
HwidCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanHwidBanDefault);
|
||||
LastConnCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanUseLastDetails);
|
||||
EraseCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanErasePlayer);
|
||||
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) NoteSeverity.None);
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) NoteSeverity.Minor);
|
||||
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) NoteSeverity.Medium);
|
||||
@@ -175,6 +186,39 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
c.Pressed = args.Pressed;
|
||||
}
|
||||
}
|
||||
|
||||
if (args.Pressed)
|
||||
{
|
||||
if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity))
|
||||
{
|
||||
_banpanelSawmill
|
||||
.Warning("Departmental role ban severity could not be parsed from config!");
|
||||
return;
|
||||
}
|
||||
SeverityOption.SelectId((int) newSeverity);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var childContainer in RolesContainer.Children)
|
||||
{
|
||||
if (childContainer is Container)
|
||||
{
|
||||
foreach (var child in childContainer.Children)
|
||||
{
|
||||
if (child is CheckBox { Pressed: true })
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity))
|
||||
{
|
||||
_banpanelSawmill
|
||||
.Warning("Role ban severity could not be parsed from config!");
|
||||
return;
|
||||
}
|
||||
SeverityOption.SelectId((int) newSeverity);
|
||||
}
|
||||
};
|
||||
outerContainer.AddChild(innerContainer);
|
||||
foreach (var role in roleList)
|
||||
@@ -353,6 +397,35 @@ public sealed partial class BanPanel : DefaultWindow
|
||||
{
|
||||
TypeOption.ModulateSelfOverride = null;
|
||||
Tabs.SetTabVisible((int) TabNumbers.Roles, TypeOption.SelectedId == (int) Types.Role);
|
||||
NoteSeverity? newSeverity = null;
|
||||
switch (TypeOption.SelectedId)
|
||||
{
|
||||
case (int)Types.Server:
|
||||
if (Enum.TryParse(_cfg.GetCVar(CCVars.ServerBanDefaultSeverity), true, out NoteSeverity serverSeverity))
|
||||
newSeverity = serverSeverity;
|
||||
else
|
||||
{
|
||||
_banpanelSawmill
|
||||
.Warning("Server ban severity could not be parsed from config!");
|
||||
}
|
||||
|
||||
break;
|
||||
case (int) Types.Role:
|
||||
|
||||
if (Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity roleSeverity))
|
||||
{
|
||||
newSeverity = roleSeverity;
|
||||
}
|
||||
else
|
||||
{
|
||||
_banpanelSawmill
|
||||
.Warning("Role ban severity could not be parsed from config!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (newSeverity != null)
|
||||
SeverityOption.SelectId((int) newSeverity.Value);
|
||||
}
|
||||
|
||||
private void UpdateSubmitEnabled()
|
||||
|
||||
@@ -163,6 +163,26 @@ namespace Content.Client.Atmos.UI
|
||||
parent.AddChild(panel);
|
||||
panel.AddChild(dataContainer);
|
||||
|
||||
// Volume label
|
||||
var volBox = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal };
|
||||
|
||||
volBox.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("gas-analyzer-window-volume-text")
|
||||
});
|
||||
volBox.AddChild(new Control
|
||||
{
|
||||
MinSize = new Vector2(10, 0),
|
||||
HorizontalExpand = true
|
||||
});
|
||||
volBox.AddChild(new Label
|
||||
{
|
||||
Text = Loc.GetString("gas-analyzer-window-volume-val-text", ("volume", $"{gasMix.Volume:0.##}")),
|
||||
Align = Label.AlignMode.Right,
|
||||
HorizontalExpand = true
|
||||
});
|
||||
dataContainer.AddChild(volBox);
|
||||
|
||||
// Pressure label
|
||||
var presBox = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal };
|
||||
|
||||
|
||||
119
Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs
Normal file
119
Content.Client/Audio/Jukebox/JukeboxBoundUserInterface.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using Content.Shared.Audio.Jukebox;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Audio.Jukebox;
|
||||
|
||||
public sealed class JukeboxBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private JukeboxMenu? _menu;
|
||||
|
||||
public JukeboxBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = new JukeboxMenu();
|
||||
_menu.OnClose += Close;
|
||||
_menu.OpenCentered();
|
||||
|
||||
_menu.OnPlayPressed += args =>
|
||||
{
|
||||
if (args)
|
||||
{
|
||||
SendMessage(new JukeboxPlayingMessage());
|
||||
}
|
||||
else
|
||||
{
|
||||
SendMessage(new JukeboxPauseMessage());
|
||||
}
|
||||
};
|
||||
|
||||
_menu.OnStopPressed += () =>
|
||||
{
|
||||
SendMessage(new JukeboxStopMessage());
|
||||
};
|
||||
|
||||
_menu.OnSongSelected += SelectSong;
|
||||
|
||||
_menu.SetTime += SetTime;
|
||||
PopulateMusic();
|
||||
Reload();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reloads the attached menu if it exists.
|
||||
/// </summary>
|
||||
public void Reload()
|
||||
{
|
||||
if (_menu == null || !EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox))
|
||||
return;
|
||||
|
||||
_menu.SetAudioStream(jukebox.AudioStream);
|
||||
|
||||
if (_protoManager.TryIndex(jukebox.SelectedSongId, out var songProto))
|
||||
{
|
||||
var length = EntMan.System<AudioSystem>().GetAudioLength(songProto.Path.Path.ToString());
|
||||
_menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
_menu.SetSelectedSong(string.Empty, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void PopulateMusic()
|
||||
{
|
||||
_menu?.Populate(_protoManager.EnumeratePrototypes<JukeboxPrototype>());
|
||||
}
|
||||
|
||||
public void SelectSong(ProtoId<JukeboxPrototype> songid)
|
||||
{
|
||||
SendMessage(new JukeboxSelectedMessage(songid));
|
||||
}
|
||||
|
||||
public void SetTime(float time)
|
||||
{
|
||||
var sentTime = time;
|
||||
|
||||
// You may be wondering, what the fuck is this
|
||||
// Well we want to be able to predict the playback slider change, of which there are many ways to do it
|
||||
// We can't just use SendPredictedMessage because it will reset every tick and audio updates every frame
|
||||
// so it will go BRRRRT
|
||||
// Using ping gets us close enough that it SHOULD, MOST OF THE TIME, fall within the 0.1 second tolerance
|
||||
// that's still on engine so our playback position never gets corrected.
|
||||
if (EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox) &&
|
||||
EntMan.TryGetComponent(jukebox.AudioStream, out AudioComponent? audioComp))
|
||||
{
|
||||
audioComp.PlaybackPosition = time;
|
||||
}
|
||||
|
||||
SendMessage(new JukeboxSetTimeMessage(sentTime));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
_menu.OnClose -= Close;
|
||||
_menu.Dispose();
|
||||
_menu = null;
|
||||
}
|
||||
}
|
||||
|
||||
18
Content.Client/Audio/Jukebox/JukeboxMenu.xaml
Normal file
18
Content.Client/Audio/Jukebox/JukeboxMenu.xaml
Normal file
@@ -0,0 +1,18 @@
|
||||
<ui:FancyWindow xmlns="https://spacestation14.io" xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="400 500" Title="{Loc 'jukebox-menu-title'}">
|
||||
<BoxContainer Margin="4 0" Orientation="Vertical">
|
||||
<ItemList Name="MusicList" SelectMode="Button" Margin="3 3 3 3"
|
||||
HorizontalExpand="True" VerticalExpand="True" SizeFlagsStretchRatio="8"/>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Name="SongSelected" Text="{Loc 'jukebox-menu-selectedsong'}" />
|
||||
<Label Name="SongName" Text="---" />
|
||||
<Slider Name="PlaybackSlider" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True"
|
||||
VerticalExpand="False" SizeFlagsStretchRatio="1">
|
||||
<Button Name="PlayButton" Text="{Loc 'jukebox-menu-buttonplay'}" />
|
||||
<Button Name="StopButton" Text="{Loc 'jukebox-menu-buttonstop'}" />
|
||||
<Label Name="DurationLabel" Text="00:00 / 00:00" HorizontalAlignment="Right" HorizontalExpand="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ui:FancyWindow>
|
||||
166
Content.Client/Audio/Jukebox/JukeboxMenu.xaml.cs
Normal file
166
Content.Client/Audio/Jukebox/JukeboxMenu.xaml.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using Content.Shared.Audio.Jukebox;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
|
||||
|
||||
namespace Content.Client.Audio.Jukebox;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class JukeboxMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
private AudioSystem _audioSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Are we currently 'playing' or paused for the play / pause button.
|
||||
/// </summary>
|
||||
private bool _playState;
|
||||
|
||||
/// <summary>
|
||||
/// True if playing, false if paused.
|
||||
/// </summary>
|
||||
public event Action<bool>? OnPlayPressed;
|
||||
public event Action? OnStopPressed;
|
||||
public event Action<ProtoId<JukeboxPrototype>>? OnSongSelected;
|
||||
public event Action<float>? SetTime;
|
||||
|
||||
private EntityUid? _audio;
|
||||
|
||||
private float _lockTimer;
|
||||
|
||||
public JukeboxMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_audioSystem = _entManager.System<AudioSystem>();
|
||||
|
||||
MusicList.OnItemSelected += args =>
|
||||
{
|
||||
var entry = MusicList[args.ItemIndex];
|
||||
|
||||
if (entry.Metadata is not string juke)
|
||||
return;
|
||||
|
||||
OnSongSelected?.Invoke(juke);
|
||||
};
|
||||
|
||||
PlayButton.OnPressed += args =>
|
||||
{
|
||||
OnPlayPressed?.Invoke(!_playState);
|
||||
};
|
||||
|
||||
StopButton.OnPressed += args =>
|
||||
{
|
||||
OnStopPressed?.Invoke();
|
||||
};
|
||||
PlaybackSlider.OnReleased += PlaybackSliderKeyUp;
|
||||
|
||||
SetPlayPauseButton(_audioSystem.IsPlaying(_audio), force: true);
|
||||
}
|
||||
|
||||
public JukeboxMenu(AudioSystem audioSystem)
|
||||
{
|
||||
_audioSystem = audioSystem;
|
||||
}
|
||||
|
||||
public void SetAudioStream(EntityUid? audio)
|
||||
{
|
||||
_audio = audio;
|
||||
}
|
||||
|
||||
private void PlaybackSliderKeyUp(Slider args)
|
||||
{
|
||||
SetTime?.Invoke(PlaybackSlider.Value);
|
||||
_lockTimer = 0.5f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-populates the list of jukebox prototypes available.
|
||||
/// </summary>
|
||||
public void Populate(IEnumerable<JukeboxPrototype> jukeboxProtos)
|
||||
{
|
||||
MusicList.Clear();
|
||||
|
||||
foreach (var entry in jukeboxProtos)
|
||||
{
|
||||
MusicList.AddItem(entry.Name, metadata: entry.ID);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetPlayPauseButton(bool playing, bool force = false)
|
||||
{
|
||||
if (_playState == playing && !force)
|
||||
return;
|
||||
|
||||
_playState = playing;
|
||||
|
||||
if (playing)
|
||||
{
|
||||
PlayButton.Text = Loc.GetString("jukebox-menu-buttonpause");
|
||||
return;
|
||||
}
|
||||
|
||||
PlayButton.Text = Loc.GetString("jukebox-menu-buttonplay");
|
||||
}
|
||||
|
||||
public void SetSelectedSong(string name, float length)
|
||||
{
|
||||
SetSelectedSongText(name);
|
||||
PlaybackSlider.MaxValue = length;
|
||||
PlaybackSlider.SetValueWithoutEvent(0);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (_lockTimer > 0f)
|
||||
{
|
||||
_lockTimer -= args.DeltaSeconds;
|
||||
}
|
||||
|
||||
PlaybackSlider.Disabled = _lockTimer > 0f;
|
||||
|
||||
if (_entManager.TryGetComponent(_audio, out AudioComponent? audio))
|
||||
{
|
||||
DurationLabel.Text = $@"{TimeSpan.FromSeconds(audio.PlaybackPosition):mm\:ss} / {_audioSystem.GetAudioLength(audio.FileName):mm\:ss}";
|
||||
}
|
||||
else
|
||||
{
|
||||
DurationLabel.Text = $"00:00 / 00:00";
|
||||
}
|
||||
|
||||
if (PlaybackSlider.Grabbed)
|
||||
return;
|
||||
|
||||
if (audio != null || _entManager.TryGetComponent(_audio, out audio))
|
||||
{
|
||||
PlaybackSlider.SetValueWithoutEvent(audio.PlaybackPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
PlaybackSlider.SetValueWithoutEvent(0f);
|
||||
}
|
||||
|
||||
SetPlayPauseButton(_audioSystem.IsPlaying(_audio, audio));
|
||||
}
|
||||
|
||||
public void SetSelectedSongText(string? text)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
SongName.Text = text;
|
||||
}
|
||||
else
|
||||
{
|
||||
SongName.Text = "---";
|
||||
}
|
||||
}
|
||||
}
|
||||
153
Content.Client/Audio/Jukebox/JukeboxSystem.cs
Normal file
153
Content.Client/Audio/Jukebox/JukeboxSystem.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using Content.Shared.Audio.Jukebox;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Audio.Jukebox;
|
||||
|
||||
|
||||
public sealed class JukeboxSystem : SharedJukeboxSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<JukeboxComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<JukeboxComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<JukeboxComponent, AfterAutoHandleStateEvent>(OnJukeboxAfterState);
|
||||
|
||||
_protoManager.PrototypesReloaded += OnProtoReload;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_protoManager.PrototypesReloaded -= OnProtoReload;
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.WasModified<JukeboxPrototype>())
|
||||
return;
|
||||
|
||||
var query = AllEntityQuery<JukeboxComponent, UserInterfaceComponent>();
|
||||
|
||||
while (query.MoveNext(out _, out var ui))
|
||||
{
|
||||
if (!ui.OpenInterfaces.TryGetValue(JukeboxUiKey.Key, out var baseBui) ||
|
||||
baseBui is not JukeboxBoundUserInterface bui)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bui.PopulateMusic();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJukeboxAfterState(Entity<JukeboxComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (!TryComp(ent, out UserInterfaceComponent? ui))
|
||||
return;
|
||||
|
||||
if (!ui.OpenInterfaces.TryGetValue(JukeboxUiKey.Key, out var baseBui) ||
|
||||
baseBui is not JukeboxBoundUserInterface bui)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bui.Reload();
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, JukeboxComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
|
||||
if (!TryComp<AppearanceComponent>(uid, out var appearance) ||
|
||||
!_appearanceSystem.TryGetData<JukeboxVisualState>(uid, JukeboxVisuals.VisualState, out var visualState, appearance))
|
||||
{
|
||||
visualState = JukeboxVisualState.On;
|
||||
}
|
||||
|
||||
UpdateAppearance(uid, visualState, component, sprite);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, JukeboxComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(JukeboxVisuals.VisualState, out var visualStateObject) ||
|
||||
visualStateObject is not JukeboxVisualState visualState)
|
||||
{
|
||||
visualState = JukeboxVisualState.On;
|
||||
}
|
||||
|
||||
UpdateAppearance(uid, visualState, component, args.Sprite);
|
||||
}
|
||||
|
||||
private void UpdateAppearance(EntityUid uid, JukeboxVisualState visualState, JukeboxComponent component, SpriteComponent sprite)
|
||||
{
|
||||
SetLayerState(JukeboxVisualLayers.Base, component.OffState, sprite);
|
||||
|
||||
switch (visualState)
|
||||
{
|
||||
case JukeboxVisualState.On:
|
||||
SetLayerState(JukeboxVisualLayers.Base, component.OnState, sprite);
|
||||
break;
|
||||
|
||||
case JukeboxVisualState.Off:
|
||||
SetLayerState(JukeboxVisualLayers.Base, component.OffState, sprite);
|
||||
break;
|
||||
|
||||
case JukeboxVisualState.Select:
|
||||
PlayAnimation(uid, JukeboxVisualLayers.Base, component.SelectState, 1.0f, sprite);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayAnimation(EntityUid uid, JukeboxVisualLayers layer, string? state, float animationTime, SpriteComponent sprite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(state))
|
||||
return;
|
||||
|
||||
if (!_animationPlayer.HasRunningAnimation(uid, state))
|
||||
{
|
||||
var animation = GetAnimation(layer, state, animationTime);
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
_animationPlayer.Play(uid, animation, state);
|
||||
}
|
||||
}
|
||||
|
||||
private static Animation GetAnimation(JukeboxVisualLayers layer, string state, float animationTime)
|
||||
{
|
||||
return new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(animationTime),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = layer,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(state, 0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void SetLayerState(JukeboxVisualLayers layer, string? state, SpriteComponent sprite)
|
||||
{
|
||||
if (string.IsNullOrEmpty(state))
|
||||
return;
|
||||
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetAutoAnimated(layer, true);
|
||||
sprite.LayerSetState(layer, state);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.CardboardBox;
|
||||
using Content.Shared.CardboardBox.Components;
|
||||
using Content.Shared.Examine;
|
||||
@@ -13,9 +14,14 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examine = default!;
|
||||
|
||||
private EntityQuery<BodyComponent> _bodyQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_bodyQuery = GetEntityQuery<BodyComponent>();
|
||||
|
||||
SubscribeNetworkEvent<PlayBoxEffectMessage>(OnBoxEffect);
|
||||
}
|
||||
|
||||
@@ -59,6 +65,10 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
|
||||
if (!_examine.InRangeUnOccluded(sourcePos, mapPos, box.Distance, null))
|
||||
continue;
|
||||
|
||||
// no effect for anything too exotic
|
||||
if (!_bodyQuery.HasComp(mob))
|
||||
continue;
|
||||
|
||||
var ent = Spawn(box.Effect, mapPos);
|
||||
|
||||
if (!xformQuery.TryGetComponent(ent, out var entTransform) || !TryComp<SpriteComponent>(ent, out var sprite))
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Content.Client.Chemistry.EntitySystems;
|
||||
using Content.Client.Chemistry.UI;
|
||||
|
||||
namespace Content.Client.Chemistry.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes a solution container's contents via a basic item status control.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shows the solution volume, max volume, and transfer amount.
|
||||
/// </remarks>
|
||||
/// <seealso cref="SolutionItemStatusSystem"/>
|
||||
/// <seealso cref="SolutionStatusControl"/>
|
||||
[RegisterComponent]
|
||||
public sealed partial class SolutionItemStatusComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the solution that will be shown on the item status control.
|
||||
/// </summary>
|
||||
[DataField, ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Solution = "default";
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Content.Client.Chemistry.Components;
|
||||
using Content.Client.Chemistry.UI;
|
||||
using Content.Client.Items;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
|
||||
namespace Content.Client.Chemistry.EntitySystems;
|
||||
|
||||
/// <summary>
|
||||
/// Wires up item status logic for <see cref="SolutionItemStatusComponent"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="SolutionStatusControl"/>
|
||||
public sealed class SolutionItemStatusSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
Subs.ItemStatus<SolutionItemStatusComponent>(
|
||||
entity => new SolutionStatusControl(entity, EntityManager, _solutionContainerSystem));
|
||||
}
|
||||
}
|
||||
59
Content.Client/Chemistry/UI/SolutionStatusControl.cs
Normal file
59
Content.Client/Chemistry/UI/SolutionStatusControl.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Content.Client.Chemistry.Components;
|
||||
using Content.Client.Chemistry.EntitySystems;
|
||||
using Content.Client.Items.UI;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
|
||||
namespace Content.Client.Chemistry.UI;
|
||||
|
||||
/// <summary>
|
||||
/// Displays basic solution information for <see cref="SolutionItemStatusComponent"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="SolutionItemStatusSystem"/>
|
||||
public sealed class SolutionStatusControl : PollingItemStatusControl<SolutionStatusControl.Data>
|
||||
{
|
||||
private readonly Entity<SolutionItemStatusComponent> _parent;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly SharedSolutionContainerSystem _solutionContainers;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
public SolutionStatusControl(
|
||||
Entity<SolutionItemStatusComponent> parent,
|
||||
IEntityManager entityManager,
|
||||
SharedSolutionContainerSystem solutionContainers)
|
||||
{
|
||||
_parent = parent;
|
||||
_entityManager = entityManager;
|
||||
_solutionContainers = solutionContainers;
|
||||
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||
AddChild(_label);
|
||||
}
|
||||
|
||||
protected override Data PollData()
|
||||
{
|
||||
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.Solution, out _, out var solution))
|
||||
return default;
|
||||
|
||||
FixedPoint2? transferAmount = null;
|
||||
if (_entityManager.TryGetComponent(_parent.Owner, out SolutionTransferComponent? transfer))
|
||||
transferAmount = transfer.TransferAmount;
|
||||
|
||||
return new Data(solution.Volume, solution.MaxVolume, transferAmount);
|
||||
}
|
||||
|
||||
protected override void Update(in Data data)
|
||||
{
|
||||
var markup = Loc.GetString("solution-status-volume",
|
||||
("currentVolume", data.Volume),
|
||||
("maxVolume", data.MaxVolume));
|
||||
if (data.TransferVolume is { } transferVolume)
|
||||
markup += "\n" + Loc.GetString("solution-status-transfer", ("volume", transferVolume));
|
||||
_label.SetMarkup(markup);
|
||||
}
|
||||
|
||||
public readonly record struct Data(FixedPoint2 Volume, FixedPoint2 MaxVolume, FixedPoint2? TransferVolume);
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using System.Threading;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
@@ -13,6 +15,8 @@ namespace Content.Client.Communications.UI
|
||||
private CommunicationsConsoleBoundUserInterface Owner { get; set; }
|
||||
private readonly CancellationTokenSource _timerCancelTokenSource = new();
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -23,6 +27,22 @@ namespace Content.Client.Communications.UI
|
||||
var loc = IoCManager.Resolve<ILocalizationManager>();
|
||||
MessageInput.Placeholder = new Rope.Leaf(loc.GetString("comms-console-menu-announcement-placeholder"));
|
||||
|
||||
var maxAnnounceLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
|
||||
MessageInput.OnTextChanged += (args) =>
|
||||
{
|
||||
if (args.Control.TextLength > maxAnnounceLength)
|
||||
{
|
||||
AnnounceButton.Disabled = true;
|
||||
AnnounceButton.ToolTip = Loc.GetString("comms-console-message-too-long");
|
||||
}
|
||||
else
|
||||
{
|
||||
AnnounceButton.Disabled = !owner.CanAnnounce;
|
||||
AnnounceButton.ToolTip = null;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(Rope.Collapse(MessageInput.TextRope));
|
||||
AnnounceButton.Disabled = !owner.CanAnnounce;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.RoundEnd;
|
||||
@@ -14,7 +15,9 @@ namespace Content.Client.GameTicking.Managers
|
||||
public sealed class ClientGameTicker : SharedGameTicker
|
||||
{
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClientAdminManager _admin = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
[ViewVariables] private bool _initialized;
|
||||
private Dictionary<NetEntity, Dictionary<string, uint?>> _jobsAvailable = new();
|
||||
@@ -44,8 +47,6 @@ namespace Content.Client.GameTicking.Managers
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
DebugTools.Assert(!_initialized);
|
||||
|
||||
SubscribeNetworkEvent<TickerJoinLobbyEvent>(JoinLobby);
|
||||
SubscribeNetworkEvent<TickerJoinGameEvent>(JoinGame);
|
||||
SubscribeNetworkEvent<TickerConnectionStatusEvent>(ConnectionStatus);
|
||||
@@ -53,14 +54,33 @@ namespace Content.Client.GameTicking.Managers
|
||||
SubscribeNetworkEvent<TickerLobbyInfoEvent>(LobbyInfo);
|
||||
SubscribeNetworkEvent<TickerLobbyCountdownEvent>(LobbyCountdown);
|
||||
SubscribeNetworkEvent<RoundEndMessageEvent>(RoundEnd);
|
||||
SubscribeNetworkEvent<RequestWindowAttentionEvent>(msg =>
|
||||
{
|
||||
IoCManager.Resolve<IClyde>().RequestWindowAttention();
|
||||
});
|
||||
SubscribeNetworkEvent<RequestWindowAttentionEvent>(OnAttentionRequest);
|
||||
SubscribeNetworkEvent<TickerLateJoinStatusEvent>(LateJoinStatus);
|
||||
SubscribeNetworkEvent<TickerJobsAvailableEvent>(UpdateJobsAvailable);
|
||||
|
||||
_initialized = true;
|
||||
_admin.AdminStatusUpdated += OnAdminUpdated;
|
||||
OnAdminUpdated();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_admin.AdminStatusUpdated -= OnAdminUpdated;
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
private void OnAdminUpdated()
|
||||
{
|
||||
// Hide some map/grid related logs from clients. This is to try prevent some easy metagaming by just
|
||||
// reading the console. E.g., logs like this one could leak the nuke station/grid:
|
||||
// > Grid NT-Arrivals 1101 (122/n25896) changed parent. Old parent: map 10 (121/n25895). New parent: FTL (123/n26470)
|
||||
#if !DEBUG
|
||||
_map.Log.Level = _admin.IsAdmin() ? LogLevel.Info : LogLevel.Warning;
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnAttentionRequest(RequestWindowAttentionEvent ev)
|
||||
{
|
||||
_clyde.RequestWindowAttention();
|
||||
}
|
||||
|
||||
private void LateJoinStatus(TickerLateJoinStatusEvent message)
|
||||
@@ -137,7 +157,7 @@ namespace Content.Client.GameTicking.Managers
|
||||
return;
|
||||
|
||||
//This is not ideal at all, but I don't see an immediately better fit anywhere else.
|
||||
_window = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, _entityManager);
|
||||
_window = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, EntityManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class GuideEntry
|
||||
}
|
||||
|
||||
[Prototype("guideEntry")]
|
||||
public sealed class GuideEntryPrototype : GuideEntry, IPrototype
|
||||
public sealed partial class GuideEntryPrototype : GuideEntry, IPrototype
|
||||
{
|
||||
public string ID => Id;
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace Content.Client.Inventory
|
||||
|
||||
public void UIInventoryStorageActivate(string slot)
|
||||
{
|
||||
EntityManager.EntityNetManager?.SendSystemNetworkMessage(new OpenSlotStorageNetworkMessage(slot));
|
||||
EntityManager.RaisePredictiveEvent(new OpenSlotStorageNetworkMessage(slot));
|
||||
}
|
||||
|
||||
public void UIInventoryExamine(string slot, EntityUid uid)
|
||||
@@ -251,6 +251,7 @@ namespace Content.Client.Inventory
|
||||
public string SlotGroup => SlotDef.SlotGroup;
|
||||
public string SlotDisplayName => SlotDef.DisplayName;
|
||||
public string TextureName => "Slots/" + SlotDef.TextureName;
|
||||
public string FullTextureName => SlotDef.FullTextureName;
|
||||
|
||||
public SlotData(SlotDefinition slotDef, ContainerSlot? container = null, bool highlighted = false,
|
||||
bool blocked = false)
|
||||
|
||||
@@ -219,7 +219,7 @@ namespace Content.Client.Inventory
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
button.SpriteView.SetEntity(null);
|
||||
button.SetEntity(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ namespace Content.Client.Inventory
|
||||
else
|
||||
return;
|
||||
|
||||
button.SpriteView.SetEntity(viewEnt);
|
||||
button.SetEntity(viewEnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
Content.Client/Items/UI/PollingItemStatusControl.cs
Normal file
28
Content.Client/Items/UI/PollingItemStatusControl.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Items.UI;
|
||||
|
||||
/// <summary>
|
||||
/// A base for item status controls that poll data every frame. Avoids UI updates if data didn't change.
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">The full status control data that is polled every frame.</typeparam>
|
||||
public abstract class PollingItemStatusControl<TData> : Control where TData : struct, IEquatable<TData>
|
||||
{
|
||||
private TData _lastData;
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var newData = PollData();
|
||||
if (newData.Equals(_lastData))
|
||||
return;
|
||||
|
||||
_lastData = newData;
|
||||
Update(newData);
|
||||
}
|
||||
|
||||
protected abstract TData PollData();
|
||||
protected abstract void Update(in TData data);
|
||||
}
|
||||
7
Content.Client/Nutrition/EntitySystems/DrinkSystem.cs
Normal file
7
Content.Client/Nutrition/EntitySystems/DrinkSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
|
||||
namespace Content.Client.Nutrition.EntitySystems;
|
||||
|
||||
public sealed class DrinkSystem : SharedDrinkSystem
|
||||
{
|
||||
}
|
||||
@@ -36,6 +36,9 @@
|
||||
<CheckBox Name="IntegerScalingCheckBox"
|
||||
Text="{Loc 'ui-options-vp-integer-scaling'}"
|
||||
ToolTip="{Loc 'ui-options-vp-integer-scaling-tooltip'}" />
|
||||
<CheckBox Name="ViewportVerticalFitCheckBox"
|
||||
Text="{Loc 'ui-options-vp-vertical-fit'}"
|
||||
ToolTip="{Loc 'ui-options-vp-vertical-fit-tooltip'}" />
|
||||
<CheckBox Name="ViewportLowResCheckBox" Text="{Loc 'ui-options-vp-low-res'}" />
|
||||
<CheckBox Name="ParallaxLowQualityCheckBox" Text="{Loc 'ui-options-parallax-low-quality'}" />
|
||||
<CheckBox Name="FpsCounterCheckBox" Text="{Loc 'ui-options-fps-counter'}" />
|
||||
|
||||
@@ -67,6 +67,12 @@ namespace Content.Client.Options.UI.Tabs
|
||||
UpdateApplyButton();
|
||||
};
|
||||
|
||||
ViewportVerticalFitCheckBox.OnToggled += _ =>
|
||||
{
|
||||
UpdateViewportScale();
|
||||
UpdateApplyButton();
|
||||
};
|
||||
|
||||
IntegerScalingCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ViewportLowResCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
ParallaxLowQualityCheckBox.OnToggled += OnCheckBoxToggled;
|
||||
@@ -79,6 +85,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
ViewportScaleSlider.Value = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
|
||||
ViewportStretchCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportStretch);
|
||||
IntegerScalingCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0;
|
||||
ViewportVerticalFitCheckBox.Pressed = _cfg.GetCVar(CCVars.ViewportVerticalFit);
|
||||
ViewportLowResCheckBox.Pressed = !_cfg.GetCVar(CCVars.ViewportScaleRender);
|
||||
ParallaxLowQualityCheckBox.Pressed = _cfg.GetCVar(CCVars.ParallaxLowQuality);
|
||||
FpsCounterCheckBox.Pressed = _cfg.GetCVar(CCVars.HudFpsCounterVisible);
|
||||
@@ -111,6 +118,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
_cfg.SetCVar(CCVars.ViewportFixedScaleFactor, (int) ViewportScaleSlider.Value);
|
||||
_cfg.SetCVar(CCVars.ViewportSnapToleranceMargin,
|
||||
IntegerScalingCheckBox.Pressed ? CCVars.ViewportSnapToleranceMargin.DefaultValue : 0);
|
||||
_cfg.SetCVar(CCVars.ViewportVerticalFit, ViewportVerticalFitCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ViewportScaleRender, !ViewportLowResCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox.Pressed);
|
||||
_cfg.SetCVar(CCVars.HudFpsCounterVisible, FpsCounterCheckBox.Pressed);
|
||||
@@ -140,6 +148,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
var isVPStretchSame = ViewportStretchCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportStretch);
|
||||
var isVPScaleSame = (int) ViewportScaleSlider.Value == _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
|
||||
var isIntegerScalingSame = IntegerScalingCheckBox.Pressed == (_cfg.GetCVar(CCVars.ViewportSnapToleranceMargin) != 0);
|
||||
var isVPVerticalFitSame = ViewportVerticalFitCheckBox.Pressed == _cfg.GetCVar(CCVars.ViewportVerticalFit);
|
||||
var isVPResSame = ViewportLowResCheckBox.Pressed == !_cfg.GetCVar(CCVars.ViewportScaleRender);
|
||||
var isPLQSame = ParallaxLowQualityCheckBox.Pressed == _cfg.GetCVar(CCVars.ParallaxLowQuality);
|
||||
var isFpsCounterVisibleSame = FpsCounterCheckBox.Pressed == _cfg.GetCVar(CCVars.HudFpsCounterVisible);
|
||||
@@ -152,6 +161,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
isVPStretchSame &&
|
||||
isVPScaleSame &&
|
||||
isIntegerScalingSame &&
|
||||
isVPVerticalFitSame &&
|
||||
isVPResSame &&
|
||||
isPLQSame &&
|
||||
isFpsCounterVisibleSame &&
|
||||
@@ -235,6 +245,8 @@ namespace Content.Client.Options.UI.Tabs
|
||||
{
|
||||
ViewportScaleBox.Visible = !ViewportStretchCheckBox.Pressed;
|
||||
IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed;
|
||||
ViewportVerticalFitCheckBox.Visible = ViewportStretchCheckBox.Pressed;
|
||||
ViewportWidthSlider.Visible = ViewportWidthSliderDisplay.Visible = !ViewportStretchCheckBox.Pressed || ViewportStretchCheckBox.Pressed && !ViewportVerticalFitCheckBox.Pressed;
|
||||
ViewportScaleText.Text = Loc.GetString("ui-options-vp-scale", ("scale", ViewportScaleSlider.Value));
|
||||
}
|
||||
|
||||
|
||||
28
Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs
Normal file
28
Content.Client/Overlays/ShowCriminalRecordIconsSystem.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.Security.Components;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed class ShowCriminalRecordIconsSystem : EquipmentHudSystem<ShowCriminalRecordIconsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CriminalRecordComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, CriminalRecordComponent component, ref GetStatusIconsEvent ev)
|
||||
{
|
||||
if (!IsActive || ev.InContainer)
|
||||
return;
|
||||
|
||||
if (_prototype.TryIndex<StatusIconPrototype>(component.StatusIcon.Id, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed class ShowHungerIconsSystem : EquipmentHudSystem<ShowHungerIconsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeMan = default!;
|
||||
[Dependency] private readonly HungerSystem _hunger = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -17,42 +16,12 @@ public sealed class ShowHungerIconsSystem : EquipmentHudSystem<ShowHungerIconsCo
|
||||
SubscribeLocalEvent<HungerComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent hungerComponent, ref GetStatusIconsEvent args)
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, HungerComponent component, ref GetStatusIconsEvent ev)
|
||||
{
|
||||
if (!IsActive || args.InContainer)
|
||||
if (!IsActive || ev.InContainer)
|
||||
return;
|
||||
|
||||
var hungerIcons = DecideHungerIcon(uid, hungerComponent);
|
||||
|
||||
args.StatusIcons.AddRange(hungerIcons);
|
||||
}
|
||||
|
||||
private IReadOnlyList<StatusIconPrototype> DecideHungerIcon(EntityUid uid, HungerComponent hungerComponent)
|
||||
{
|
||||
var result = new List<StatusIconPrototype>();
|
||||
|
||||
switch (hungerComponent.CurrentThreshold)
|
||||
{
|
||||
case HungerThreshold.Overfed:
|
||||
if (_prototypeMan.TryIndex<StatusIconPrototype>("HungerIconOverfed", out var overfed))
|
||||
{
|
||||
result.Add(overfed);
|
||||
}
|
||||
break;
|
||||
case HungerThreshold.Peckish:
|
||||
if (_prototypeMan.TryIndex<StatusIconPrototype>("HungerIconPeckish", out var peckish))
|
||||
{
|
||||
result.Add(peckish);
|
||||
}
|
||||
break;
|
||||
case HungerThreshold.Starving:
|
||||
if (_prototypeMan.TryIndex<StatusIconPrototype>("HungerIconStarving", out var starving))
|
||||
{
|
||||
result.Add(starving);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
if (_hunger.TryGetStatusIconPrototype(component, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
}
|
||||
|
||||
60
Content.Client/Overlays/ShowJobIconsSystem.cs
Normal file
60
Content.Client/Overlays/ShowJobIconsSystem.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed class ShowJobIconsSystem : EquipmentHudSystem<ShowJobIconsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
|
||||
[ValidatePrototypeId<StatusIconPrototype>]
|
||||
private const string JobIconForNoId = "JobIconNoId";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StatusIconComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, StatusIconComponent _, ref GetStatusIconsEvent ev)
|
||||
{
|
||||
if (!IsActive || ev.InContainer)
|
||||
return;
|
||||
|
||||
var iconId = JobIconForNoId;
|
||||
|
||||
if (_accessReader.FindAccessItemsInventory(uid, out var items))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
// ID Card
|
||||
if (TryComp<IdCardComponent>(item, out var id))
|
||||
{
|
||||
iconId = id.JobIcon;
|
||||
break;
|
||||
}
|
||||
|
||||
// PDA
|
||||
if (TryComp<PdaComponent>(item, out var pda)
|
||||
&& pda.ContainedId != null
|
||||
&& TryComp(pda.ContainedId, out id))
|
||||
{
|
||||
iconId = id.JobIcon;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_prototype.TryIndex<StatusIconPrototype>(iconId, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
else
|
||||
Log.Error($"Invalid job icon prototype: {iconPrototype}");
|
||||
}
|
||||
}
|
||||
28
Content.Client/Overlays/ShowMindShieldIconsSystem.cs
Normal file
28
Content.Client/Overlays/ShowMindShieldIconsSystem.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed class ShowMindShieldIconsSystem : EquipmentHudSystem<ShowMindShieldIconsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MindShieldComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, MindShieldComponent component, ref GetStatusIconsEvent ev)
|
||||
{
|
||||
if (!IsActive || ev.InContainer)
|
||||
return;
|
||||
|
||||
if (_prototype.TryIndex<StatusIconPrototype>(component.MindShieldStatusIcon.Id, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Security.Components;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed class ShowSecurityIconsSystem : EquipmentHudSystem<ShowSecurityIconsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeMan = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
|
||||
[ValidatePrototypeId<StatusIconPrototype>]
|
||||
private const string JobIconForNoId = "JobIconNoId";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StatusIconComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, StatusIconComponent _, ref GetStatusIconsEvent @event)
|
||||
{
|
||||
if (!IsActive || @event.InContainer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var securityIcons = DecideSecurityIcon(uid);
|
||||
|
||||
@event.StatusIcons.AddRange(securityIcons);
|
||||
}
|
||||
|
||||
private IReadOnlyList<StatusIconPrototype> DecideSecurityIcon(EntityUid uid)
|
||||
{
|
||||
var result = new List<StatusIconPrototype>();
|
||||
|
||||
var jobIconToGet = JobIconForNoId;
|
||||
if (_accessReader.FindAccessItemsInventory(uid, out var items))
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
// ID Card
|
||||
if (TryComp(item, out IdCardComponent? id))
|
||||
{
|
||||
jobIconToGet = id.JobIcon;
|
||||
break;
|
||||
}
|
||||
|
||||
// PDA
|
||||
if (TryComp(item, out PdaComponent? pda)
|
||||
&& pda.ContainedId != null
|
||||
&& TryComp(pda.ContainedId, out id))
|
||||
{
|
||||
jobIconToGet = id.JobIcon;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_prototypeMan.TryIndex<StatusIconPrototype>(jobIconToGet, out var jobIcon))
|
||||
result.Add(jobIcon);
|
||||
else
|
||||
Log.Error($"Invalid job icon prototype: {jobIcon}");
|
||||
|
||||
if (TryComp<MindShieldComponent>(uid, out var comp))
|
||||
{
|
||||
if (_prototypeMan.TryIndex<StatusIconPrototype>(comp.MindShieldStatusIcon.Id, out var icon))
|
||||
result.Add(icon);
|
||||
}
|
||||
|
||||
if (TryComp<CriminalRecordComponent>(uid, out var record))
|
||||
{
|
||||
if(_prototypeMan.TryIndex<StatusIconPrototype>(record.StatusIcon.Id, out var criminalIcon))
|
||||
result.Add(criminalIcon);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Content.Shared.NukeOps;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem<ShowSyndicateIconsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
@@ -16,28 +17,13 @@ public sealed class ShowSyndicateIconsSystem : EquipmentHudSystem<ShowSyndicateI
|
||||
SubscribeLocalEvent<NukeOperativeComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, NukeOperativeComponent nukeOperativeComponent, ref GetStatusIconsEvent args)
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, NukeOperativeComponent component, ref GetStatusIconsEvent ev)
|
||||
{
|
||||
if (!IsActive || args.InContainer)
|
||||
{
|
||||
if (!IsActive || ev.InContainer)
|
||||
return;
|
||||
}
|
||||
|
||||
var syndicateIcons = SyndicateIcon(uid, nukeOperativeComponent);
|
||||
|
||||
args.StatusIcons.AddRange(syndicateIcons);
|
||||
}
|
||||
|
||||
private IReadOnlyList<StatusIconPrototype> SyndicateIcon(EntityUid uid, NukeOperativeComponent nukeOperativeComponent)
|
||||
{
|
||||
var result = new List<StatusIconPrototype>();
|
||||
|
||||
if (_prototype.TryIndex<StatusIconPrototype>(nukeOperativeComponent.SyndStatusIcon, out var syndicateicon))
|
||||
{
|
||||
result.Add(syndicateicon);
|
||||
}
|
||||
|
||||
return result;
|
||||
if (_prototype.TryIndex<StatusIconPrototype>(component.SyndStatusIcon, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Content.Shared.StatusIcon.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Overlays;
|
||||
|
||||
public sealed class ShowThirstIconsSystem : EquipmentHudSystem<ShowThirstIconsComponent>
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeMan = default!;
|
||||
[Dependency] private readonly ThirstSystem _thirst = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -17,42 +16,12 @@ public sealed class ShowThirstIconsSystem : EquipmentHudSystem<ShowThirstIconsCo
|
||||
SubscribeLocalEvent<ThirstComponent, GetStatusIconsEvent>(OnGetStatusIconsEvent);
|
||||
}
|
||||
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent thirstComponent, ref GetStatusIconsEvent args)
|
||||
private void OnGetStatusIconsEvent(EntityUid uid, ThirstComponent component, ref GetStatusIconsEvent ev)
|
||||
{
|
||||
if (!IsActive || args.InContainer)
|
||||
if (!IsActive || ev.InContainer)
|
||||
return;
|
||||
|
||||
var thirstIcons = DecideThirstIcon(uid, thirstComponent);
|
||||
|
||||
args.StatusIcons.AddRange(thirstIcons);
|
||||
}
|
||||
|
||||
private IReadOnlyList<StatusIconPrototype> DecideThirstIcon(EntityUid uid, ThirstComponent thirstComponent)
|
||||
{
|
||||
var result = new List<StatusIconPrototype>();
|
||||
|
||||
switch (thirstComponent.CurrentThirstThreshold)
|
||||
{
|
||||
case ThirstThreshold.OverHydrated:
|
||||
if (_prototypeMan.TryIndex<StatusIconPrototype>("ThirstIconOverhydrated", out var overhydrated))
|
||||
{
|
||||
result.Add(overhydrated);
|
||||
}
|
||||
break;
|
||||
case ThirstThreshold.Thirsty:
|
||||
if (_prototypeMan.TryIndex<StatusIconPrototype>("ThirstIconThirsty", out var thirsty))
|
||||
{
|
||||
result.Add(thirsty);
|
||||
}
|
||||
break;
|
||||
case ThirstThreshold.Parched:
|
||||
if (_prototypeMan.TryIndex<StatusIconPrototype>("ThirstIconParched", out var parched))
|
||||
{
|
||||
result.Add(parched);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
if (_thirst.TryGetStatusIconPrototype(component, out var iconPrototype))
|
||||
ev.StatusIcons.Add(iconPrototype!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Pinpointer;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Client.Pinpointer;
|
||||
|
||||
public sealed class NavMapSystem : SharedNavMapSystem
|
||||
public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NavMapComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
@@ -21,89 +17,47 @@ public sealed class NavMapSystem : SharedNavMapSystem
|
||||
if (args.Current is not NavMapComponentState state)
|
||||
return;
|
||||
|
||||
component.Chunks.Clear();
|
||||
|
||||
foreach (var (origin, data) in state.TileData)
|
||||
if (!state.FullState)
|
||||
{
|
||||
component.Chunks.Add(origin, new NavMapChunk(origin)
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
TileData = data,
|
||||
});
|
||||
}
|
||||
if (!state.AllChunks!.Contains(index))
|
||||
component.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
component.Beacons.Clear();
|
||||
component.Beacons.AddRange(state.Beacons);
|
||||
|
||||
component.Airlocks.Clear();
|
||||
component.Airlocks.AddRange(state.Airlocks);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NavMapOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public NavMapOverlay(IEntityManager entManager, IMapManager mapManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_mapManager = mapManager;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var query = _entManager.GetEntityQuery<NavMapComponent>();
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var scale = Matrix3.CreateScale(new Vector2(1f, 1f));
|
||||
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids);
|
||||
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
if (!query.TryGetComponent(grid, out var navMap) || !xformQuery.TryGetComponent(grid.Owner, out var xform))
|
||||
continue;
|
||||
|
||||
// TODO: Faster helper method
|
||||
var (_, _, matrix, invMatrix) = xform.GetWorldPositionRotationMatrixWithInv();
|
||||
|
||||
var localAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
Matrix3.Multiply(in scale, in matrix, out var matty);
|
||||
|
||||
args.WorldHandle.SetTransform(matty);
|
||||
|
||||
for (var x = Math.Floor(localAABB.Left); x <= Math.Ceiling(localAABB.Right); x += SharedNavMapSystem.ChunkSize * grid.Comp.TileSize)
|
||||
foreach (var beacon in component.Beacons)
|
||||
{
|
||||
for (var y = Math.Floor(localAABB.Bottom); y <= Math.Ceiling(localAABB.Top); y += SharedNavMapSystem.ChunkSize * grid.Comp.TileSize)
|
||||
{
|
||||
var floored = new Vector2i((int) x, (int) y);
|
||||
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(floored, SharedNavMapSystem.ChunkSize);
|
||||
|
||||
if (!navMap.Chunks.TryGetValue(chunkOrigin, out var chunk))
|
||||
continue;
|
||||
|
||||
// TODO: Okay maybe I should just use ushorts lmao...
|
||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
||||
{
|
||||
var value = (int) Math.Pow(2, i);
|
||||
|
||||
var mask = chunk.TileData & value;
|
||||
|
||||
if (mask == 0x0)
|
||||
continue;
|
||||
|
||||
var tile = chunk.Origin * SharedNavMapSystem.ChunkSize + SharedNavMapSystem.GetTile(mask);
|
||||
args.WorldHandle.DrawRect(new Box2(tile * grid.Comp.TileSize, (tile + 1) * grid.Comp.TileSize), Color.Aqua, false);
|
||||
}
|
||||
}
|
||||
if (!state.AllBeacons!.Contains(beacon))
|
||||
component.Beacons.Remove(beacon);
|
||||
}
|
||||
}
|
||||
|
||||
args.WorldHandle.SetTransform(Matrix3.Identity);
|
||||
else
|
||||
{
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!state.Chunks.ContainsKey(index))
|
||||
component.Chunks.Remove(index);
|
||||
}
|
||||
|
||||
foreach (var beacon in component.Beacons)
|
||||
{
|
||||
if (!state.Beacons.Contains(beacon))
|
||||
component.Beacons.Remove(beacon);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var ((category, origin), chunk) in state.Chunks)
|
||||
{
|
||||
var newChunk = new NavMapChunk(origin);
|
||||
|
||||
foreach (var (atmosDirection, value) in chunk)
|
||||
newChunk.TileData[atmosDirection] = value;
|
||||
|
||||
component.Chunks[(category, origin)] = newChunk;
|
||||
}
|
||||
|
||||
foreach (var beacon in state.Beacons)
|
||||
component.Beacons.Add(beacon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.Atmos;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
@@ -27,6 +29,7 @@ public partial class NavMapControl : MapGridControl
|
||||
{
|
||||
[Dependency] private IResourceCache _cache = default!;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
private readonly SharedNavMapSystem _navMapSystem;
|
||||
|
||||
public EntityUid? Owner;
|
||||
public EntityUid? MapUid;
|
||||
@@ -40,7 +43,10 @@ public partial class NavMapControl : MapGridControl
|
||||
// Tracked data
|
||||
public Dictionary<EntityCoordinates, (bool Visible, Color Color)> TrackedCoordinates = new();
|
||||
public Dictionary<NetEntity, NavMapBlip> TrackedEntities = new();
|
||||
public Dictionary<Vector2i, List<NavMapLine>>? TileGrid = default!;
|
||||
|
||||
public List<(Vector2, Vector2)> TileLines = new();
|
||||
public List<(Vector2, Vector2)> TileRects = new();
|
||||
public List<(Vector2[], Color)> TilePolygons = new();
|
||||
|
||||
// Default colors
|
||||
public Color WallColor = new(102, 217, 102);
|
||||
@@ -53,14 +59,23 @@ public partial class NavMapControl : MapGridControl
|
||||
protected static float MinDisplayedRange = 8f;
|
||||
protected static float MaxDisplayedRange = 128f;
|
||||
protected static float DefaultDisplayedRange = 48f;
|
||||
protected float MinmapScaleModifier = 0.075f;
|
||||
protected float FullWallInstep = 0.165f;
|
||||
protected float ThinWallThickness = 0.165f;
|
||||
protected float ThinDoorThickness = 0.30f;
|
||||
|
||||
// Local variables
|
||||
private float _updateTimer = 0.25f;
|
||||
private float _updateTimer = 1.0f;
|
||||
private Dictionary<Color, Color> _sRGBLookUp = new();
|
||||
protected Color BackgroundColor;
|
||||
protected float BackgroundOpacity = 0.9f;
|
||||
private int _targetFontsize = 8;
|
||||
|
||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookup = new();
|
||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookupReversed = new();
|
||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookup = new();
|
||||
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookupReversed = new();
|
||||
|
||||
// Components
|
||||
private NavMapComponent? _navMap;
|
||||
private MapGridComponent? _grid;
|
||||
@@ -72,6 +87,7 @@ public partial class NavMapControl : MapGridControl
|
||||
private readonly Label _zoom = new()
|
||||
{
|
||||
VerticalAlignment = VAlignment.Top,
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(8f, 8f),
|
||||
};
|
||||
|
||||
@@ -80,6 +96,7 @@ public partial class NavMapControl : MapGridControl
|
||||
Text = Loc.GetString("navmap-recenter"),
|
||||
VerticalAlignment = VAlignment.Top,
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(8f, 4f),
|
||||
Disabled = true,
|
||||
};
|
||||
@@ -87,9 +104,10 @@ public partial class NavMapControl : MapGridControl
|
||||
private readonly CheckBox _beacons = new()
|
||||
{
|
||||
Text = Loc.GetString("navmap-toggle-beacons"),
|
||||
Margin = new Thickness(4f, 0f),
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(4f, 0f),
|
||||
Pressed = true,
|
||||
};
|
||||
|
||||
@@ -98,6 +116,8 @@ public partial class NavMapControl : MapGridControl
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_transformSystem = EntManager.System<SharedTransformSystem>();
|
||||
_navMapSystem = EntManager.System<SharedNavMapSystem>();
|
||||
|
||||
BackgroundColor = Color.FromSrgb(TileColor.WithAlpha(BackgroundOpacity));
|
||||
|
||||
RectClipContent = true;
|
||||
@@ -112,6 +132,8 @@ public partial class NavMapControl : MapGridControl
|
||||
BorderColor = StyleNano.PanelDark
|
||||
},
|
||||
VerticalExpand = false,
|
||||
HorizontalExpand = true,
|
||||
SetWidth = 650f,
|
||||
Children =
|
||||
{
|
||||
new BoxContainer()
|
||||
@@ -130,6 +152,7 @@ public partial class NavMapControl : MapGridControl
|
||||
var topContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
topPanel,
|
||||
@@ -157,6 +180,9 @@ public partial class NavMapControl : MapGridControl
|
||||
{
|
||||
EntManager.TryGetComponent(MapUid, out _navMap);
|
||||
EntManager.TryGetComponent(MapUid, out _grid);
|
||||
EntManager.TryGetComponent(MapUid, out _xform);
|
||||
EntManager.TryGetComponent(MapUid, out _physics);
|
||||
EntManager.TryGetComponent(MapUid, out _fixtures);
|
||||
|
||||
UpdateNavMap();
|
||||
}
|
||||
@@ -251,119 +277,93 @@ public partial class NavMapControl : MapGridControl
|
||||
EntManager.TryGetComponent(MapUid, out _physics);
|
||||
EntManager.TryGetComponent(MapUid, out _fixtures);
|
||||
|
||||
if (_navMap == null || _grid == null || _xform == null)
|
||||
return;
|
||||
|
||||
// Map re-centering
|
||||
_recenter.Disabled = DrawRecenter();
|
||||
|
||||
_zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(DefaultDisplayedRange / WorldRange ):0.0}"));
|
||||
|
||||
if (_navMap == null || _xform == null)
|
||||
return;
|
||||
// Update zoom text
|
||||
_zoom.Text = Loc.GetString("navmap-zoom", ("value", $"{(DefaultDisplayedRange / WorldRange):0.0}"));
|
||||
|
||||
// Update offset with physics local center
|
||||
var offset = Offset;
|
||||
|
||||
if (_physics != null)
|
||||
offset += _physics.LocalCenter;
|
||||
|
||||
// Draw tiles
|
||||
if (_fixtures != null)
|
||||
var offsetVec = new Vector2(offset.X, -offset.Y);
|
||||
|
||||
// Wall sRGB
|
||||
if (!_sRGBLookUp.TryGetValue(WallColor, out var wallsRGB))
|
||||
{
|
||||
wallsRGB = Color.ToSrgb(WallColor);
|
||||
_sRGBLookUp[WallColor] = wallsRGB;
|
||||
}
|
||||
|
||||
// Draw floor tiles
|
||||
if (TilePolygons.Any())
|
||||
{
|
||||
Span<Vector2> verts = new Vector2[8];
|
||||
|
||||
foreach (var fixture in _fixtures.Fixtures.Values)
|
||||
foreach (var (polygonVerts, polygonColor) in TilePolygons)
|
||||
{
|
||||
if (fixture.Shape is not PolygonShape poly)
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < poly.VertexCount; i++)
|
||||
for (var i = 0; i < polygonVerts.Length; i++)
|
||||
{
|
||||
var vert = poly.Vertices[i] - offset;
|
||||
|
||||
var vert = polygonVerts[i] - offset;
|
||||
verts[i] = ScalePosition(new Vector2(vert.X, -vert.Y));
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..poly.VertexCount], TileColor);
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts[..polygonVerts.Length], polygonColor);
|
||||
}
|
||||
}
|
||||
|
||||
var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
|
||||
|
||||
// Drawing lines can be rather expensive due to the number of neighbors that need to be checked in order
|
||||
// to figure out where they should be drawn. However, we don't *need* to do check these every frame.
|
||||
// Instead, lets periodically update where to draw each line and then store these points in a list.
|
||||
// Then we can just run through the list each frame and draw the lines without any extra computation.
|
||||
|
||||
// Draw walls
|
||||
if (TileGrid != null && TileGrid.Count > 0)
|
||||
// Draw map lines
|
||||
if (TileLines.Any())
|
||||
{
|
||||
var walls = new ValueList<Vector2>();
|
||||
var lines = new ValueList<Vector2>(TileLines.Count * 2);
|
||||
|
||||
foreach ((var chunk, var chunkedLines) in TileGrid)
|
||||
foreach (var (o, t) in TileLines)
|
||||
{
|
||||
var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
|
||||
var origin = ScalePosition(o - offsetVec);
|
||||
var terminus = ScalePosition(t - offsetVec);
|
||||
|
||||
if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
||||
continue;
|
||||
|
||||
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
||||
continue;
|
||||
|
||||
foreach (var chunkedLine in chunkedLines)
|
||||
{
|
||||
var start = ScalePosition(chunkedLine.Origin - new Vector2(offset.X, -offset.Y));
|
||||
var end = ScalePosition(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y));
|
||||
|
||||
walls.Add(start);
|
||||
walls.Add(end);
|
||||
}
|
||||
lines.Add(origin);
|
||||
lines.Add(terminus);
|
||||
}
|
||||
|
||||
if (walls.Count > 0)
|
||||
{
|
||||
if (!_sRGBLookUp.TryGetValue(WallColor, out var sRGB))
|
||||
{
|
||||
sRGB = Color.ToSrgb(WallColor);
|
||||
_sRGBLookUp[WallColor] = sRGB;
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, walls.Span, sRGB);
|
||||
}
|
||||
if (lines.Count > 0)
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, lines.Span, wallsRGB);
|
||||
}
|
||||
|
||||
var airlockBuffer = Vector2.One * (MinimapScale / 2.25f) * 0.75f;
|
||||
var airlockLines = new ValueList<Vector2>();
|
||||
var foobarVec = new Vector2(1, -1);
|
||||
|
||||
foreach (var airlock in _navMap.Airlocks)
|
||||
// Draw map rects
|
||||
if (TileRects.Any())
|
||||
{
|
||||
var position = airlock.Position - offset;
|
||||
position = ScalePosition(position with { Y = -position.Y });
|
||||
airlockLines.Add(position + airlockBuffer);
|
||||
airlockLines.Add(position - airlockBuffer * foobarVec);
|
||||
var rects = new ValueList<Vector2>(TileRects.Count * 8);
|
||||
|
||||
airlockLines.Add(position + airlockBuffer);
|
||||
airlockLines.Add(position + airlockBuffer * foobarVec);
|
||||
|
||||
airlockLines.Add(position - airlockBuffer);
|
||||
airlockLines.Add(position + airlockBuffer * foobarVec);
|
||||
|
||||
airlockLines.Add(position - airlockBuffer);
|
||||
airlockLines.Add(position - airlockBuffer * foobarVec);
|
||||
|
||||
airlockLines.Add(position + airlockBuffer * -Vector2.UnitY);
|
||||
airlockLines.Add(position - airlockBuffer * -Vector2.UnitY);
|
||||
}
|
||||
|
||||
if (airlockLines.Count > 0)
|
||||
{
|
||||
if (!_sRGBLookUp.TryGetValue(WallColor, out var sRGB))
|
||||
foreach (var (lt, rb) in TileRects)
|
||||
{
|
||||
sRGB = Color.ToSrgb(WallColor);
|
||||
_sRGBLookUp[WallColor] = sRGB;
|
||||
var leftTop = ScalePosition(lt - offsetVec);
|
||||
var rightBottom = ScalePosition(rb - offsetVec);
|
||||
|
||||
var rightTop = new Vector2(rightBottom.X, leftTop.Y);
|
||||
var leftBottom = new Vector2(leftTop.X, rightBottom.Y);
|
||||
|
||||
rects.Add(leftTop);
|
||||
rects.Add(rightTop);
|
||||
rects.Add(rightTop);
|
||||
rects.Add(rightBottom);
|
||||
rects.Add(rightBottom);
|
||||
rects.Add(leftBottom);
|
||||
rects.Add(leftBottom);
|
||||
rects.Add(leftTop);
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, airlockLines.Span, sRGB);
|
||||
if (rects.Count > 0)
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, rects.Span, wallsRGB);
|
||||
}
|
||||
|
||||
// Invoke post wall drawing action
|
||||
if (PostWallDrawingAction != null)
|
||||
PostWallDrawingAction.Invoke(handle);
|
||||
|
||||
@@ -373,7 +373,7 @@ public partial class NavMapControl : MapGridControl
|
||||
var rectBuffer = new Vector2(5f, 3f);
|
||||
|
||||
// Calculate font size for current zoom level
|
||||
var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize , 0);
|
||||
var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0);
|
||||
var font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize);
|
||||
|
||||
foreach (var beacon in _navMap.Beacons)
|
||||
@@ -409,8 +409,6 @@ public partial class NavMapControl : MapGridControl
|
||||
}
|
||||
|
||||
// Tracked entities (can use a supplied sprite as a marker instead; should probably just replace TrackedCoordinates with this eventually)
|
||||
var iconVertexUVs = new Dictionary<(Texture, Color), ValueList<DrawVertexUV2D>>();
|
||||
|
||||
foreach (var blip in TrackedEntities.Values)
|
||||
{
|
||||
if (blip.Blinks && !lit)
|
||||
@@ -419,9 +417,6 @@ public partial class NavMapControl : MapGridControl
|
||||
if (blip.Texture == null)
|
||||
continue;
|
||||
|
||||
if (!iconVertexUVs.TryGetValue((blip.Texture, blip.Color), out var vertexUVs))
|
||||
vertexUVs = new();
|
||||
|
||||
var mapPos = blip.Coordinates.ToMap(EntManager, _transformSystem);
|
||||
|
||||
if (mapPos.MapId != MapId.Nullspace)
|
||||
@@ -429,29 +424,11 @@ public partial class NavMapControl : MapGridControl
|
||||
var position = _transformSystem.GetInvWorldMatrix(_xform).Transform(mapPos.Position) - offset;
|
||||
position = ScalePosition(new Vector2(position.X, -position.Y));
|
||||
|
||||
var scalingCoefficient = 2.5f;
|
||||
var positionOffset = scalingCoefficient * float.Sqrt(MinimapScale);
|
||||
var scalingCoefficient = MinmapScaleModifier * float.Sqrt(MinimapScale);
|
||||
var positionOffset = new Vector2(scalingCoefficient * blip.Texture.Width, scalingCoefficient * blip.Texture.Height);
|
||||
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y - positionOffset), new Vector2(1f, 1f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X - positionOffset, position.Y + positionOffset), new Vector2(1f, 0f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y - positionOffset), new Vector2(0f, 1f)));
|
||||
vertexUVs.Add(new DrawVertexUV2D(new Vector2(position.X + positionOffset, position.Y + positionOffset), new Vector2(0f, 0f)));
|
||||
handle.DrawTextureRect(blip.Texture, new UIBox2(position - positionOffset, position + positionOffset), blip.Color);
|
||||
}
|
||||
|
||||
iconVertexUVs[(blip.Texture, blip.Color)] = vertexUVs;
|
||||
}
|
||||
|
||||
foreach ((var (texture, color), var vertexUVs) in iconVertexUVs)
|
||||
{
|
||||
if (!_sRGBLookUp.TryGetValue(color, out var sRGB))
|
||||
{
|
||||
sRGB = Color.ToSrgb(color);
|
||||
_sRGBLookUp[color] = sRGB;
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, texture, vertexUVs.Span, sRGB);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,124 +446,294 @@ public partial class NavMapControl : MapGridControl
|
||||
}
|
||||
|
||||
protected virtual void UpdateNavMap()
|
||||
{
|
||||
// Clear stale values
|
||||
TilePolygons.Clear();
|
||||
TileLines.Clear();
|
||||
TileRects.Clear();
|
||||
|
||||
UpdateNavMapFloorTiles();
|
||||
UpdateNavMapWallLines();
|
||||
UpdateNavMapAirlocks();
|
||||
}
|
||||
|
||||
private void UpdateNavMapFloorTiles()
|
||||
{
|
||||
if (_fixtures == null)
|
||||
return;
|
||||
|
||||
var verts = new Vector2[8];
|
||||
|
||||
foreach (var fixture in _fixtures.Fixtures.Values)
|
||||
{
|
||||
if (fixture.Shape is not PolygonShape poly)
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < poly.VertexCount; i++)
|
||||
{
|
||||
var vert = poly.Vertices[i];
|
||||
verts[i] = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y));
|
||||
}
|
||||
|
||||
TilePolygons.Add((verts[..poly.VertexCount], TileColor));
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateNavMapWallLines()
|
||||
{
|
||||
if (_navMap == null || _grid == null)
|
||||
return;
|
||||
|
||||
TileGrid = GetDecodedWallChunks(_navMap.Chunks, _grid);
|
||||
}
|
||||
// We'll use the following dictionaries to combine collinear wall lines
|
||||
HorizLinesLookup.Clear();
|
||||
HorizLinesLookupReversed.Clear();
|
||||
VertLinesLookup.Clear();
|
||||
VertLinesLookupReversed.Clear();
|
||||
|
||||
public Dictionary<Vector2i, List<NavMapLine>> GetDecodedWallChunks
|
||||
(Dictionary<Vector2i, NavMapChunk> chunks,
|
||||
MapGridComponent grid)
|
||||
{
|
||||
var decodedOutput = new Dictionary<Vector2i, List<NavMapLine>>();
|
||||
|
||||
foreach ((var chunkOrigin, var chunk) in chunks)
|
||||
foreach ((var (category, chunkOrigin), var chunk) in _navMap.Chunks)
|
||||
{
|
||||
var list = new List<NavMapLine>();
|
||||
if (category != NavMapChunkType.Wall)
|
||||
continue;
|
||||
|
||||
// TODO: Okay maybe I should just use ushorts lmao...
|
||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
||||
{
|
||||
var value = (int) Math.Pow(2, i);
|
||||
|
||||
var mask = chunk.TileData & value;
|
||||
var value = (ushort) Math.Pow(2, i);
|
||||
var mask = _navMapSystem.GetCombinedEdgesForChunk(chunk.TileData) & value;
|
||||
|
||||
if (mask == 0x0)
|
||||
continue;
|
||||
|
||||
// Alright now we'll work out our edges
|
||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize;
|
||||
var position = new Vector2(tile.X, -tile.Y);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
|
||||
|
||||
if (!_navMapSystem.AllTileEdgesAreOccupied(chunk.TileData, relativeTile))
|
||||
{
|
||||
AddRectForThinWall(chunk.TileData, tile);
|
||||
continue;
|
||||
}
|
||||
|
||||
tile = tile with { Y = -tile.Y };
|
||||
|
||||
NavMapChunk? neighborChunk;
|
||||
bool neighbor;
|
||||
|
||||
// North edge
|
||||
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
|
||||
{
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) &&
|
||||
(neighborChunk.TileData &
|
||||
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(0, 1)), out neighborChunk) &&
|
||||
(neighborChunk.TileData[AtmosDirection.South] &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
|
||||
neighbor = (chunk.TileData & flag) != 0x0;
|
||||
neighbor = (chunk.TileData[AtmosDirection.South] & flag) != 0x0;
|
||||
}
|
||||
|
||||
if (!neighbor)
|
||||
{
|
||||
// Add points
|
||||
list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, -grid.TileSize)));
|
||||
}
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile + new Vector2i(_grid.TileSize, -_grid.TileSize), HorizLinesLookup, HorizLinesLookupReversed);
|
||||
|
||||
// East edge
|
||||
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
|
||||
{
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) &&
|
||||
(neighborChunk.TileData &
|
||||
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(1, 0)), out neighborChunk) &&
|
||||
(neighborChunk.TileData[AtmosDirection.West] &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
|
||||
neighbor = (chunk.TileData & flag) != 0x0;
|
||||
neighbor = (chunk.TileData[AtmosDirection.West] & flag) != 0x0;
|
||||
}
|
||||
|
||||
if (!neighbor)
|
||||
{
|
||||
// Add points
|
||||
list.Add(new NavMapLine(position + new Vector2(grid.TileSize, -grid.TileSize), position + new Vector2(grid.TileSize, 0f)));
|
||||
}
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), tile + new Vector2i(_grid.TileSize, 0), VertLinesLookup, VertLinesLookupReversed);
|
||||
|
||||
// South edge
|
||||
if (relativeTile.Y == 0)
|
||||
{
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, -1), out neighborChunk) &&
|
||||
(neighborChunk.TileData &
|
||||
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(0, -1)), out neighborChunk) &&
|
||||
(neighborChunk.TileData[AtmosDirection.North] &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1));
|
||||
neighbor = (chunk.TileData & flag) != 0x0;
|
||||
neighbor = (chunk.TileData[AtmosDirection.North] & flag) != 0x0;
|
||||
}
|
||||
|
||||
if (!neighbor)
|
||||
{
|
||||
// Add points
|
||||
list.Add(new NavMapLine(position + new Vector2(grid.TileSize, 0f), position));
|
||||
}
|
||||
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed);
|
||||
|
||||
// West edge
|
||||
if (relativeTile.X == 0)
|
||||
{
|
||||
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(-1, 0), out neighborChunk) &&
|
||||
(neighborChunk.TileData &
|
||||
neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(-1, 0)), out neighborChunk) &&
|
||||
(neighborChunk.TileData[AtmosDirection.East] &
|
||||
SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0));
|
||||
neighbor = (chunk.TileData & flag) != 0x0;
|
||||
neighbor = (chunk.TileData[AtmosDirection.East] & flag) != 0x0;
|
||||
}
|
||||
|
||||
if (!neighbor)
|
||||
{
|
||||
// Add point
|
||||
list.Add(new NavMapLine(position, position + new Vector2(0f, -grid.TileSize)));
|
||||
}
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup, VertLinesLookupReversed);
|
||||
|
||||
// Draw a diagonal line for interiors.
|
||||
list.Add(new NavMapLine(position + new Vector2(0f, -grid.TileSize), position + new Vector2(grid.TileSize, 0f)));
|
||||
// Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these
|
||||
TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0)));
|
||||
}
|
||||
|
||||
decodedOutput.Add(chunkOrigin, list);
|
||||
}
|
||||
|
||||
return decodedOutput;
|
||||
// Record the combined lines
|
||||
foreach (var (origin, terminal) in HorizLinesLookup)
|
||||
TileLines.Add((origin.Item2, terminal.Item2));
|
||||
|
||||
foreach (var (origin, terminal) in VertLinesLookup)
|
||||
TileLines.Add((origin.Item2, terminal.Item2));
|
||||
}
|
||||
|
||||
private void UpdateNavMapAirlocks()
|
||||
{
|
||||
if (_navMap == null || _grid == null)
|
||||
return;
|
||||
|
||||
foreach (var ((category, _), chunk) in _navMap.Chunks)
|
||||
{
|
||||
if (category != NavMapChunkType.Airlock)
|
||||
continue;
|
||||
|
||||
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
|
||||
{
|
||||
var value = (int) Math.Pow(2, i);
|
||||
var mask = _navMapSystem.GetCombinedEdgesForChunk(chunk.TileData) & value;
|
||||
|
||||
if (mask == 0x0)
|
||||
continue;
|
||||
|
||||
var relative = SharedNavMapSystem.GetTile(mask);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize;
|
||||
|
||||
// If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge
|
||||
if (!_navMapSystem.AllTileEdgesAreOccupied(chunk.TileData, relative))
|
||||
{
|
||||
AddRectForThinAirlock(chunk.TileData, tile);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise add a single full tile airlock
|
||||
TileRects.Add((new Vector2(tile.X + FullWallInstep, -tile.Y - FullWallInstep),
|
||||
new Vector2(tile.X - FullWallInstep + 1f, -tile.Y + FullWallInstep - 1)));
|
||||
|
||||
TileLines.Add((new Vector2(tile.X + 0.5f, -tile.Y - FullWallInstep),
|
||||
new Vector2(tile.X + 0.5f, -tile.Y + FullWallInstep - 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddRectForThinWall(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
||||
{
|
||||
if (_navMapSystem == null || _grid == null)
|
||||
return;
|
||||
|
||||
var leftTop = new Vector2(-0.5f, -0.5f + ThinWallThickness);
|
||||
var rightBottom = new Vector2(0.5f, -0.5f);
|
||||
|
||||
foreach (var (direction, mask) in tileData)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize);
|
||||
var flag = (ushort) SharedNavMapSystem.GetFlag(relative);
|
||||
|
||||
if ((mask & flag) == 0)
|
||||
continue;
|
||||
|
||||
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
|
||||
var angle = new Angle(0);
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case AtmosDirection.East: angle = new Angle(MathF.PI * 0.5f); break;
|
||||
case AtmosDirection.South: angle = new Angle(MathF.PI); break;
|
||||
case AtmosDirection.West: angle = new Angle(MathF.PI * -0.5f); break;
|
||||
}
|
||||
|
||||
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddRectForThinAirlock(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile)
|
||||
{
|
||||
if (_navMapSystem == null || _grid == null)
|
||||
return;
|
||||
|
||||
var leftTop = new Vector2(-0.5f + FullWallInstep, -0.5f + FullWallInstep + ThinDoorThickness);
|
||||
var rightBottom = new Vector2(0.5f - FullWallInstep, -0.5f + FullWallInstep);
|
||||
var centreTop = new Vector2(0f, -0.5f + FullWallInstep + ThinDoorThickness);
|
||||
var centreBottom = new Vector2(0f, -0.5f + FullWallInstep);
|
||||
|
||||
foreach (var (direction, mask) in tileData)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize);
|
||||
var flag = (ushort) SharedNavMapSystem.GetFlag(relative);
|
||||
|
||||
if ((mask & flag) == 0)
|
||||
continue;
|
||||
|
||||
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
|
||||
var angle = new Angle(0);
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case AtmosDirection.East: angle = new Angle(MathF.PI * 0.5f);break;
|
||||
case AtmosDirection.South: angle = new Angle(MathF.PI); break;
|
||||
case AtmosDirection.West: angle = new Angle(MathF.PI * -0.5f); break;
|
||||
}
|
||||
|
||||
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
|
||||
TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition));
|
||||
}
|
||||
}
|
||||
|
||||
protected void AddOrUpdateNavMapLine
|
||||
(Vector2i origin,
|
||||
Vector2i terminus,
|
||||
Dictionary<(int, Vector2i), (int, Vector2i)> lookup,
|
||||
Dictionary<(int, Vector2i), (int, Vector2i)> lookupReversed,
|
||||
int index = 0)
|
||||
{
|
||||
(int, Vector2i) foundTermiusTuple;
|
||||
(int, Vector2i) foundOriginTuple;
|
||||
|
||||
if (lookup.TryGetValue((index, terminus), out foundTermiusTuple) &&
|
||||
lookupReversed.TryGetValue((index, origin), out foundOriginTuple))
|
||||
{
|
||||
lookup[foundOriginTuple] = foundTermiusTuple;
|
||||
lookupReversed[foundTermiusTuple] = foundOriginTuple;
|
||||
|
||||
lookup.Remove((index, terminus));
|
||||
lookupReversed.Remove((index, origin));
|
||||
}
|
||||
|
||||
else if (lookup.TryGetValue((index, terminus), out foundTermiusTuple))
|
||||
{
|
||||
lookup[(index, origin)] = foundTermiusTuple;
|
||||
lookup.Remove((index, terminus));
|
||||
lookupReversed[foundTermiusTuple] = (index, origin);
|
||||
}
|
||||
|
||||
else if (lookupReversed.TryGetValue((index, origin), out foundOriginTuple))
|
||||
{
|
||||
lookupReversed[(index, terminus)] = foundOriginTuple;
|
||||
lookupReversed.Remove(foundOriginTuple);
|
||||
lookup[foundOriginTuple] = (index, terminus);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
lookup.Add((index, origin), (index, terminus));
|
||||
lookupReversed.Add((index, terminus), (index, origin));
|
||||
}
|
||||
}
|
||||
|
||||
protected Vector2 GetOffset()
|
||||
@@ -612,15 +759,3 @@ public struct NavMapBlip
|
||||
Selectable = selectable;
|
||||
}
|
||||
}
|
||||
|
||||
public struct NavMapLine
|
||||
{
|
||||
public readonly Vector2 Origin;
|
||||
public readonly Vector2 Terminus;
|
||||
|
||||
public NavMapLine(Vector2 origin, Vector2 terminus)
|
||||
{
|
||||
Origin = origin;
|
||||
Terminus = terminus;
|
||||
}
|
||||
}
|
||||
|
||||
33
Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs
Normal file
33
Content.Client/Polymorph/Systems/ChameleonProjectorSystem.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Polymorph.Components;
|
||||
using Content.Shared.Polymorph.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Polymorph.Systems;
|
||||
|
||||
public sealed class ChameleonProjectorSystem : SharedChameleonProjectorSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
private EntityQuery<AppearanceComponent> _appearanceQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_appearanceQuery = GetEntityQuery<AppearanceComponent>();
|
||||
|
||||
SubscribeLocalEvent<ChameleonDisguiseComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||
}
|
||||
|
||||
private void OnHandleState(Entity<ChameleonDisguiseComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
CopyComp<SpriteComponent>(ent);
|
||||
CopyComp<GenericVisualizerComponent>(ent);
|
||||
CopyComp<SolutionContainerVisualsComponent>(ent);
|
||||
|
||||
// reload appearance to hopefully prevent any invisible layers
|
||||
if (_appearanceQuery.TryComp(ent, out var appearance))
|
||||
_appearance.QueueUpdate(ent, appearance);
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
||||
|
||||
public PowerMonitoringCableNetworksComponent? PowerMonitoringCableNetworks;
|
||||
public List<PowerMonitoringConsoleLineGroup> HiddenLineGroups = new();
|
||||
public Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>? PowerCableNetwork;
|
||||
public Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>? FocusCableNetwork;
|
||||
public List<PowerMonitoringConsoleLine> PowerCableNetwork = new();
|
||||
public List<PowerMonitoringConsoleLine> FocusCableNetwork = new();
|
||||
|
||||
private MapGridComponent? _grid;
|
||||
|
||||
@@ -48,15 +48,15 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
||||
if (!_entManager.TryGetComponent<PowerMonitoringCableNetworksComponent>(Owner, out var cableNetworks))
|
||||
return;
|
||||
|
||||
if (!_entManager.TryGetComponent(MapUid, out _grid))
|
||||
return;
|
||||
|
||||
PowerCableNetwork = GetDecodedPowerCableChunks(cableNetworks.AllChunks, _grid);
|
||||
FocusCableNetwork = GetDecodedPowerCableChunks(cableNetworks.FocusChunks, _grid);
|
||||
PowerCableNetwork = GetDecodedPowerCableChunks(cableNetworks.AllChunks);
|
||||
FocusCableNetwork = GetDecodedPowerCableChunks(cableNetworks.FocusChunks);
|
||||
}
|
||||
|
||||
public void DrawAllCableNetworks(DrawingHandleScreen handle)
|
||||
{
|
||||
if (!_entManager.TryGetComponent(MapUid, out _grid))
|
||||
return;
|
||||
|
||||
// Draw full cable network
|
||||
if (PowerCableNetwork != null && PowerCableNetwork.Count > 0)
|
||||
{
|
||||
@@ -69,36 +69,29 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
||||
DrawCableNetwork(handle, FocusCableNetwork, Color.White);
|
||||
}
|
||||
|
||||
public void DrawCableNetwork(DrawingHandleScreen handle, Dictionary<Vector2i, List<PowerMonitoringConsoleLine>> fullCableNetwork, Color modulator)
|
||||
public void DrawCableNetwork(DrawingHandleScreen handle, List<PowerMonitoringConsoleLine> fullCableNetwork, Color modulator)
|
||||
{
|
||||
if (!_entManager.TryGetComponent(MapUid, out _grid))
|
||||
return;
|
||||
|
||||
var offset = GetOffset();
|
||||
var area = new Box2(-WorldRange, -WorldRange, WorldRange + 1f, WorldRange + 1f).Translated(offset);
|
||||
offset = offset with { Y = -offset.Y };
|
||||
|
||||
if (WorldRange / WorldMaxRange > 0.5f)
|
||||
{
|
||||
var cableNetworks = new ValueList<Vector2>[3];
|
||||
|
||||
foreach ((var chunk, var chunkedLines) in fullCableNetwork)
|
||||
foreach (var line in fullCableNetwork)
|
||||
{
|
||||
var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
|
||||
|
||||
if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
||||
if (HiddenLineGroups.Contains(line.Group))
|
||||
continue;
|
||||
|
||||
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
||||
continue;
|
||||
var cableOffset = _powerCableOffsets[(int) line.Group];
|
||||
var start = ScalePosition(line.Origin + cableOffset - offset);
|
||||
var end = ScalePosition(line.Terminus + cableOffset - offset);
|
||||
|
||||
foreach (var chunkedLine in chunkedLines)
|
||||
{
|
||||
if (HiddenLineGroups.Contains(chunkedLine.Group))
|
||||
continue;
|
||||
|
||||
var start = ScalePosition(chunkedLine.Origin - new Vector2(offset.X, -offset.Y));
|
||||
var end = ScalePosition(chunkedLine.Terminus - new Vector2(offset.X, -offset.Y));
|
||||
|
||||
cableNetworks[(int) chunkedLine.Group].Add(start);
|
||||
cableNetworks[(int) chunkedLine.Group].Add(end);
|
||||
}
|
||||
cableNetworks[(int) line.Group].Add(start);
|
||||
cableNetworks[(int) line.Group].Add(end);
|
||||
}
|
||||
|
||||
for (int cableNetworkIdx = 0; cableNetworkIdx < cableNetworks.Length; cableNetworkIdx++)
|
||||
@@ -124,48 +117,39 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
||||
{
|
||||
var cableVertexUVs = new ValueList<Vector2>[3];
|
||||
|
||||
foreach ((var chunk, var chunkedLines) in fullCableNetwork)
|
||||
foreach (var line in fullCableNetwork)
|
||||
{
|
||||
var offsetChunk = new Vector2(chunk.X, chunk.Y) * SharedNavMapSystem.ChunkSize;
|
||||
|
||||
if (offsetChunk.X < area.Left - SharedNavMapSystem.ChunkSize || offsetChunk.X > area.Right)
|
||||
if (HiddenLineGroups.Contains(line.Group))
|
||||
continue;
|
||||
|
||||
if (offsetChunk.Y < area.Bottom - SharedNavMapSystem.ChunkSize || offsetChunk.Y > area.Top)
|
||||
continue;
|
||||
var cableOffset = _powerCableOffsets[(int) line.Group];
|
||||
|
||||
foreach (var chunkedLine in chunkedLines)
|
||||
{
|
||||
if (HiddenLineGroups.Contains(chunkedLine.Group))
|
||||
continue;
|
||||
var leftTop = ScalePosition(new Vector2
|
||||
(Math.Min(line.Origin.X, line.Terminus.X) - 0.1f,
|
||||
Math.Min(line.Origin.Y, line.Terminus.Y) - 0.1f)
|
||||
+ cableOffset - offset);
|
||||
|
||||
var leftTop = ScalePosition(new Vector2
|
||||
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
|
||||
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
|
||||
- new Vector2(offset.X, -offset.Y));
|
||||
var rightTop = ScalePosition(new Vector2
|
||||
(Math.Max(line.Origin.X, line.Terminus.X) + 0.1f,
|
||||
Math.Min(line.Origin.Y, line.Terminus.Y) - 0.1f)
|
||||
+ cableOffset - offset);
|
||||
|
||||
var rightTop = ScalePosition(new Vector2
|
||||
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
|
||||
Math.Min(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) - 0.1f)
|
||||
- new Vector2(offset.X, -offset.Y));
|
||||
var leftBottom = ScalePosition(new Vector2
|
||||
(Math.Min(line.Origin.X, line.Terminus.X) - 0.1f,
|
||||
Math.Max(line.Origin.Y, line.Terminus.Y) + 0.1f)
|
||||
+ cableOffset - offset);
|
||||
|
||||
var leftBottom = ScalePosition(new Vector2
|
||||
(Math.Min(chunkedLine.Origin.X, chunkedLine.Terminus.X) - 0.1f,
|
||||
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
|
||||
- new Vector2(offset.X, -offset.Y));
|
||||
var rightBottom = ScalePosition(new Vector2
|
||||
(Math.Max(line.Origin.X, line.Terminus.X) + 0.1f,
|
||||
Math.Max(line.Origin.Y, line.Terminus.Y) + 0.1f)
|
||||
+ cableOffset - offset);
|
||||
|
||||
var rightBottom = ScalePosition(new Vector2
|
||||
(Math.Max(chunkedLine.Origin.X, chunkedLine.Terminus.X) + 0.1f,
|
||||
Math.Max(chunkedLine.Origin.Y, chunkedLine.Terminus.Y) + 0.1f)
|
||||
- new Vector2(offset.X, -offset.Y));
|
||||
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(leftBottom);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(leftTop);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(leftTop);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(rightBottom);
|
||||
cableVertexUVs[(int) chunkedLine.Group].Add(rightTop);
|
||||
}
|
||||
cableVertexUVs[(int) line.Group].Add(leftBottom);
|
||||
cableVertexUVs[(int) line.Group].Add(leftTop);
|
||||
cableVertexUVs[(int) line.Group].Add(rightBottom);
|
||||
cableVertexUVs[(int) line.Group].Add(leftTop);
|
||||
cableVertexUVs[(int) line.Group].Add(rightBottom);
|
||||
cableVertexUVs[(int) line.Group].Add(rightTop);
|
||||
}
|
||||
|
||||
for (int cableNetworkIdx = 0; cableNetworkIdx < cableVertexUVs.Length; cableNetworkIdx++)
|
||||
@@ -188,23 +172,28 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>? GetDecodedPowerCableChunks(Dictionary<Vector2i, PowerCableChunk>? chunks, MapGridComponent? grid)
|
||||
public List<PowerMonitoringConsoleLine> GetDecodedPowerCableChunks(Dictionary<Vector2i, PowerCableChunk>? chunks)
|
||||
{
|
||||
if (chunks == null || grid == null)
|
||||
return null;
|
||||
var decodedOutput = new List<PowerMonitoringConsoleLine>();
|
||||
|
||||
var decodedOutput = new Dictionary<Vector2i, List<PowerMonitoringConsoleLine>>();
|
||||
if (!_entManager.TryGetComponent(MapUid, out _grid))
|
||||
return decodedOutput;
|
||||
|
||||
if (chunks == null)
|
||||
return decodedOutput;
|
||||
|
||||
// We'll use the following dictionaries to combine collinear power cable lines
|
||||
HorizLinesLookup.Clear();
|
||||
HorizLinesLookupReversed.Clear();
|
||||
VertLinesLookup.Clear();
|
||||
VertLinesLookupReversed.Clear();
|
||||
|
||||
foreach ((var chunkOrigin, var chunk) in chunks)
|
||||
{
|
||||
var list = new List<PowerMonitoringConsoleLine>();
|
||||
|
||||
for (int cableIdx = 0; cableIdx < chunk.PowerCableData.Length; cableIdx++)
|
||||
{
|
||||
var chunkMask = chunk.PowerCableData[cableIdx];
|
||||
|
||||
Vector2 offset = _powerCableOffsets[cableIdx];
|
||||
|
||||
for (var chunkIdx = 0; chunkIdx < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; chunkIdx++)
|
||||
{
|
||||
var value = (int) Math.Pow(2, chunkIdx);
|
||||
@@ -214,8 +203,8 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
||||
continue;
|
||||
|
||||
var relativeTile = SharedNavMapSystem.GetTile(mask);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * grid.TileSize;
|
||||
var position = new Vector2(tile.X, -tile.Y);
|
||||
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
|
||||
tile = tile with { Y = -tile.Y };
|
||||
|
||||
PowerCableChunk neighborChunk;
|
||||
bool neighbor;
|
||||
@@ -237,12 +226,7 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
||||
if (neighbor)
|
||||
{
|
||||
// Add points
|
||||
var line = new PowerMonitoringConsoleLine
|
||||
(position + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
||||
position + new Vector2(1f, 0f) + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
||||
(PowerMonitoringConsoleLineGroup) cableIdx);
|
||||
|
||||
list.Add(line);
|
||||
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed, cableIdx);
|
||||
}
|
||||
|
||||
// North
|
||||
@@ -260,21 +244,21 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
|
||||
if (neighbor)
|
||||
{
|
||||
// Add points
|
||||
var line = new PowerMonitoringConsoleLine
|
||||
(position + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
||||
position + new Vector2(0f, -1f) + offset + new Vector2(grid.TileSize * 0.5f, -grid.TileSize * 0.5f),
|
||||
(PowerMonitoringConsoleLineGroup) cableIdx);
|
||||
|
||||
list.Add(line);
|
||||
AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup, VertLinesLookupReversed, cableIdx);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (list.Count > 0)
|
||||
decodedOutput.Add(chunkOrigin, list);
|
||||
}
|
||||
|
||||
var gridOffset = new Vector2(_grid.TileSize * 0.5f, -_grid.TileSize * 0.5f);
|
||||
|
||||
foreach (var (origin, terminal) in HorizLinesLookup)
|
||||
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1));
|
||||
|
||||
foreach (var (origin, terminal) in VertLinesLookup)
|
||||
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1));
|
||||
|
||||
return decodedOutput;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,9 +170,6 @@ public sealed partial class PowerMonitoringWindow : FancyWindow
|
||||
NavMap.TrackedEntities[mon.Value] = blip;
|
||||
}
|
||||
|
||||
// Update nav map
|
||||
NavMap.ForceNavMapUpdate();
|
||||
|
||||
// If the entry group doesn't match the current tab, the data is out dated, do not use it
|
||||
if (allEntries.Length > 0 && allEntries[0].Group != GetCurrentPowerMonitoringConsoleGroup())
|
||||
return;
|
||||
|
||||
@@ -135,6 +135,9 @@ namespace Content.Client.Preferences.UI
|
||||
_humanoidProfileEditor.CharacterSlot = characterIndexCopy;
|
||||
_humanoidProfileEditor.UpdateControls();
|
||||
_preferencesManager.SelectCharacter(character);
|
||||
var controller = UserInterfaceManager.GetUIController<LobbyUIController>();
|
||||
controller.UpdateProfile(_humanoidProfileEditor.Profile);
|
||||
controller.ReloadCharacterUI();
|
||||
UpdateUI();
|
||||
args.Event.Handle();
|
||||
};
|
||||
|
||||
@@ -1115,6 +1115,7 @@ namespace Content.Client.Preferences.UI
|
||||
UpdateEyePickers();
|
||||
UpdateSaveButton();
|
||||
UpdateLoadouts();
|
||||
UpdateRoleRequirements();
|
||||
UpdateJobPriorities();
|
||||
UpdateAntagPreferences();
|
||||
UpdateTraitPreferences();
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed class StorageSystem : SharedStorageSystem
|
||||
|
||||
SubscribeLocalEvent<StorageComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeNetworkEvent<PickupAnimationEvent>(HandlePickupAnimation);
|
||||
SubscribeNetworkEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
|
||||
SubscribeAllEvent<AnimateInsertingEntitiesEvent>(HandleAnimatingInsertingEntities);
|
||||
}
|
||||
|
||||
public override void UpdateUI(Entity<StorageComponent?> entity)
|
||||
|
||||
@@ -1234,6 +1234,11 @@ namespace Content.Client.Stylesheets
|
||||
new StyleProperty("font", notoSans10),
|
||||
}),
|
||||
|
||||
Element<RichTextLabel>()
|
||||
.Class(StyleClassItemStatus)
|
||||
.Prop(nameof(RichTextLabel.LineHeightScale), 0.7f)
|
||||
.Prop(nameof(Control.Margin), new Thickness(0, 0, 0, -6)),
|
||||
|
||||
// Slider
|
||||
new StyleRule(SelectorElement.Type(typeof(Slider)), new []
|
||||
{
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
using Content.Client.Tools.UI;
|
||||
using Content.Shared.Tools.Components;
|
||||
|
||||
namespace Content.Client.Tools.Components
|
||||
{
|
||||
[RegisterComponent, Access(typeof(ToolSystem), typeof(WelderStatusControl))]
|
||||
public sealed partial class WelderComponent : SharedWelderComponent
|
||||
{
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool UiUpdateNeeded { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public float FuelCapacity { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public float Fuel { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
using Content.Client.Items;
|
||||
using Content.Client.Tools.Components;
|
||||
using Content.Client.Tools.UI;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using SharedToolSystem = Content.Shared.Tools.Systems.SharedToolSystem;
|
||||
|
||||
namespace Content.Client.Tools
|
||||
@@ -15,8 +13,7 @@ namespace Content.Client.Tools
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<WelderComponent, ComponentHandleState>(OnWelderHandleState);
|
||||
Subs.ItemStatus<WelderComponent>(ent => new WelderStatusControl(ent));
|
||||
Subs.ItemStatus<WelderComponent>(ent => new WelderStatusControl(ent, EntityManager, this));
|
||||
Subs.ItemStatus<MultipleToolComponent>(ent => new MultipleToolStatusControl(ent));
|
||||
}
|
||||
|
||||
@@ -42,20 +39,5 @@ namespace Content.Client.Tools
|
||||
sprite.LayerSetSprite(0, current.Sprite);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWelderHandleState(EntityUid uid, WelderComponent welder, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not WelderComponentState state)
|
||||
return;
|
||||
|
||||
welder.FuelCapacity = state.FuelCapacity;
|
||||
welder.Fuel = state.Fuel;
|
||||
welder.UiUpdateNeeded = true;
|
||||
}
|
||||
|
||||
protected override bool IsWelder(EntityUid uid)
|
||||
{
|
||||
return HasComp<WelderComponent>(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,45 @@
|
||||
using Content.Client.Items.UI;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.Tools.Components;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Client.UserInterface;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
using ItemToggleComponent = Content.Shared.Item.ItemToggle.Components.ItemToggleComponent;
|
||||
|
||||
namespace Content.Client.Tools.UI;
|
||||
|
||||
public sealed class WelderStatusControl : Control
|
||||
public sealed class WelderStatusControl : PollingItemStatusControl<WelderStatusControl.Data>
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private readonly WelderComponent _parent;
|
||||
private readonly ItemToggleComponent? _toggleComponent;
|
||||
private readonly Entity<WelderComponent> _parent;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly SharedToolSystem _toolSystem;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
public WelderStatusControl(Entity<WelderComponent> parent)
|
||||
public WelderStatusControl(Entity<WelderComponent> parent, IEntityManager entityManager, SharedToolSystem toolSystem)
|
||||
{
|
||||
_parent = parent;
|
||||
_entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (_entMan.TryGetComponent<ItemToggleComponent>(parent, out var itemToggle))
|
||||
_toggleComponent = itemToggle;
|
||||
_entityManager = entityManager;
|
||||
_toolSystem = toolSystem;
|
||||
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||
AddChild(_label);
|
||||
|
||||
UpdateDraw();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
protected override Data PollData()
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!_parent.UiUpdateNeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
Update();
|
||||
var (fuel, capacity) = _toolSystem.GetWelderFuelAndCapacity(_parent, _parent.Comp);
|
||||
return new Data(fuel, capacity, _parent.Comp.Enabled);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
protected override void Update(in Data data)
|
||||
{
|
||||
_parent.UiUpdateNeeded = false;
|
||||
|
||||
var fuelCap = _parent.FuelCapacity;
|
||||
var fuel = _parent.Fuel;
|
||||
var lit = false;
|
||||
if (_toggleComponent != null)
|
||||
{
|
||||
lit = _toggleComponent.Activated;
|
||||
}
|
||||
|
||||
_label.SetMarkup(Loc.GetString("welder-component-on-examine-detailed-message",
|
||||
("colorName", fuel < fuelCap / 4f ? "darkorange" : "orange"),
|
||||
("fuelLeft", Math.Round(fuel, 1)),
|
||||
("fuelCapacity", fuelCap),
|
||||
("status", Loc.GetString(lit ? "welder-component-on-examine-welder-lit-message" : "welder-component-on-examine-welder-not-lit-message"))));
|
||||
("colorName", data.Fuel < data.FuelCapacity / 4f ? "darkorange" : "orange"),
|
||||
("fuelLeft", data.Fuel),
|
||||
("fuelCapacity", data.FuelCapacity),
|
||||
("status", Loc.GetString(data.Lit ? "welder-component-on-examine-welder-lit-message" : "welder-component-on-examine-welder-not-lit-message"))));
|
||||
}
|
||||
|
||||
public record struct Data(FixedPoint2 Fuel, FixedPoint2 FuelCapacity, bool Lit);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace Content.Client.UserInterface.Controls
|
||||
var stretch = _cfg.GetCVar(CCVars.ViewportStretch);
|
||||
var renderScaleUp = _cfg.GetCVar(CCVars.ViewportScaleRender);
|
||||
var fixedFactor = _cfg.GetCVar(CCVars.ViewportFixedScaleFactor);
|
||||
var verticalFit = _cfg.GetCVar(CCVars.ViewportVerticalFit);
|
||||
|
||||
if (stretch)
|
||||
{
|
||||
@@ -60,6 +61,7 @@ namespace Content.Client.UserInterface.Controls
|
||||
// Did not find a snap, enable stretching.
|
||||
Viewport.FixedStretchSize = null;
|
||||
Viewport.StretchMode = ScalingViewportStretchMode.Bilinear;
|
||||
Viewport.IgnoreDimension = verticalFit ? ScalingViewportIgnoreDimension.Horizontal : ScalingViewportIgnoreDimension.None;
|
||||
|
||||
if (renderScaleUp)
|
||||
{
|
||||
@@ -104,6 +106,8 @@ namespace Content.Client.UserInterface.Controls
|
||||
// where we are clipping the viewport to make it fit.
|
||||
var cfgToleranceClip = _cfg.GetCVar(CCVars.ViewportSnapToleranceClip);
|
||||
|
||||
var cfgVerticalFit = _cfg.GetCVar(CCVars.ViewportVerticalFit);
|
||||
|
||||
// Calculate if the viewport, when rendered at an integer scale,
|
||||
// is close enough to the control size to enable "snapping" to NN,
|
||||
// potentially cutting a tiny bit off/leaving a margin.
|
||||
@@ -123,7 +127,8 @@ namespace Content.Client.UserInterface.Controls
|
||||
// The rule for which snap fits is that at LEAST one axis needs to be in the tolerance size wise.
|
||||
// One axis MAY be larger but not smaller than tolerance.
|
||||
// Obviously if it's too small it's bad, and if it's too big on both axis we should stretch up.
|
||||
if (Fits(dx) && Fits(dy) || Fits(dx) && Larger(dy) || Larger(dx) && Fits(dy))
|
||||
// Additionally, if the viewport's supposed to be vertically fit, then the horizontal scale should just be ignored where appropriate.
|
||||
if ((Fits(dx) || cfgVerticalFit) && Fits(dy) || !cfgVerticalFit && Fits(dx) && Larger(dy) || Larger(dx) && Fits(dy))
|
||||
{
|
||||
// Found snap that fits.
|
||||
return i;
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace Content.Client.UserInterface.Controls
|
||||
public SlotButton(SlotData slotData)
|
||||
{
|
||||
ButtonTexturePath = slotData.TextureName;
|
||||
FullButtonTexturePath = slotData.FullTextureName;
|
||||
Blocked = slotData.Blocked;
|
||||
Highlight = slotData.Highlighted;
|
||||
StorageTexturePath = "Slots/back";
|
||||
|
||||
@@ -15,11 +15,12 @@ namespace Content.Client.UserInterface.Controls
|
||||
public TextureRect ButtonRect { get; }
|
||||
public TextureRect BlockedRect { get; }
|
||||
public TextureRect HighlightRect { get; }
|
||||
public SpriteView SpriteView { get; }
|
||||
public SpriteView HoverSpriteView { get; }
|
||||
public TextureButton StorageButton { get; }
|
||||
public CooldownGraphic CooldownDisplay { get; }
|
||||
|
||||
private SpriteView SpriteView { get; }
|
||||
|
||||
public EntityUid? Entity => SpriteView.Entity;
|
||||
|
||||
private bool _slotNameSet;
|
||||
@@ -68,7 +69,18 @@ namespace Content.Client.UserInterface.Controls
|
||||
set
|
||||
{
|
||||
_buttonTexturePath = value;
|
||||
ButtonRect.Texture = Theme.ResolveTextureOrNull(_buttonTexturePath)?.Texture;
|
||||
UpdateButtonTexture();
|
||||
}
|
||||
}
|
||||
|
||||
private string? _fullButtonTexturePath;
|
||||
public string? FullButtonTexturePath
|
||||
{
|
||||
get => _fullButtonTexturePath;
|
||||
set
|
||||
{
|
||||
_fullButtonTexturePath = value;
|
||||
UpdateButtonTexture();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +209,21 @@ namespace Content.Client.UserInterface.Controls
|
||||
HoverSpriteView.SetEntity(null);
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid? ent)
|
||||
{
|
||||
SpriteView.SetEntity(ent);
|
||||
UpdateButtonTexture();
|
||||
}
|
||||
|
||||
private void UpdateButtonTexture()
|
||||
{
|
||||
var fullTexture = Theme.ResolveTextureOrNull(_fullButtonTexturePath);
|
||||
var texture = Entity.HasValue && fullTexture != null
|
||||
? fullTexture.Texture
|
||||
: Theme.ResolveTextureOrNull(_buttonTexturePath)?.Texture;
|
||||
ButtonRect.Texture = texture;
|
||||
}
|
||||
|
||||
private void OnButtonPressed(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
Pressed?.Invoke(args, this);
|
||||
@@ -229,8 +256,8 @@ namespace Content.Client.UserInterface.Controls
|
||||
base.OnThemeUpdated();
|
||||
|
||||
StorageButton.TextureNormal = Theme.ResolveTextureOrNull(_storageTexturePath)?.Texture;
|
||||
ButtonRect.Texture = Theme.ResolveTextureOrNull(_buttonTexturePath)?.Texture;
|
||||
HighlightRect.Texture = Theme.ResolveTextureOrNull(_highlightTexturePath)?.Texture;
|
||||
UpdateButtonTexture();
|
||||
}
|
||||
|
||||
EntityUid? IEntityControl.UiEntity => Entity;
|
||||
|
||||
@@ -28,6 +28,15 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
private readonly Dictionary<string, HandButton> _handLookup = new();
|
||||
private HandsComponent? _playerHandsComponent;
|
||||
private HandButton? _activeHand = null;
|
||||
|
||||
// We only have two item status controls (left and right hand),
|
||||
// but we may have more than two hands.
|
||||
// We handle this by having the item status be the *last active* hand of that side.
|
||||
// These variables store which that is.
|
||||
// ("middle" hands are hardcoded as right, whatever)
|
||||
private HandButton? _statusHandLeft;
|
||||
private HandButton? _statusHandRight;
|
||||
|
||||
private int _backupSuffix = 0; //this is used when autogenerating container names if they don't have names
|
||||
|
||||
private HotbarGui? HandsGui => UIManager.GetActiveUIWidgetOrNull<HotbarGui>();
|
||||
@@ -120,12 +129,12 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
|
||||
if (_entities.TryGetComponent(hand.HeldEntity, out VirtualItemComponent? virt))
|
||||
{
|
||||
handButton.SpriteView.SetEntity(virt.BlockingEntity);
|
||||
handButton.SetEntity(virt.BlockingEntity);
|
||||
handButton.Blocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
handButton.SpriteView.SetEntity(hand.HeldEntity);
|
||||
handButton.SetEntity(hand.HeldEntity);
|
||||
handButton.Blocked = false;
|
||||
}
|
||||
}
|
||||
@@ -171,17 +180,16 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
|
||||
if (_entities.TryGetComponent(entity, out VirtualItemComponent? virt))
|
||||
{
|
||||
hand.SpriteView.SetEntity(virt.BlockingEntity);
|
||||
hand.SetEntity(virt.BlockingEntity);
|
||||
hand.Blocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hand.SpriteView.SetEntity(entity);
|
||||
hand.SetEntity(entity);
|
||||
hand.Blocked = false;
|
||||
}
|
||||
|
||||
if (_playerHandsComponent?.ActiveHand?.Name == name)
|
||||
HandsGui?.UpdatePanelEntity(entity);
|
||||
UpdateHandStatus(hand, entity);
|
||||
}
|
||||
|
||||
private void OnItemRemoved(string name, EntityUid entity)
|
||||
@@ -190,9 +198,8 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
if (hand == null)
|
||||
return;
|
||||
|
||||
hand.SpriteView.SetEntity(null);
|
||||
if (_playerHandsComponent?.ActiveHand?.Name == name)
|
||||
HandsGui?.UpdatePanelEntity(null);
|
||||
hand.SetEntity(null);
|
||||
UpdateHandStatus(hand, null);
|
||||
}
|
||||
|
||||
private HandsContainer GetFirstAvailableContainer()
|
||||
@@ -232,7 +239,6 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
if (_activeHand != null)
|
||||
_activeHand.Highlight = false;
|
||||
|
||||
HandsGui?.UpdatePanelEntity(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -250,7 +256,19 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
_player.LocalSession?.AttachedEntity is { } playerEntity &&
|
||||
_handsSystem.TryGetHand(playerEntity, handName, out var hand, _playerHandsComponent))
|
||||
{
|
||||
HandsGui.UpdatePanelEntity(hand.HeldEntity);
|
||||
if (hand.Location == HandLocation.Left)
|
||||
{
|
||||
_statusHandLeft = handControl;
|
||||
HandsGui.UpdatePanelEntityLeft(hand.HeldEntity);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Middle or right
|
||||
_statusHandRight = handControl;
|
||||
HandsGui.UpdatePanelEntityRight(hand.HeldEntity);
|
||||
}
|
||||
|
||||
HandsGui.SetHighlightHand(hand.Location);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,6 +296,14 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
GetFirstAvailableContainer().AddButton(button);
|
||||
}
|
||||
|
||||
// If we don't have a status for this hand type yet, set it.
|
||||
// This means we have status filled by default in most scenarios,
|
||||
// otherwise the user'd need to switch hands to "activate" the hands the first time.
|
||||
if (location == HandLocation.Left)
|
||||
_statusHandLeft ??= button;
|
||||
else
|
||||
_statusHandRight ??= button;
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -336,6 +362,11 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
handContainer.RemoveButton(handButton);
|
||||
}
|
||||
|
||||
if (_statusHandLeft == handButton)
|
||||
_statusHandLeft = null;
|
||||
if (_statusHandRight == handButton)
|
||||
_statusHandRight = null;
|
||||
|
||||
_handLookup.Remove(handName);
|
||||
handButton.Dispose();
|
||||
return true;
|
||||
@@ -407,4 +438,13 @@ public sealed class HandsUIController : UIController, IOnStateEntered<GameplaySt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateHandStatus(HandButton hand, EntityUid? entity)
|
||||
{
|
||||
if (hand == _statusHandLeft)
|
||||
HandsGui?.UpdatePanelEntityLeft(entity);
|
||||
|
||||
if (hand == _statusHandRight)
|
||||
HandsGui?.UpdatePanelEntityRight(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class HotbarUIController : UIController
|
||||
ReloadHotbar();
|
||||
}
|
||||
|
||||
public void Setup(HandsContainer handsContainer, ItemStatusPanel handStatus, StorageContainer storageContainer)
|
||||
public void Setup(HandsContainer handsContainer, StorageContainer storageContainer)
|
||||
{
|
||||
_inventory = UIManager.GetUIController<InventoryUIController>();
|
||||
_hands = UIManager.GetUIController<HandsUIController>();
|
||||
|
||||
@@ -10,21 +10,14 @@
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Center">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Control HorizontalAlignment="Center">
|
||||
<inventory:ItemStatusPanel
|
||||
Name="StatusPanel"
|
||||
Visible="False"
|
||||
HorizontalAlignment="Center"
|
||||
/>
|
||||
<BoxContainer Name="StorageContainer"
|
||||
Access="Public"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="10">
|
||||
<storage:StorageContainer
|
||||
Name="StoragePanel"
|
||||
Visible="False"/>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
<BoxContainer Name="StorageContainer"
|
||||
Access="Public"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="10">
|
||||
<storage:StorageContainer
|
||||
Name="StoragePanel"
|
||||
Visible="False"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal" Name="Hotbar" HorizontalAlignment="Center">
|
||||
<inventory:ItemSlotButtonContainer
|
||||
Name="SecondHotbar"
|
||||
@@ -35,13 +28,22 @@
|
||||
ExpandBackwards="True"
|
||||
Columns="6"
|
||||
HorizontalExpand="False"/>
|
||||
<inventory:ItemStatusPanel
|
||||
Name="StatusPanelRight"
|
||||
HorizontalAlignment="Center" Margin="0 0 -2 2"
|
||||
SetWidth="125"
|
||||
MaxHeight="60"/>
|
||||
<hands:HandsContainer
|
||||
Name="HandContainer"
|
||||
Access="Public"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalExpand="False"
|
||||
ColumnLimit="6"
|
||||
Margin="4 0 4 0"/>
|
||||
ColumnLimit="6"/>
|
||||
<inventory:ItemStatusPanel
|
||||
Name="StatusPanelLeft"
|
||||
HorizontalAlignment="Center" Margin="-2 0 0 2"
|
||||
SetWidth="125"
|
||||
MaxHeight="60"/>
|
||||
<inventory:ItemSlotButtonContainer
|
||||
Name="MainHotbar"
|
||||
SlotGroup="MainHotbar"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -10,22 +11,27 @@ public sealed partial class HotbarGui : UIWidget
|
||||
public HotbarGui()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
StatusPanel.Update(null);
|
||||
StatusPanelRight.SetSide(HandLocation.Right);
|
||||
StatusPanelLeft.SetSide(HandLocation.Left);
|
||||
var hotbarController = UserInterfaceManager.GetUIController<HotbarUIController>();
|
||||
|
||||
hotbarController.Setup(HandContainer, StatusPanel, StoragePanel);
|
||||
hotbarController.Setup(HandContainer, StoragePanel);
|
||||
LayoutContainer.SetGrowVertical(this, LayoutContainer.GrowDirection.Begin);
|
||||
}
|
||||
|
||||
public void UpdatePanelEntity(EntityUid? entity)
|
||||
public void UpdatePanelEntityLeft(EntityUid? entity)
|
||||
{
|
||||
StatusPanel.Update(entity);
|
||||
if (entity == null)
|
||||
{
|
||||
StatusPanel.Visible = false;
|
||||
return;
|
||||
}
|
||||
StatusPanelLeft.Update(entity);
|
||||
}
|
||||
|
||||
StatusPanel.Visible = true;
|
||||
public void UpdatePanelEntityRight(EntityUid? entity)
|
||||
{
|
||||
StatusPanelRight.Update(entity);
|
||||
}
|
||||
|
||||
public void SetHighlightHand(HandLocation? hand)
|
||||
{
|
||||
StatusPanelLeft.UpdateHighlight(hand is HandLocation.Left);
|
||||
StatusPanelRight.UpdateHighlight(hand is HandLocation.Middle or HandLocation.Right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,26 @@
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Inventory.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Center"
|
||||
MinSize="150 0">
|
||||
<PanelContainer
|
||||
Name="Panel"
|
||||
ModulateSelfOverride="#FFFFFFE6"
|
||||
HorizontalExpand="True">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxTexture
|
||||
ContentMarginLeftOverride="6"
|
||||
ContentMarginRightOverride="6"
|
||||
ContentMarginTopOverride="4"
|
||||
ContentMarginBottomOverride="4" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="0">
|
||||
<BoxContainer Name="StatusContents" Orientation="Vertical"/>
|
||||
<Label Name="ItemNameLabel" StyleClasses="ItemStatus"/>
|
||||
HorizontalAlignment="Center">
|
||||
<Control Name="VisWrapper" Visible="False">
|
||||
<PanelContainer Name="Panel">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxTexture
|
||||
PatchMarginBottom="4"
|
||||
PatchMarginTop="6"
|
||||
TextureScale="2 2"
|
||||
Mode="Tile"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<PanelContainer Name="HighlightPanel">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxTexture PatchMarginBottom="4" PatchMarginTop="6" TextureScale="2 2">
|
||||
</graphics:StyleBoxTexture>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<BoxContainer Name="Contents" Orientation="Vertical" Margin="0 6 0 4">
|
||||
<BoxContainer Name="StatusContents" Orientation="Vertical" />
|
||||
<Label Name="ItemNameLabel" ClipText="True" StyleClasses="ItemStatus" Align="Left" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</Control>
|
||||
</controls:ItemStatusPanel>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Items;
|
||||
using Content.Client.Resources;
|
||||
using Content.Shared.Hands.Components;
|
||||
@@ -5,6 +6,7 @@ using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Inventory.VirtualItem;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -14,12 +16,15 @@ using static Content.Client.IoC.StaticIoC;
|
||||
namespace Content.Client.UserInterface.Systems.Inventory.Controls;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ItemStatusPanel : BoxContainer
|
||||
public sealed partial class ItemStatusPanel : Control
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
[ViewVariables] private EntityUid? _entity;
|
||||
|
||||
// Tracked so we can re-run SetSide() if the theme changes.
|
||||
private HandLocation _side;
|
||||
|
||||
public ItemStatusPanel()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -30,41 +35,65 @@ public sealed partial class ItemStatusPanel : BoxContainer
|
||||
|
||||
public void SetSide(HandLocation location)
|
||||
{
|
||||
string texture;
|
||||
// AN IMPORTANT REMINDER ABOUT THIS CODE:
|
||||
// In the UI, the RIGHT hand is on the LEFT on the screen.
|
||||
// So that a character facing DOWN matches the hand positions.
|
||||
|
||||
Texture? texture;
|
||||
Texture? textureHighlight;
|
||||
StyleBox.Margin cutOut;
|
||||
StyleBox.Margin flat;
|
||||
Label.AlignMode textAlign;
|
||||
Thickness contentMargin;
|
||||
|
||||
switch (location)
|
||||
{
|
||||
case HandLocation.Left:
|
||||
texture = "/Textures/Interface/Nano/item_status_right.svg.96dpi.png";
|
||||
cutOut = StyleBox.Margin.Left | StyleBox.Margin.Top;
|
||||
flat = StyleBox.Margin.Right | StyleBox.Margin.Bottom;
|
||||
textAlign = Label.AlignMode.Right;
|
||||
case HandLocation.Right:
|
||||
texture = Theme.ResolveTexture("item_status_right");
|
||||
textureHighlight = Theme.ResolveTexture("item_status_right_highlight");
|
||||
cutOut = StyleBox.Margin.Left;
|
||||
flat = StyleBox.Margin.Right;
|
||||
contentMargin = MarginFromThemeColor("_itemstatus_content_margin_right");
|
||||
break;
|
||||
case HandLocation.Middle:
|
||||
texture = "/Textures/Interface/Nano/item_status_middle.svg.96dpi.png";
|
||||
cutOut = StyleBox.Margin.Right | StyleBox.Margin.Top;
|
||||
flat = StyleBox.Margin.Left | StyleBox.Margin.Bottom;
|
||||
textAlign = Label.AlignMode.Left;
|
||||
break;
|
||||
case HandLocation.Right:
|
||||
texture = "/Textures/Interface/Nano/item_status_left.svg.96dpi.png";
|
||||
cutOut = StyleBox.Margin.Right | StyleBox.Margin.Top;
|
||||
flat = StyleBox.Margin.Left | StyleBox.Margin.Bottom;
|
||||
textAlign = Label.AlignMode.Left;
|
||||
case HandLocation.Left:
|
||||
texture = Theme.ResolveTexture("item_status_left");
|
||||
textureHighlight = Theme.ResolveTexture("item_status_left_highlight");
|
||||
cutOut = StyleBox.Margin.Right;
|
||||
flat = StyleBox.Margin.Left;
|
||||
contentMargin = MarginFromThemeColor("_itemstatus_content_margin_left");
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(location), location, null);
|
||||
}
|
||||
|
||||
var panel = (StyleBoxTexture) Panel.PanelOverride!;
|
||||
panel.Texture = ResC.GetTexture(texture);
|
||||
panel.SetPatchMargin(flat, 2);
|
||||
panel.SetPatchMargin(cutOut, 13);
|
||||
Contents.Margin = contentMargin;
|
||||
|
||||
ItemNameLabel.Align = textAlign;
|
||||
var panel = (StyleBoxTexture) Panel.PanelOverride!;
|
||||
panel.Texture = texture;
|
||||
panel.SetPatchMargin(flat, 4);
|
||||
panel.SetPatchMargin(cutOut, 7);
|
||||
|
||||
var panelHighlight = (StyleBoxTexture) HighlightPanel.PanelOverride!;
|
||||
panelHighlight.Texture = textureHighlight;
|
||||
panelHighlight.SetPatchMargin(flat, 4);
|
||||
panelHighlight.SetPatchMargin(cutOut, 7);
|
||||
|
||||
_side = location;
|
||||
}
|
||||
|
||||
private Thickness MarginFromThemeColor(string itemName)
|
||||
{
|
||||
// This is the worst thing I've ever programmed
|
||||
// (can you tell I'm a graphics programmer?)
|
||||
// (the margin needs to change depending on the UI theme, so we use a fake color entry to store the value)
|
||||
|
||||
var color = Theme.ResolveColorOrSpecified(itemName);
|
||||
return new Thickness(color.RByte, color.GByte, color.BByte, color.AByte);
|
||||
}
|
||||
|
||||
protected override void OnThemeUpdated()
|
||||
{
|
||||
SetSide(_side);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -79,7 +108,7 @@ public sealed partial class ItemStatusPanel : BoxContainer
|
||||
{
|
||||
ClearOldStatus();
|
||||
_entity = null;
|
||||
Panel.Visible = false;
|
||||
VisWrapper.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,7 +120,12 @@ public sealed partial class ItemStatusPanel : BoxContainer
|
||||
UpdateItemName();
|
||||
}
|
||||
|
||||
Panel.Visible = true;
|
||||
VisWrapper.Visible = true;
|
||||
}
|
||||
|
||||
public void UpdateHighlight(bool highlight)
|
||||
{
|
||||
HighlightPanel.Visible = highlight;
|
||||
}
|
||||
|
||||
private void UpdateItemName()
|
||||
|
||||
@@ -417,7 +417,7 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
|
||||
|
||||
if (_strippingWindow?.InventoryButtons.GetButton(update.Name) is { } inventoryButton)
|
||||
{
|
||||
inventoryButton.SpriteView.SetEntity(entity);
|
||||
inventoryButton.SetEntity(entity);
|
||||
inventoryButton.StorageButton.Visible = showStorage;
|
||||
}
|
||||
|
||||
@@ -426,12 +426,12 @@ public sealed class InventoryUIController : UIController, IOnStateEntered<Gamepl
|
||||
|
||||
if (_entities.TryGetComponent(entity, out VirtualItemComponent? virtb))
|
||||
{
|
||||
button.SpriteView.SetEntity(virtb.BlockingEntity);
|
||||
button.SetEntity(virtb.BlockingEntity);
|
||||
button.Blocked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
button.SpriteView.SetEntity(entity);
|
||||
button.SetEntity(entity);
|
||||
button.Blocked = false;
|
||||
button.StorageButton.Visible = showStorage;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ public sealed class ViewportUIController : UIController
|
||||
_configurationManager.OnValueChanged(CCVars.ViewportMinimumWidth, _ => UpdateViewportRatio());
|
||||
_configurationManager.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportRatio());
|
||||
_configurationManager.OnValueChanged(CCVars.ViewportWidth, _ => UpdateViewportRatio());
|
||||
_configurationManager.OnValueChanged(CCVars.ViewportVerticalFit, _ => UpdateViewportRatio());
|
||||
|
||||
var gameplayStateLoad = UIManager.GetUIController<GameplayStateLoadController>();
|
||||
gameplayStateLoad.OnScreenLoad += OnScreenLoad;
|
||||
@@ -45,13 +46,19 @@ public sealed class ViewportUIController : UIController
|
||||
var min = _configurationManager.GetCVar(CCVars.ViewportMinimumWidth);
|
||||
var max = _configurationManager.GetCVar(CCVars.ViewportMaximumWidth);
|
||||
var width = _configurationManager.GetCVar(CCVars.ViewportWidth);
|
||||
var verticalfit = _configurationManager.GetCVar(CCVars.ViewportVerticalFit) && _configurationManager.GetCVar(CCVars.ViewportStretch);
|
||||
|
||||
if (width < min || width > max)
|
||||
if (verticalfit)
|
||||
{
|
||||
width = max;
|
||||
}
|
||||
else if (width < min || width > max)
|
||||
{
|
||||
width = CCVars.ViewportWidth.DefaultValue;
|
||||
}
|
||||
|
||||
Viewport.Viewport.ViewportSize = (EyeManager.PixelsPerMeter * width, EyeManager.PixelsPerMeter * ViewportHeight);
|
||||
Viewport.UpdateCfg();
|
||||
}
|
||||
|
||||
public void ReloadViewport()
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Content.Client.Viewport
|
||||
private int _curRenderScale;
|
||||
private ScalingViewportStretchMode _stretchMode = ScalingViewportStretchMode.Bilinear;
|
||||
private ScalingViewportRenderScaleMode _renderScaleMode = ScalingViewportRenderScaleMode.Fixed;
|
||||
private ScalingViewportIgnoreDimension _ignoreDimension = ScalingViewportIgnoreDimension.None;
|
||||
private int _fixedRenderScale = 1;
|
||||
|
||||
private readonly List<CopyPixelsDelegate<Rgba32>> _queuedScreenshots = new();
|
||||
@@ -106,6 +107,17 @@ namespace Content.Client.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ScalingViewportIgnoreDimension IgnoreDimension
|
||||
{
|
||||
get => _ignoreDimension;
|
||||
set
|
||||
{
|
||||
_ignoreDimension = value;
|
||||
InvalidateViewport();
|
||||
}
|
||||
}
|
||||
|
||||
public ScalingViewport()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -178,7 +190,19 @@ namespace Content.Client.Viewport
|
||||
if (FixedStretchSize == null)
|
||||
{
|
||||
var (ratioX, ratioY) = ourSize / vpSize;
|
||||
var ratio = Math.Min(ratioX, ratioY);
|
||||
var ratio = 1f;
|
||||
switch (_ignoreDimension)
|
||||
{
|
||||
case ScalingViewportIgnoreDimension.None:
|
||||
ratio = Math.Min(ratioX, ratioY);
|
||||
break;
|
||||
case ScalingViewportIgnoreDimension.Vertical:
|
||||
ratio = ratioX;
|
||||
break;
|
||||
case ScalingViewportIgnoreDimension.Horizontal:
|
||||
ratio = ratioY;
|
||||
break;
|
||||
}
|
||||
|
||||
var size = vpSize * ratio;
|
||||
// Size
|
||||
@@ -357,4 +381,25 @@ namespace Content.Client.Viewport
|
||||
/// </summary>
|
||||
CeilInt
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the viewport is allowed to freely scale, this determines which dimensions should be ignored while fitting the viewport
|
||||
/// </summary>
|
||||
public enum ScalingViewportIgnoreDimension
|
||||
{
|
||||
/// <summary>
|
||||
/// The viewport won't ignore any dimension.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The viewport will ignore the horizontal dimension, and will exclusively consider the vertical dimension for scaling.
|
||||
/// </summary>
|
||||
Horizontal,
|
||||
|
||||
/// <summary>
|
||||
/// The viewport will ignore the vertical dimension, and will exclusively consider the horizontal dimension for scaling.
|
||||
/// </summary>
|
||||
Vertical
|
||||
}
|
||||
}
|
||||
|
||||
178
Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs
Normal file
178
Content.Client/Weapons/Ranged/ItemStatus/BulletRender.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Resources;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.ItemStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Renders one or more rows of bullets for item status.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a custom control to allow complex responsive layout logic.
|
||||
/// </remarks>
|
||||
public sealed class BulletRender : Control
|
||||
{
|
||||
private static readonly Color ColorA = Color.FromHex("#b68f0e");
|
||||
private static readonly Color ColorB = Color.FromHex("#d7df60");
|
||||
private static readonly Color ColorGoneA = Color.FromHex("#000000");
|
||||
private static readonly Color ColorGoneB = Color.FromHex("#222222");
|
||||
|
||||
/// <summary>
|
||||
/// Try to ensure there's at least this many bullets on one row.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For example, if there are two rows and the second row has only two bullets,
|
||||
/// we "steal" some bullets from the row below it to make it look nicer.
|
||||
/// </remarks>
|
||||
public const int MinCountPerRow = 7;
|
||||
|
||||
public const int BulletHeight = 12;
|
||||
public const int BulletSeparationNormal = 3;
|
||||
public const int BulletSeparationTiny = 2;
|
||||
public const int BulletWidthNormal = 5;
|
||||
public const int BulletWidthTiny = 2;
|
||||
public const int VerticalSeparation = 2;
|
||||
|
||||
private readonly Texture _bulletTiny;
|
||||
private readonly Texture _bulletNormal;
|
||||
|
||||
private int _capacity;
|
||||
private BulletType _type = BulletType.Normal;
|
||||
|
||||
public int Rows { get; set; } = 2;
|
||||
public int Count { get; set; }
|
||||
|
||||
public int Capacity
|
||||
{
|
||||
get => _capacity;
|
||||
set
|
||||
{
|
||||
_capacity = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
public BulletType Type
|
||||
{
|
||||
get => _type;
|
||||
set
|
||||
{
|
||||
_type = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
public BulletRender()
|
||||
{
|
||||
var resC = IoCManager.Resolve<IResourceCache>();
|
||||
_bulletTiny = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/tiny.png");
|
||||
_bulletNormal = resC.GetTexture("/Textures/Interface/ItemStatus/Bullets/normal.png");
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var countPerRow = Math.Min(Capacity, CountPerRow(availableSize.X));
|
||||
|
||||
var rows = Math.Min((int) MathF.Ceiling(Capacity / (float) countPerRow), Rows);
|
||||
|
||||
var height = BulletHeight * rows + (BulletSeparationNormal * rows - 1);
|
||||
var width = RowWidth(countPerRow);
|
||||
|
||||
return new Vector2(width, height);
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
// Scale rendering in this control by UIScale.
|
||||
var currentTransform = handle.GetTransform();
|
||||
handle.SetTransform(Matrix3.CreateScale(new Vector2(UIScale)) * currentTransform);
|
||||
|
||||
var countPerRow = CountPerRow(Size.X);
|
||||
|
||||
var (separation, _) = BulletParams();
|
||||
var texture = Type == BulletType.Normal ? _bulletNormal : _bulletTiny;
|
||||
|
||||
var pos = new Vector2();
|
||||
|
||||
var altColor = false;
|
||||
|
||||
var spent = Capacity - Count;
|
||||
|
||||
var bulletsDone = 0;
|
||||
|
||||
// Draw by rows, bottom to top.
|
||||
for (var row = 0; row < Rows; row++)
|
||||
{
|
||||
altColor = false;
|
||||
|
||||
var thisRowCount = Math.Min(countPerRow, Capacity - bulletsDone);
|
||||
if (thisRowCount <= 0)
|
||||
break;
|
||||
|
||||
// Handle MinCountPerRow
|
||||
// We only do this if:
|
||||
// 1. The next row would have less than MinCountPerRow bullets.
|
||||
// 2. The next row is actually visible (we aren't the last row).
|
||||
// 3. MinCountPerRow is actually smaller than the count per row (avoid degenerate cases).
|
||||
var nextRowCount = Capacity - bulletsDone - thisRowCount;
|
||||
if (nextRowCount < MinCountPerRow && row != Rows - 1 && MinCountPerRow < countPerRow)
|
||||
thisRowCount -= MinCountPerRow - nextRowCount;
|
||||
|
||||
// Account for row width to right-align.
|
||||
var rowWidth = RowWidth(thisRowCount);
|
||||
pos.X += Size.X - rowWidth;
|
||||
|
||||
// Draw row left to right (so overlapping works)
|
||||
for (var bullet = 0; bullet < thisRowCount; bullet++)
|
||||
{
|
||||
var absIdx = Capacity - bulletsDone - thisRowCount + bullet;
|
||||
Color color;
|
||||
if (absIdx >= spent)
|
||||
color = altColor ? ColorA : ColorB;
|
||||
else
|
||||
color = altColor ? ColorGoneA : ColorGoneB;
|
||||
|
||||
var renderPos = pos;
|
||||
renderPos.Y = Size.Y - renderPos.Y - BulletHeight;
|
||||
handle.DrawTexture(texture, renderPos, color);
|
||||
pos.X += separation;
|
||||
altColor ^= true;
|
||||
}
|
||||
|
||||
bulletsDone += thisRowCount;
|
||||
pos.X = 0;
|
||||
pos.Y += BulletHeight + VerticalSeparation;
|
||||
}
|
||||
}
|
||||
|
||||
private int CountPerRow(float width)
|
||||
{
|
||||
var (separation, bulletWidth) = BulletParams();
|
||||
return (int) ((width - bulletWidth + separation) / separation);
|
||||
}
|
||||
|
||||
private (int separation, int width) BulletParams()
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
BulletType.Normal => (BulletSeparationNormal, BulletWidthNormal),
|
||||
BulletType.Tiny => (BulletSeparationTiny, BulletWidthTiny),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
private int RowWidth(int count)
|
||||
{
|
||||
var (separation, bulletWidth) = BulletParams();
|
||||
|
||||
return (count - 1) * separation + bulletWidth;
|
||||
}
|
||||
|
||||
public enum BulletType
|
||||
{
|
||||
Normal,
|
||||
Tiny
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,11 @@ using Content.Client.Items;
|
||||
using Content.Client.Resources;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.Weapons.Ranged.Components;
|
||||
using Content.Client.Weapons.Ranged.ItemStatus;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Graphics;
|
||||
|
||||
namespace Content.Client.Weapons.Ranged.Systems;
|
||||
|
||||
@@ -91,116 +91,26 @@ public sealed partial class GunSystem
|
||||
|
||||
private sealed class DefaultStatusControl : Control
|
||||
{
|
||||
private readonly BoxContainer _bulletsListTop;
|
||||
private readonly BoxContainer _bulletsListBottom;
|
||||
private readonly BulletRender _bulletRender;
|
||||
|
||||
public DefaultStatusControl()
|
||||
{
|
||||
MinHeight = 15;
|
||||
HorizontalExpand = true;
|
||||
VerticalAlignment = Control.VAlignment.Center;
|
||||
AddChild(new BoxContainer
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
AddChild(_bulletRender = new BulletRender
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListTop = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
new Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListBottom = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
VerticalAlignment = VAlignment.Bottom
|
||||
});
|
||||
}
|
||||
|
||||
public void Update(int count, int capacity)
|
||||
{
|
||||
_bulletsListTop.RemoveAllChildren();
|
||||
_bulletsListBottom.RemoveAllChildren();
|
||||
_bulletRender.Count = count;
|
||||
_bulletRender.Capacity = capacity;
|
||||
|
||||
string texturePath;
|
||||
if (capacity <= 20)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
|
||||
}
|
||||
else if (capacity <= 30)
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/small.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
texturePath = "/Textures/Interface/ItemStatus/Bullets/tiny.png";
|
||||
}
|
||||
|
||||
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||
|
||||
const int tinyMaxRow = 60;
|
||||
|
||||
if (capacity > tinyMaxRow)
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture);
|
||||
FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, count, capacity, texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
|
||||
{
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorGoneA : colorGoneB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorA : colorB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
_bulletRender.Type = capacity > 50 ? BulletRender.BulletType.Tiny : BulletRender.BulletType.Normal;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +201,7 @@ public sealed partial class GunSystem
|
||||
|
||||
private sealed class ChamberMagazineStatusControl : Control
|
||||
{
|
||||
private readonly BoxContainer _bulletsList;
|
||||
private readonly BulletRender _bulletRender;
|
||||
private readonly TextureRect _chamberedBullet;
|
||||
private readonly Label _noMagazineLabel;
|
||||
private readonly Label _ammoCount;
|
||||
@@ -308,23 +218,16 @@ public sealed partial class GunSystem
|
||||
HorizontalExpand = true,
|
||||
Children =
|
||||
{
|
||||
(_chamberedBullet = new TextureRect
|
||||
{
|
||||
Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered_rotated.png"),
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
}),
|
||||
new Control() { MinSize = new Vector2(5,0) },
|
||||
new Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Margin = new Thickness(0, 0, 5, 0),
|
||||
Children =
|
||||
{
|
||||
(_bulletsList = new BoxContainer
|
||||
(_bulletRender = new BulletRender
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
SeparationOverride = 0
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
VerticalAlignment = VAlignment.Bottom
|
||||
}),
|
||||
(_noMagazineLabel = new Label
|
||||
{
|
||||
@@ -333,12 +236,25 @@ public sealed partial class GunSystem
|
||||
})
|
||||
}
|
||||
},
|
||||
new Control() { MinSize = new Vector2(5,0) },
|
||||
(_ammoCount = new Label
|
||||
new BoxContainer
|
||||
{
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus},
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
}),
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
VerticalAlignment = VAlignment.Bottom,
|
||||
Margin = new Thickness(0, 0, 0, 2),
|
||||
Children =
|
||||
{
|
||||
(_ammoCount = new Label
|
||||
{
|
||||
StyleClasses = {StyleNano.StyleClassItemStatus},
|
||||
HorizontalAlignment = HAlignment.Right,
|
||||
}),
|
||||
(_chamberedBullet = new TextureRect
|
||||
{
|
||||
Texture = StaticIoC.ResC.GetTexture("/Textures/Interface/ItemStatus/Bullets/chambered.png"),
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -348,61 +264,24 @@ public sealed partial class GunSystem
|
||||
_chamberedBullet.ModulateSelfOverride =
|
||||
chambered ? Color.FromHex("#d7df60") : Color.Black;
|
||||
|
||||
_bulletsList.RemoveAllChildren();
|
||||
|
||||
if (!magazine)
|
||||
{
|
||||
_bulletRender.Visible = false;
|
||||
_noMagazineLabel.Visible = true;
|
||||
_ammoCount.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_bulletRender.Visible = true;
|
||||
_noMagazineLabel.Visible = false;
|
||||
_ammoCount.Visible = true;
|
||||
|
||||
var texturePath = "/Textures/Interface/ItemStatus/Bullets/normal.png";
|
||||
var texture = StaticIoC.ResC.GetTexture(texturePath);
|
||||
_bulletRender.Count = count;
|
||||
_bulletRender.Capacity = capacity;
|
||||
|
||||
_bulletRender.Type = capacity > 50 ? BulletRender.BulletType.Tiny : BulletRender.BulletType.Normal;
|
||||
|
||||
_ammoCount.Text = $"x{count:00}";
|
||||
capacity = Math.Min(capacity, 20);
|
||||
FillBulletRow(_bulletsList, count, capacity, texture);
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
|
||||
{
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
|
||||
// Draw the empty ones
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorGoneA : colorGoneB,
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
|
||||
// Draw the full ones, but limit the count to the capacity
|
||||
count = Math.Min(count, capacity);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorA : colorB,
|
||||
Stretch = TextureRect.StretchMode.KeepCentered
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayAlarmAnimation(Animation animation)
|
||||
|
||||
@@ -39,6 +39,14 @@ public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
SendMessage(new AnalysisConsoleExtractButtonPressedMessage());
|
||||
};
|
||||
_consoleMenu.OnUpBiasButtonPressed += () =>
|
||||
{
|
||||
SendMessage(new AnalysisConsoleBiasButtonPressedMessage(false));
|
||||
};
|
||||
_consoleMenu.OnDownBiasButtonPressed += () =>
|
||||
{
|
||||
SendMessage(new AnalysisConsoleBiasButtonPressedMessage(true));
|
||||
};
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
@@ -47,7 +55,7 @@ public sealed class AnalysisConsoleBoundUserInterface : BoundUserInterface
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case AnalysisConsoleScanUpdateState msg:
|
||||
case AnalysisConsoleUpdateState msg:
|
||||
_consoleMenu?.SetButtonsDisabled(msg);
|
||||
_consoleMenu?.UpdateInformationDisplay(msg);
|
||||
_consoleMenu?.UpdateProgressBar(msg);
|
||||
|
||||
@@ -1,30 +1,46 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc 'analysis-console-menu-title'}"
|
||||
MinSize="620 280"
|
||||
SetSize="620 280">
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
Title="{Loc 'analysis-console-menu-title'}"
|
||||
MinSize="620 280"
|
||||
SetSize="620 280">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer Margin="10 10 10 10" MinWidth="150" Orientation="Vertical" VerticalExpand="True" SizeFlagsStretchRatio="1">
|
||||
<BoxContainer Margin="10 10 10 10" MinWidth="150" Orientation="Vertical"
|
||||
VerticalExpand="True" SizeFlagsStretchRatio="1">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<Button Name="ServerSelectionButton"
|
||||
Text="{Loc 'analysis-console-server-list-button'}"></Button>
|
||||
Text="{Loc 'analysis-console-server-list-button'}"></Button>
|
||||
<BoxContainer MinHeight="5"></BoxContainer>
|
||||
<Button Name="ScanButton"
|
||||
Text="{Loc 'analysis-console-scan-button'}"
|
||||
ToolTip="{Loc 'analysis-console-scan-tooltip-info'}">
|
||||
Text="{Loc 'analysis-console-scan-button'}"
|
||||
ToolTip="{Loc 'analysis-console-scan-tooltip-info'}">
|
||||
</Button>
|
||||
<BoxContainer MinHeight="5"></BoxContainer>
|
||||
<Button Name="PrintButton"
|
||||
Text="{Loc 'analysis-console-print-button'}"
|
||||
ToolTip="{Loc 'analysis-console-print-tooltip-info'}">
|
||||
Text="{Loc 'analysis-console-print-button'}"
|
||||
ToolTip="{Loc 'analysis-console-print-tooltip-info'}">
|
||||
</Button>
|
||||
<BoxContainer MinHeight="5"></BoxContainer>
|
||||
<Button Name="ExtractButton"
|
||||
Text="{Loc 'analysis-console-extract-button'}"
|
||||
ToolTip="{Loc 'analysis-console-extract-button-info'}">
|
||||
Text="{Loc 'analysis-console-extract-button'}"
|
||||
ToolTip="{Loc 'analysis-console-extract-button-info'}">
|
||||
</Button>
|
||||
<BoxContainer MinHeight="5"></BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="UpBiasButton"
|
||||
Text="{Loc 'analysis-console-bias-up'}"
|
||||
ToolTip="{Loc 'analysis-console-bias-button-info-up'}"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenRight">
|
||||
</Button>
|
||||
<Button Name="DownBiasButton"
|
||||
Text="{Loc 'analysis-console-bias-down'}"
|
||||
ToolTip="{Loc 'analysis-console-bias-button-info-down'}"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="OpenLeft">
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Name="ProgressLabel"></Label>
|
||||
@@ -36,13 +52,13 @@
|
||||
</ProgressBar>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<customControls:VSeparator StyleClasses="LowDivider"/>
|
||||
<customControls:VSeparator StyleClasses="LowDivider" />
|
||||
<PanelContainer Margin="10 10 10 10" HorizontalExpand="True" SizeFlagsStretchRatio="3">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Margin="10 10 10 10" Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" >
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<BoxContainer VerticalExpand="True">
|
||||
<RichTextLabel Name="Information"> </RichTextLabel>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Xenoarchaeology.Equipment;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -19,6 +20,8 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
public event Action? OnScanButtonPressed;
|
||||
public event Action? OnPrintButtonPressed;
|
||||
public event Action? OnExtractButtonPressed;
|
||||
public event Action? OnUpBiasButtonPressed;
|
||||
public event Action? OnDownBiasButtonPressed;
|
||||
|
||||
// For rendering the progress bar, updated from BUI state
|
||||
private TimeSpan? _startTime;
|
||||
@@ -36,6 +39,12 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
ScanButton.OnPressed += _ => OnScanButtonPressed?.Invoke();
|
||||
PrintButton.OnPressed += _ => OnPrintButtonPressed?.Invoke();
|
||||
ExtractButton.OnPressed += _ => OnExtractButtonPressed?.Invoke();
|
||||
UpBiasButton.OnPressed += _ => OnUpBiasButtonPressed?.Invoke();
|
||||
DownBiasButton.OnPressed += _ => OnDownBiasButtonPressed?.Invoke();
|
||||
|
||||
var buttonGroup = new ButtonGroup(false);
|
||||
UpBiasButton.Group = buttonGroup;
|
||||
DownBiasButton.Group = buttonGroup;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -60,7 +69,7 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
ProgressBar.Value = Math.Clamp(1.0f - (float) remaining.Divide(total), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public void SetButtonsDisabled(AnalysisConsoleScanUpdateState state)
|
||||
public void SetButtonsDisabled(AnalysisConsoleUpdateState state)
|
||||
{
|
||||
ScanButton.Disabled = !state.CanScan;
|
||||
PrintButton.Disabled = !state.CanPrint;
|
||||
@@ -78,7 +87,6 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
ExtractButton.AddStyleClass("ButtonColorGreen");
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateArtifactIcon(EntityUid? uid)
|
||||
{
|
||||
if (uid == null)
|
||||
@@ -91,7 +99,7 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
ArtifactDisplay.SetEntity(uid);
|
||||
}
|
||||
|
||||
public void UpdateInformationDisplay(AnalysisConsoleScanUpdateState state)
|
||||
public void UpdateInformationDisplay(AnalysisConsoleUpdateState state)
|
||||
{
|
||||
var message = new FormattedMessage();
|
||||
|
||||
@@ -129,7 +137,7 @@ public sealed partial class AnalysisConsoleMenu : FancyWindow
|
||||
Information.SetMessage(message);
|
||||
}
|
||||
|
||||
public void UpdateProgressBar(AnalysisConsoleScanUpdateState state)
|
||||
public void UpdateProgressBar(AnalysisConsoleUpdateState state)
|
||||
{
|
||||
ProgressBar.Visible = state.Scanning;
|
||||
ProgressLabel.Visible = state.Scanning;
|
||||
|
||||
@@ -10,9 +10,8 @@ namespace Content.IntegrationTests.Pair;
|
||||
public sealed class TestMapData
|
||||
{
|
||||
public EntityUid MapUid { get; set; }
|
||||
public EntityUid GridUid { get; set; }
|
||||
public MapId MapId { get; set; }
|
||||
public MapGridComponent MapGrid { get; set; } = default!;
|
||||
public Entity<MapGridComponent> Grid;
|
||||
public MapId MapId;
|
||||
public EntityCoordinates GridCoords { get; set; }
|
||||
public MapCoordinates MapCoords { get; set; }
|
||||
public TileRef Tile { get; set; }
|
||||
@@ -21,4 +20,4 @@ public sealed class TestMapData
|
||||
public EntityUid CMapUid { get; set; }
|
||||
public EntityUid CGridUid { get; set; }
|
||||
public EntityCoordinates CGridCoords { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
@@ -14,36 +15,37 @@ public sealed partial class TestPair
|
||||
/// <summary>
|
||||
/// Creates a map, a grid, and a tile, and gives back references to them.
|
||||
/// </summary>
|
||||
public async Task<TestMapData> CreateTestMap()
|
||||
[MemberNotNull(nameof(TestMap))]
|
||||
public async Task<TestMapData> CreateTestMap(bool initialized = true, string tile = "Plating")
|
||||
{
|
||||
var mapData = new TestMapData();
|
||||
TestMap = mapData;
|
||||
await Server.WaitIdleAsync();
|
||||
var tileDefinitionManager = Server.ResolveDependency<ITileDefinitionManager>();
|
||||
|
||||
var mapData = new TestMapData();
|
||||
TestMap = mapData;
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
mapData.MapId = Server.MapMan.CreateMap();
|
||||
mapData.MapUid = Server.MapMan.GetMapEntityId(mapData.MapId);
|
||||
var mapGrid = Server.MapMan.CreateGridEntity(mapData.MapId);
|
||||
mapData.MapGrid = mapGrid;
|
||||
mapData.GridUid = mapGrid.Owner; // Fixing this requires an engine PR.
|
||||
mapData.GridCoords = new EntityCoordinates(mapData.GridUid, 0, 0);
|
||||
var plating = tileDefinitionManager["Plating"];
|
||||
mapData.MapUid = Server.System<SharedMapSystem>().CreateMap(out mapData.MapId, runMapInit: initialized);
|
||||
mapData.Grid = Server.MapMan.CreateGridEntity(mapData.MapId);
|
||||
mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0);
|
||||
var plating = tileDefinitionManager[tile];
|
||||
var platingTile = new Tile(plating.TileId);
|
||||
mapData.MapGrid.SetTile(mapData.GridCoords, platingTile);
|
||||
mapData.Grid.Comp.SetTile(mapData.GridCoords, platingTile);
|
||||
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
|
||||
mapData.Tile = mapData.MapGrid.GetAllTiles().First();
|
||||
mapData.Tile = mapData.Grid.Comp.GetAllTiles().First();
|
||||
});
|
||||
|
||||
TestMap = mapData;
|
||||
if (!Settings.Connected)
|
||||
return mapData;
|
||||
|
||||
await RunTicksSync(10);
|
||||
mapData.CMapUid = ToClientUid(mapData.MapUid);
|
||||
mapData.CGridUid = ToClientUid(mapData.GridUid);
|
||||
mapData.CGridUid = ToClientUid(mapData.Grid);
|
||||
mapData.CGridCoords = new EntityCoordinates(mapData.CGridUid, 0, 0);
|
||||
|
||||
TestMap = mapData;
|
||||
return mapData;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
// Move to pre-round lobby. Required to toggle dummy ticker on and off
|
||||
if (gameTicker.RunLevel != GameRunLevel.PreRoundLobby)
|
||||
{
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting server.");
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting round.");
|
||||
Assert.That(gameTicker.DummyTicker, Is.False);
|
||||
Server.CfgMan.SetCVar(CCVars.GameLobbyEnabled, true);
|
||||
await Server.WaitPost(() => gameTicker.RestartRound());
|
||||
@@ -146,6 +146,7 @@ public sealed partial class TestPair : IAsyncDisposable
|
||||
|
||||
// Restart server.
|
||||
await testOut.WriteLineAsync($"Recycling: {Watch.Elapsed.TotalMilliseconds} ms: Restarting server again");
|
||||
await Server.WaitPost(() => Server.EntMan.FlushEntities());
|
||||
await Server.WaitPost(() => gameTicker.RestartRound());
|
||||
await RunTicksSync(1);
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ public sealed class AddTests
|
||||
|
||||
var guid = Guid.NewGuid();
|
||||
|
||||
var testMap = await pair.CreateTestMap();
|
||||
var coordinates = testMap.GridCoords;
|
||||
await pair.CreateTestMap();
|
||||
var coordinates = pair.TestMap.GridCoords;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var entity = sEntities.SpawnEntity(null, coordinates);
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace Content.IntegrationTests.Tests.DeviceNetwork
|
||||
DeviceNetworkComponent networkComponent1 = null;
|
||||
DeviceNetworkComponent networkComponent2 = null;
|
||||
WiredNetworkComponent wiredNetworkComponent = null;
|
||||
var grid = testMap.MapGrid;
|
||||
var grid = testMap.Grid.Comp;
|
||||
|
||||
var testValue = "test";
|
||||
var payload = new NetworkPayload
|
||||
|
||||
@@ -3,8 +3,6 @@ using Content.IntegrationTests.Tests.Construction.Interaction;
|
||||
using Content.IntegrationTests.Tests.Interaction;
|
||||
using Content.IntegrationTests.Tests.Weldable;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Content.Server.Tools.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.DoAfter;
|
||||
|
||||
|
||||
@@ -354,41 +354,18 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityManager = server.ResolveDependency<IEntityManager>();
|
||||
var componentFactory = server.ResolveDependency<IComponentFactory>();
|
||||
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var mapSystem = entityManager.System<SharedMapSystem>();
|
||||
var logmill = server.ResolveDependency<ILogManager>().GetSawmill("EntityTest");
|
||||
|
||||
Entity<MapGridComponent> grid = default!;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
// Create a one tile grid to stave off the grid 0 monsters
|
||||
var mapId = mapManager.CreateMap();
|
||||
|
||||
mapManager.AddUninitializedMap(mapId);
|
||||
|
||||
grid = mapManager.CreateGridEntity(mapId);
|
||||
|
||||
var tileDefinition = tileDefinitionManager["Plating"];
|
||||
var tile = new Tile(tileDefinition.TileId);
|
||||
var coordinates = new EntityCoordinates(grid.Owner, Vector2.Zero);
|
||||
|
||||
mapSystem.SetTile(grid.Owner, grid.Comp!, coordinates, tile);
|
||||
|
||||
mapManager.DoMapInitialize(mapId);
|
||||
});
|
||||
|
||||
await pair.CreateTestMap();
|
||||
await server.WaitRunTicks(5);
|
||||
var testLocation = pair.TestMap.GridCoords;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
var testLocation = new EntityCoordinates(grid.Owner, Vector2.Zero);
|
||||
|
||||
foreach (var type in componentFactory.AllRegisteredTypes)
|
||||
{
|
||||
|
||||
@@ -46,17 +46,14 @@ namespace Content.IntegrationTests.Tests.Fluids
|
||||
var server = pair.Server;
|
||||
|
||||
var testMap = await pair.CreateTestMap();
|
||||
var grid = testMap.Grid.Comp;
|
||||
|
||||
var entitySystemManager = server.ResolveDependency<IEntitySystemManager>();
|
||||
var spillSystem = entitySystemManager.GetEntitySystem<PuddleSystem>();
|
||||
|
||||
MapGridComponent grid = null;
|
||||
|
||||
// Remove all tiles
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
grid = testMap.MapGrid;
|
||||
|
||||
foreach (var tile in grid.GetAllTiles())
|
||||
{
|
||||
grid.SetTile(tile.GridIndices, Tile.Empty);
|
||||
|
||||
@@ -989,7 +989,7 @@ public abstract partial class InteractionTest
|
||||
/// </summary>
|
||||
protected async Task AddGravity(EntityUid? uid = null)
|
||||
{
|
||||
var target = uid ?? MapData.GridUid;
|
||||
var target = uid ?? MapData.Grid;
|
||||
await Server.WaitPost(() =>
|
||||
{
|
||||
var gravity = SEntMan.EnsureComponent<GravityComponent>(target);
|
||||
|
||||
@@ -184,7 +184,7 @@ public abstract partial class InteractionTest
|
||||
await Pair.CreateTestMap();
|
||||
PlayerCoords = SEntMan.GetNetCoordinates(MapData.GridCoords.Offset(new Vector2(0.5f, 0.5f)).WithEntityId(MapData.MapUid, Transform, SEntMan));
|
||||
TargetCoords = SEntMan.GetNetCoordinates(MapData.GridCoords.Offset(new Vector2(1.5f, 0.5f)).WithEntityId(MapData.MapUid, Transform, SEntMan));
|
||||
await SetTile(Plating, grid: MapData.MapGrid);
|
||||
await SetTile(Plating, grid: MapData.Grid.Comp);
|
||||
|
||||
// Get player data
|
||||
var sPlayerMan = Server.ResolveDependency<Robust.Server.Player.IPlayerManager>();
|
||||
|
||||
@@ -31,7 +31,7 @@ public abstract class MovementTest : InteractionTest
|
||||
|
||||
for (var i = -Tiles; i <= Tiles; i++)
|
||||
{
|
||||
await SetTile(Plating, SEntMan.GetNetCoordinates(pCoords.Offset(new Vector2(i, 0))), MapData.MapGrid);
|
||||
await SetTile(Plating, SEntMan.GetNetCoordinates(pCoords.Offset(new Vector2(i, 0))), MapData.Grid.Comp);
|
||||
}
|
||||
AssertGridCount(1);
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ public sealed class MaterialArbitrageTest
|
||||
var spawnedPrice = await GetSpawnedPrice(spawnedEnts);
|
||||
var price = await GetPrice(id);
|
||||
if (spawnedPrice > 0 && price > 0)
|
||||
Assert.That(spawnedPrice, Is.LessThanOrEqualTo(price), $"{id} increases in price after being destroyed");
|
||||
Assert.That(spawnedPrice, Is.LessThanOrEqualTo(price), $"{id} increases in price after being destroyed\nEntities spawned on destruction: {string.Join(',', spawnedEnts)}");
|
||||
|
||||
// Check lathe production
|
||||
if (latheRecipes.TryGetValue(id, out var recipe))
|
||||
@@ -359,7 +359,7 @@ public sealed class MaterialArbitrageTest
|
||||
{
|
||||
var ent = entManager.SpawnEntity(id, testMap.GridCoords);
|
||||
stackSys.SetCount(ent, 1);
|
||||
priceCache[id] = price = pricing.GetPrice(ent);
|
||||
priceCache[id] = price = pricing.GetPrice(ent, false);
|
||||
entManager.DeleteEntity(ent);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ namespace Content.IntegrationTests.Tests
|
||||
"MeteorArena",
|
||||
//"Atlas",
|
||||
//"Reach",
|
||||
//"Train"
|
||||
//"Train",
|
||||
//"Oasis"
|
||||
|
||||
//CrystallPunk maps
|
||||
"CaveArena",
|
||||
@@ -158,7 +159,10 @@ namespace Content.IntegrationTests.Tests
|
||||
[Test, TestCaseSource(nameof(GameMaps))]
|
||||
public async Task GameMapsLoadableTest(string mapProto)
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||
{
|
||||
Dirty = true // Stations spawn a bunch of nullspace entities and maps like centcomm.
|
||||
});
|
||||
var server = pair.Server;
|
||||
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
|
||||
@@ -38,31 +38,15 @@ public sealed class PrototypeSaveTest
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var entityMan = server.ResolveDependency<IEntityManager>();
|
||||
var prototypeMan = server.ResolveDependency<IPrototypeManager>();
|
||||
var tileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
||||
var seriMan = server.ResolveDependency<ISerializationManager>();
|
||||
var compFact = server.ResolveDependency<IComponentFactory>();
|
||||
|
||||
var prototypes = new List<EntityPrototype>();
|
||||
MapGridComponent grid = default!;
|
||||
EntityUid uid;
|
||||
MapId mapId = default;
|
||||
|
||||
//Build up test environment
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
// Create a one tile grid to stave off the grid 0 monsters
|
||||
mapId = mapManager.CreateMap();
|
||||
|
||||
mapManager.AddUninitializedMap(mapId);
|
||||
|
||||
grid = mapManager.CreateGrid(mapId);
|
||||
|
||||
var tileDefinition = tileDefinitionManager["FloorSteel"]; // Wires n such disable ambiance while under the floor
|
||||
var tile = new Tile(tileDefinition.TileId);
|
||||
var coordinates = grid.Owner.ToCoordinates();
|
||||
|
||||
grid.SetTile(coordinates, tile);
|
||||
});
|
||||
await pair.CreateTestMap(false, "FloorSteel"); // Wires n such disable ambiance while under the floor
|
||||
var mapId = pair.TestMap.MapId;
|
||||
var grid = pair.TestMap.Grid;
|
||||
|
||||
await server.WaitRunTicks(5);
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class DockTest : ContentUnitTest
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
entManager.DeleteEntity(map.GridUid);
|
||||
entManager.DeleteEntity(map.Grid);
|
||||
var grid1 = mapManager.CreateGridEntity(mapId);
|
||||
var grid2 = mapManager.CreateGridEntity(mapId);
|
||||
var grid1Ent = grid1.Owner;
|
||||
@@ -104,7 +104,7 @@ public sealed class DockTest : ContentUnitTest
|
||||
// Spawn shuttle and affirm no valid docks.
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
entManager.DeleteEntity(map.GridUid);
|
||||
entManager.DeleteEntity(map.Grid);
|
||||
Assert.That(entManager.System<MapLoaderSystem>().TryLoad(otherMap.MapId, "/Maps/Shuttles/emergency.yml", out var rootUids));
|
||||
shuttle = rootUids[0];
|
||||
|
||||
|
||||
114
Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs
Normal file
114
Content.IntegrationTests/Tests/Station/EvacShuttleTest.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Linq;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Shuttles.Components;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Server.Station.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Station;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(EmergencyShuttleSystem))]
|
||||
public sealed class EvacShuttleTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensure that the emergency shuttle can be called, and that it will travel to centcomm
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task EmergencyEvacTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient(new PoolSettings { DummyTicker = true, Dirty = true });
|
||||
var server = pair.Server;
|
||||
var entMan = server.EntMan;
|
||||
var ticker = server.System<GameTicker>();
|
||||
|
||||
// Dummy ticker tests should not have centcomm
|
||||
Assert.That(entMan.Count<StationCentcommComponent>(), Is.Zero);
|
||||
|
||||
var shuttleEnabled = pair.Server.CfgMan.GetCVar(CCVars.EmergencyShuttleEnabled);
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameMap, "Saltern");
|
||||
pair.Server.CfgMan.SetCVar(CCVars.GameDummyTicker, false);
|
||||
pair.Server.CfgMan.SetCVar(CCVars.EmergencyShuttleEnabled, true);
|
||||
|
||||
await server.WaitPost(() => ticker.RestartRound());
|
||||
await pair.RunTicksSync(25);
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound));
|
||||
|
||||
// Find the station, centcomm, and shuttle, and ftl map.
|
||||
|
||||
Assert.That(entMan.Count<StationCentcommComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<StationEmergencyShuttleComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<StationDataComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<EmergencyShuttleComponent>(), Is.EqualTo(1));
|
||||
Assert.That(entMan.Count<FTLMapComponent>(), Is.EqualTo(0));
|
||||
|
||||
var station = (Entity<StationCentcommComponent>) entMan.AllComponentsList<StationCentcommComponent>().Single();
|
||||
var data = entMan.GetComponent<StationDataComponent>(station);
|
||||
var shuttleData = entMan.GetComponent<StationEmergencyShuttleComponent>(station);
|
||||
|
||||
var saltern = data.Grids.Single();
|
||||
Assert.That(entMan.HasComponent<MapGridComponent>(saltern));
|
||||
|
||||
var shuttle = shuttleData.EmergencyShuttle!.Value;
|
||||
Assert.That(entMan.HasComponent<EmergencyShuttleComponent>(shuttle));
|
||||
Assert.That(entMan.HasComponent<MapGridComponent>(shuttle));
|
||||
|
||||
var centcomm = station.Comp.Entity!.Value;
|
||||
Assert.That(entMan.HasComponent<MapGridComponent>(centcomm));
|
||||
|
||||
var centcommMap = station.Comp.MapEntity!.Value;
|
||||
Assert.That(entMan.HasComponent<MapComponent>(centcommMap));
|
||||
Assert.That(server.Transform(centcomm).MapUid, Is.EqualTo(centcommMap));
|
||||
|
||||
var salternXform = server.Transform(saltern);
|
||||
Assert.That(salternXform.MapUid, Is.Not.Null);
|
||||
Assert.That(salternXform.MapUid, Is.Not.EqualTo(centcommMap));
|
||||
|
||||
var shuttleXform = server.Transform(shuttle);
|
||||
Assert.That(shuttleXform.MapUid, Is.Not.Null);
|
||||
Assert.That(shuttleXform.MapUid, Is.EqualTo(centcommMap));
|
||||
|
||||
// Set up shuttle timing
|
||||
var evacSys = server.System<EmergencyShuttleSystem>();
|
||||
evacSys.TransitTime = ShuttleSystem.DefaultTravelTime; // Absolute minimum transit time, so the test has to run for at least this long
|
||||
// TODO SHUTTLE fix spaghetti
|
||||
|
||||
var dockTime = server.CfgMan.GetCVar(CCVars.EmergencyShuttleDockTime);
|
||||
server.CfgMan.SetCVar(CCVars.EmergencyShuttleDockTime, 2);
|
||||
async Task RunSeconds(float seconds)
|
||||
{
|
||||
await pair.RunTicksSync((int) Math.Ceiling(seconds / server.Timing.TickPeriod.TotalSeconds));
|
||||
}
|
||||
|
||||
// Call evac shuttle.
|
||||
await pair.WaitCommand("callshuttle 0:02");
|
||||
await RunSeconds(3);
|
||||
|
||||
// Shuttle should have arrived on the station
|
||||
Assert.That(shuttleXform.MapUid, Is.EqualTo(salternXform.MapUid));
|
||||
|
||||
await RunSeconds(2);
|
||||
|
||||
// Shuttle should be FTLing back to centcomm
|
||||
Assert.That(entMan.Count<FTLMapComponent>(), Is.EqualTo(1));
|
||||
var ftl = (Entity<FTLMapComponent>) entMan.AllComponentsList<FTLMapComponent>().Single();
|
||||
Assert.That(entMan.HasComponent<MapComponent>(ftl));
|
||||
Assert.That(ftl.Owner, Is.Not.EqualTo(centcommMap));
|
||||
Assert.That(ftl.Owner, Is.Not.EqualTo(salternXform.MapUid));
|
||||
Assert.That(shuttleXform.MapUid, Is.EqualTo(ftl.Owner));
|
||||
|
||||
// Shuttle should have arrived at centcomm
|
||||
await RunSeconds(ShuttleSystem.DefaultTravelTime);
|
||||
Assert.That(shuttleXform.MapUid, Is.EqualTo(centcommMap));
|
||||
|
||||
// Round should be ending now
|
||||
Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PostRound));
|
||||
|
||||
server.CfgMan.SetCVar(CCVars.EmergencyShuttleDockTime, dockTime);
|
||||
pair.Server.CfgMan.SetCVar(CCVars.EmergencyShuttleEnabled, shuttleEnabled);
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ public sealed class TileConstructionTests : InteractionTest
|
||||
// Remove grid
|
||||
await SetTile(null);
|
||||
await SetTile(null, PlayerCoords);
|
||||
Assert.That(MapData.MapGrid.Deleted);
|
||||
Assert.That(MapData.Grid.Comp.Deleted);
|
||||
AssertGridCount(0);
|
||||
|
||||
// Place Lattice
|
||||
@@ -70,7 +70,7 @@ public sealed class TileConstructionTests : InteractionTest
|
||||
// Remove grid
|
||||
await SetTile(null);
|
||||
await SetTile(null, PlayerCoords);
|
||||
Assert.That(MapData.MapGrid.Deleted);
|
||||
Assert.That(MapData.Grid.Comp.Deleted);
|
||||
AssertGridCount(0);
|
||||
|
||||
// Space -> Lattice
|
||||
|
||||
@@ -111,7 +111,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
Text = Loc.GetString("admin-verb-text-make-thief"),
|
||||
Category = VerbCategory.Antag,
|
||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/ihscombat.rsi"), "icon"),
|
||||
Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/Color/black.rsi"), "icon"),
|
||||
Act = () =>
|
||||
{
|
||||
_thief.AdminMakeThief(args.Target, false); //Midround add pacified is bad
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Dependency] private readonly ExplosionSystem _explosionSystem = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -59,12 +60,14 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
|
||||
var gridId = xform.GridUid;
|
||||
var coords = xform.Coordinates;
|
||||
|
||||
var tilePos = grid.TileIndicesFor(coords);
|
||||
var tilePos = _mapSystem.TileIndicesFor(gridId.Value, grid, coords);
|
||||
|
||||
// Update and invalidate new position.
|
||||
airtight.LastPosition = (gridId.Value, tilePos);
|
||||
InvalidatePosition(gridId.Value, tilePos);
|
||||
|
||||
var airtightEv = new AirtightChanged(uid, airtight, (gridId.Value, tilePos));
|
||||
RaiseLocalEvent(uid, ref airtightEv, true);
|
||||
}
|
||||
|
||||
private void OnAirtightReAnchor(EntityUid uid, AirtightComponent airtight, ref ReAnchorEvent args)
|
||||
@@ -74,6 +77,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// Update and invalidate new position.
|
||||
airtight.LastPosition = (gridId, args.TilePos);
|
||||
InvalidatePosition(gridId, args.TilePos);
|
||||
|
||||
var airtightEv = new AirtightChanged(uid, airtight, (gridId, args.TilePos));
|
||||
RaiseLocalEvent(uid, ref airtightEv, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +159,5 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight,
|
||||
(EntityUid Grid, Vector2i Tile) Position);
|
||||
public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight, (EntityUid Grid, Vector2i Tile) Position);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Administration;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -84,44 +85,72 @@ public sealed partial class AtmosphereSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
var transform = Transform(euid.Value);
|
||||
// Force Invalidate & update air on all tiles
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> grid =
|
||||
new(euid.Value, gridAtmosphere, Comp<GasTileOverlayComponent>(euid.Value), gridComp, Transform(euid.Value));
|
||||
|
||||
foreach (var (indices, tileMain) in gridAtmosphere.Tiles)
|
||||
RebuildGridTiles(grid);
|
||||
|
||||
var query = GetEntityQuery<AtmosFixMarkerComponent>();
|
||||
foreach (var (indices, tile) in gridAtmosphere.Tiles.ToArray())
|
||||
{
|
||||
var tile = tileMain.Air;
|
||||
if (tile == null)
|
||||
if (tile.Air is not {Immutable: false} air)
|
||||
continue;
|
||||
|
||||
if (!_mapSystem.TryGetTile(gridComp, indices, out var gTile) || gTile.IsEmpty)
|
||||
{
|
||||
gridAtmosphere.Tiles.Remove(indices);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tile.Immutable && !IsTileSpace(euid, transform.MapUid, indices))
|
||||
{
|
||||
tile = new GasMixture(tile.Volume) { Temperature = tile.Temperature };
|
||||
tileMain.Air = tile;
|
||||
}
|
||||
|
||||
tile.Clear();
|
||||
air.Clear();
|
||||
var mixtureId = 0;
|
||||
foreach (var entUid in gridComp.GetAnchoredEntities(indices))
|
||||
var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(grid, grid, indices);
|
||||
while (enumerator.MoveNext(out var entUid))
|
||||
{
|
||||
if (!TryComp(entUid, out AtmosFixMarkerComponent? afm))
|
||||
continue;
|
||||
mixtureId = afm.Mode;
|
||||
break;
|
||||
if (query.TryComp(entUid, out var marker))
|
||||
mixtureId = marker.Mode;
|
||||
}
|
||||
var mixture = mixtures[mixtureId];
|
||||
Merge(tile, mixture);
|
||||
tile.Temperature = mixture.Temperature;
|
||||
|
||||
gridAtmosphere.InvalidatedCoords.Add(indices);
|
||||
var mixture = mixtures[mixtureId];
|
||||
Merge(air, mixture);
|
||||
air.Temperature = mixture.Temperature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears & re-creates all references to <see cref="TileAtmosphere"/>s stored on a grid.
|
||||
/// </summary>
|
||||
private void RebuildGridTiles(
|
||||
Entity<GridAtmosphereComponent, GasTileOverlayComponent, MapGridComponent, TransformComponent> ent)
|
||||
{
|
||||
foreach (var indices in ent.Comp1.Tiles.Keys)
|
||||
{
|
||||
InvalidateVisuals((ent, ent), indices);
|
||||
}
|
||||
|
||||
var atmos = ent.Comp1;
|
||||
atmos.MapTiles.Clear();
|
||||
atmos.ActiveTiles.Clear();
|
||||
atmos.ExcitedGroups.Clear();
|
||||
atmos.HotspotTiles.Clear();
|
||||
atmos.SuperconductivityTiles.Clear();
|
||||
atmos.HighPressureDelta.Clear();
|
||||
atmos.CurrentRunTiles.Clear();
|
||||
atmos.CurrentRunExcitedGroups.Clear();
|
||||
atmos.InvalidatedCoords.Clear();
|
||||
atmos.CurrentRunInvalidatedTiles.Clear();
|
||||
atmos.PossiblyDisconnectedTiles.Clear();
|
||||
atmos.Tiles.Clear();
|
||||
|
||||
var volume = GetVolumeForTiles(ent);
|
||||
TryComp(ent.Comp4.MapUid, out MapAtmosphereComponent? mapAtmos);
|
||||
|
||||
var enumerator = _map.GetAllTilesEnumerator(ent, ent);
|
||||
while (enumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
var tile = GetOrNewTile(ent, ent, tileRef.Value.GridIndices);
|
||||
UpdateTileData(ent, mapAtmos, tile);
|
||||
UpdateAdjacentTiles(ent, tile, activate: true);
|
||||
UpdateTileAir(ent, tile, volume);
|
||||
}
|
||||
}
|
||||
|
||||
private CompletionResult FixGridAtmosCommandCompletions(IConsoleShell shell, string[] args)
|
||||
{
|
||||
MapId? playerMap = null;
|
||||
|
||||
@@ -30,13 +30,15 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
private int _currentRunAtmosphereIndex;
|
||||
private bool _simulationPaused;
|
||||
|
||||
private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index)
|
||||
private TileAtmosphere GetOrNewTile(EntityUid owner, GridAtmosphereComponent atmosphere, Vector2i index, bool invalidateNew = true)
|
||||
{
|
||||
var tile = atmosphere.Tiles.GetOrNew(index, out var existing);
|
||||
if (existing)
|
||||
return tile;
|
||||
|
||||
atmosphere.InvalidatedCoords.Add(index);
|
||||
if (invalidateNew)
|
||||
atmosphere.InvalidatedCoords.Add(index);
|
||||
|
||||
tile.GridIndex = owner;
|
||||
tile.GridIndices = index;
|
||||
return tile;
|
||||
@@ -68,7 +70,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
atmosphere.CurrentRunInvalidatedTiles.EnsureCapacity(atmosphere.InvalidatedCoords.Count);
|
||||
foreach (var indices in atmosphere.InvalidatedCoords)
|
||||
{
|
||||
var tile = GetOrNewTile(uid, atmosphere, indices);
|
||||
var tile = GetOrNewTile(uid, atmosphere, indices, invalidateNew: false);
|
||||
atmosphere.CurrentRunInvalidatedTiles.Enqueue(tile);
|
||||
|
||||
// Update tile.IsSpace and tile.MapAtmosphere, and tile.AirtightData.
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
private void OnDisabledMessage(EntityUid uid, GasAnalyzerComponent component, GasAnalyzerDisableMessage message)
|
||||
{
|
||||
if (message.Session.AttachedEntity is not {Valid: true})
|
||||
if (message.Session.AttachedEntity is not { Valid: true })
|
||||
return;
|
||||
DisableAnalyzer(uid, component);
|
||||
}
|
||||
@@ -169,7 +169,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// Check if position is out of range => don't update and disable
|
||||
if (!component.LastPosition.Value.InRange(EntityManager, _transform, userPos, SharedInteractionSystem.InteractionRange))
|
||||
{
|
||||
if(component.User is { } userId && component.Enabled)
|
||||
if (component.User is { } userId && component.Enabled)
|
||||
_popup.PopupEntity(Loc.GetString("gas-analyzer-shutoff"), userId, userId);
|
||||
DisableAnalyzer(uid, component, component.User);
|
||||
return false;
|
||||
@@ -182,13 +182,13 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
var tileMixture = _atmo.GetContainingMixture(uid, true);
|
||||
if (tileMixture != null)
|
||||
{
|
||||
gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), tileMixture.Pressure, tileMixture.Temperature,
|
||||
gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), tileMixture.Volume, tileMixture.Pressure, tileMixture.Temperature,
|
||||
GenerateGasEntryArray(tileMixture)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// No gases were found
|
||||
gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), 0f, 0f));
|
||||
gasMixList.Add(new GasMixEntry(Loc.GetString("gas-analyzer-window-environment-tab-label"), 0f, 0f, 0f));
|
||||
}
|
||||
|
||||
var deviceFlipped = false;
|
||||
@@ -209,8 +209,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
foreach (var mixes in ev.GasMixtures)
|
||||
{
|
||||
if(mixes.Value != null)
|
||||
gasMixList.Add(new GasMixEntry(mixes.Key, mixes.Value.Pressure, mixes.Value.Temperature, GenerateGasEntryArray(mixes.Value)));
|
||||
if (mixes.Item2 != null)
|
||||
gasMixList.Add(new GasMixEntry(mixes.Item1, mixes.Item2.Volume, mixes.Item2.Pressure, mixes.Item2.Temperature, GenerateGasEntryArray(mixes.Item2)));
|
||||
}
|
||||
|
||||
deviceFlipped = ev.DeviceFlipped;
|
||||
@@ -223,7 +223,16 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
foreach (var pair in node.Nodes)
|
||||
{
|
||||
if (pair.Value is PipeNode pipeNode)
|
||||
gasMixList.Add(new GasMixEntry(pair.Key, pipeNode.Air.Pressure, pipeNode.Air.Temperature, GenerateGasEntryArray(pipeNode.Air)));
|
||||
{
|
||||
// check if the volume is zero for some reason so we don't divide by zero
|
||||
if (pipeNode.Air.Volume == 0f)
|
||||
continue;
|
||||
// only display the gas in the analyzed pipe element, not the whole system
|
||||
var pipeAir = pipeNode.Air.Clone();
|
||||
pipeAir.Multiply(pipeNode.Volume / pipeNode.Air.Volume);
|
||||
pipeAir.Volume = pipeNode.Volume;
|
||||
gasMixList.Add(new GasMixEntry(pair.Key, pipeAir.Volume, pipeAir.Pressure, pipeAir.Temperature, GenerateGasEntryArray(pipeAir)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,9 +295,9 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public sealed class GasAnalyzerScanEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Key is the mix name (ex "pipe", "inlet", "filter"), value is the pipe direction and GasMixture. Add all mixes that should be reported when scanned.
|
||||
/// The string is for the name (ex "pipe", "inlet", "filter"), GasMixture for the corresponding gas mix. Add all mixes that should be reported when scanned.
|
||||
/// </summary>
|
||||
public Dictionary<string, GasMixture?>? GasMixtures;
|
||||
public List<(string, GasMixture?)>? GasMixtures;
|
||||
|
||||
/// <summary>
|
||||
/// If the device is flipped. Flipped is defined as when the inline input is 90 degrees CW to the side input
|
||||
|
||||
@@ -359,7 +359,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
/// </summary>
|
||||
private void OnAnalyzed(EntityUid uid, GasTankComponent component, GasAnalyzerScanEvent args)
|
||||
{
|
||||
args.GasMixtures = new Dictionary<string, GasMixture?> { {Name(uid), component.Air} };
|
||||
args.GasMixtures ??= new List<(string, GasMixture?)>();
|
||||
args.GasMixtures.Add((Name(uid), component.Air));
|
||||
}
|
||||
|
||||
private void OnGasTankPrice(EntityUid uid, GasTankComponent component, ref PriceCalculationEvent args)
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
||||
|
||||
if (filter.FilteredGas.HasValue)
|
||||
{
|
||||
var filteredOut = new GasMixture() {Temperature = removed.Temperature};
|
||||
var filteredOut = new GasMixture() { Temperature = removed.Temperature };
|
||||
|
||||
filteredOut.SetMoles(filter.FilteredGas.Value, removed.GetMoles(filter.FilteredGas.Value));
|
||||
removed.SetMoles(filter.FilteredGas.Value, 0f);
|
||||
@@ -180,17 +180,30 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
||||
/// </summary>
|
||||
private void OnFilterAnalyzed(EntityUid uid, GasFilterComponent component, GasAnalyzerScanEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
args.GasMixtures ??= new List<(string, GasMixture?)>();
|
||||
|
||||
args.GasMixtures ??= new Dictionary<string, GasMixture?>();
|
||||
|
||||
if(_nodeContainer.TryGetNode(nodeContainer, component.InletName, out PipeNode? inlet))
|
||||
args.GasMixtures.Add(Loc.GetString("gas-analyzer-window-text-inlet"), inlet.Air);
|
||||
if(_nodeContainer.TryGetNode(nodeContainer, component.FilterName, out PipeNode? filterNode))
|
||||
args.GasMixtures.Add(Loc.GetString("gas-analyzer-window-text-filter"), filterNode.Air);
|
||||
if(_nodeContainer.TryGetNode(nodeContainer, component.OutletName, out PipeNode? outlet))
|
||||
args.GasMixtures.Add(Loc.GetString("gas-analyzer-window-text-outlet"), outlet.Air);
|
||||
// multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system
|
||||
if (_nodeContainer.TryGetNode(uid, component.InletName, out PipeNode? inlet) && inlet.Air.Volume != 0f)
|
||||
{
|
||||
var inletAirLocal = inlet.Air.Clone();
|
||||
inletAirLocal.Multiply(inlet.Volume / inlet.Air.Volume);
|
||||
inletAirLocal.Volume = inlet.Volume;
|
||||
args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-inlet"), inletAirLocal));
|
||||
}
|
||||
if (_nodeContainer.TryGetNode(uid, component.FilterName, out PipeNode? filterNode) && filterNode.Air.Volume != 0f)
|
||||
{
|
||||
var filterNodeAirLocal = filterNode.Air.Clone();
|
||||
filterNodeAirLocal.Multiply(filterNode.Volume / filterNode.Air.Volume);
|
||||
filterNodeAirLocal.Volume = filterNode.Volume;
|
||||
args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-filter"), filterNodeAirLocal));
|
||||
}
|
||||
if (_nodeContainer.TryGetNode(uid, component.OutletName, out PipeNode? outlet) && outlet.Air.Volume != 0f)
|
||||
{
|
||||
var outletAirLocal = outlet.Air.Clone();
|
||||
outletAirLocal.Multiply(outlet.Volume / outlet.Air.Volume);
|
||||
outletAirLocal.Volume = outlet.Volume;
|
||||
args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-outlet"), outletAirLocal));
|
||||
}
|
||||
|
||||
args.DeviceFlipped = inlet != null && filterNode != null && inlet.CurrentPipeDirection.ToDirection() == filterNode.CurrentPipeDirection.ToDirection().GetClockwise90Degrees();
|
||||
}
|
||||
|
||||
@@ -205,19 +205,31 @@ namespace Content.Server.Atmos.Piping.Trinary.EntitySystems
|
||||
/// </summary>
|
||||
private void OnMixerAnalyzed(EntityUid uid, GasMixerComponent component, GasAnalyzerScanEvent args)
|
||||
{
|
||||
if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer))
|
||||
return;
|
||||
args.GasMixtures ??= new List<(string, GasMixture?)>();
|
||||
|
||||
var gasMixDict = new Dictionary<string, GasMixture?>();
|
||||
// multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system
|
||||
if (_nodeContainer.TryGetNode(uid, component.InletOneName, out PipeNode? inletOne) && inletOne.Air.Volume != 0f)
|
||||
{
|
||||
var inletOneAirLocal = inletOne.Air.Clone();
|
||||
inletOneAirLocal.Multiply(inletOne.Volume / inletOne.Air.Volume);
|
||||
inletOneAirLocal.Volume = inletOne.Volume;
|
||||
args.GasMixtures.Add(($"{inletOne.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletOneAirLocal));
|
||||
}
|
||||
if (_nodeContainer.TryGetNode(uid, component.InletTwoName, out PipeNode? inletTwo) && inletTwo.Air.Volume != 0f)
|
||||
{
|
||||
var inletTwoAirLocal = inletTwo.Air.Clone();
|
||||
inletTwoAirLocal.Multiply(inletTwo.Volume / inletTwo.Air.Volume);
|
||||
inletTwoAirLocal.Volume = inletTwo.Volume;
|
||||
args.GasMixtures.Add(($"{inletTwo.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletTwoAirLocal));
|
||||
}
|
||||
if (_nodeContainer.TryGetNode(uid, component.OutletName, out PipeNode? outlet) && outlet.Air.Volume != 0f)
|
||||
{
|
||||
var outletAirLocal = outlet.Air.Clone();
|
||||
outletAirLocal.Multiply(outlet.Volume / outlet.Air.Volume);
|
||||
outletAirLocal.Volume = outlet.Volume;
|
||||
args.GasMixtures.Add((Loc.GetString("gas-analyzer-window-text-outlet"), outletAirLocal));
|
||||
}
|
||||
|
||||
if(_nodeContainer.TryGetNode(nodeContainer, component.InletOneName, out PipeNode? inletOne))
|
||||
gasMixDict.Add($"{inletOne.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletOne.Air);
|
||||
if(_nodeContainer.TryGetNode(nodeContainer, component.InletTwoName, out PipeNode? inletTwo))
|
||||
gasMixDict.Add($"{inletTwo.CurrentPipeDirection} {Loc.GetString("gas-analyzer-window-text-inlet")}", inletTwo.Air);
|
||||
if(_nodeContainer.TryGetNode(nodeContainer, component.OutletName, out PipeNode? outlet))
|
||||
gasMixDict.Add(Loc.GetString("gas-analyzer-window-text-outlet"), outlet.Air);
|
||||
|
||||
args.GasMixtures = gasMixDict;
|
||||
args.DeviceFlipped = inletOne != null && inletTwo != null && inletOne.CurrentPipeDirection.ToDirection() == inletTwo.CurrentPipeDirection.ToDirection().GetClockwise90Degrees();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,9 +294,17 @@ public sealed class GasCanisterSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Returns the gas mixture for the gas analyzer
|
||||
/// </summary>
|
||||
private void OnAnalyzed(EntityUid uid, GasCanisterComponent component, GasAnalyzerScanEvent args)
|
||||
private void OnAnalyzed(EntityUid uid, GasCanisterComponent canisterComponent, GasAnalyzerScanEvent args)
|
||||
{
|
||||
args.GasMixtures = new Dictionary<string, GasMixture?> { {Name(uid), component.Air} };
|
||||
args.GasMixtures ??= new List<(string, GasMixture?)>();
|
||||
args.GasMixtures.Add((Name(uid), canisterComponent.Air));
|
||||
// if a tank is inserted show it on the analyzer as well
|
||||
if (canisterComponent.GasTankSlot.Item != null)
|
||||
{
|
||||
var tank = canisterComponent.GasTankSlot.Item.Value;
|
||||
var tankComponent = Comp<GasTankComponent>(tank);
|
||||
args.GasMixtures.Add((Name(tank), tankComponent.Air));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
return;
|
||||
}
|
||||
|
||||
var timeDelta = args.dt;
|
||||
var timeDelta = args.dt;
|
||||
var pressureDelta = timeDelta * vent.TargetPressureChange;
|
||||
|
||||
if (vent.PumpDirection == VentPumpDirection.Releasing && pipe.Air.Pressure > 0)
|
||||
@@ -292,7 +292,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
/// </summary>
|
||||
private void OnAnalyzed(EntityUid uid, GasVentPumpComponent component, GasAnalyzerScanEvent args)
|
||||
{
|
||||
var gasMixDict = new Dictionary<string, GasMixture?>();
|
||||
args.GasMixtures ??= new List<(string, GasMixture?)>();
|
||||
|
||||
// these are both called pipe, above it switches using this so I duplicated that...?
|
||||
var nodeName = component.PumpDirection switch
|
||||
@@ -301,10 +301,14 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
VentPumpDirection.Siphoning => component.Outlet,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
if (_nodeContainer.TryGetNode(uid, nodeName, out PipeNode? pipe))
|
||||
gasMixDict.Add(nodeName, pipe.Air);
|
||||
|
||||
args.GasMixtures = gasMixDict;
|
||||
// multiply by volume fraction to make sure to send only the gas inside the analyzed pipe element, not the whole pipe system
|
||||
if (_nodeContainer.TryGetNode(uid, nodeName, out PipeNode? pipe) && pipe.Air.Volume != 0f)
|
||||
{
|
||||
var pipeAirLocal = pipe.Air.Clone();
|
||||
pipeAirLocal.Multiply(pipe.Volume / pipe.Air.Volume);
|
||||
pipeAirLocal.Volume = pipe.Volume;
|
||||
args.GasMixtures.Add((nodeName, pipeAirLocal));
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWeldChanged(EntityUid uid, GasVentPumpComponent component, ref WeldableChangedEvent args)
|
||||
|
||||
@@ -151,10 +151,8 @@ namespace Content.Server.Atmos.Portable
|
||||
/// </summary>
|
||||
private void OnScrubberAnalyzed(EntityUid uid, PortableScrubberComponent component, GasAnalyzerScanEvent args)
|
||||
{
|
||||
args.GasMixtures ??= new Dictionary<string, GasMixture?> { { Name(uid), component.Air } };
|
||||
// If it's connected to a port, include the port side
|
||||
if (_nodeContainer.TryGetNode(uid, component.PortName, out PipeNode? port))
|
||||
args.GasMixtures.Add(component.PortName, port.Air);
|
||||
args.GasMixtures ??= new List<(string, GasMixture?)>();
|
||||
args.GasMixtures.Add((Name(uid), component.Air));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
152
Content.Server/Audio/Jukebox/JukeboxSystem.cs
Normal file
152
Content.Server/Audio/Jukebox/JukeboxSystem.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Audio.Jukebox;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using JukeboxComponent = Content.Shared.Audio.Jukebox.JukeboxComponent;
|
||||
|
||||
namespace Content.Server.Audio.Jukebox;
|
||||
|
||||
|
||||
public sealed class JukeboxSystem : SharedJukeboxSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxSelectedMessage>(OnJukeboxSelected);
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxPlayingMessage>(OnJukeboxPlay);
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxPauseMessage>(OnJukeboxPause);
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxStopMessage>(OnJukeboxStop);
|
||||
SubscribeLocalEvent<JukeboxComponent, JukeboxSetTimeMessage>(OnJukeboxSetTime);
|
||||
SubscribeLocalEvent<JukeboxComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<JukeboxComponent, ComponentShutdown>(OnComponentShutdown);
|
||||
|
||||
SubscribeLocalEvent<JukeboxComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, JukeboxComponent component, ComponentInit args)
|
||||
{
|
||||
if (HasComp<ApcPowerReceiverComponent>(uid))
|
||||
{
|
||||
TryUpdateVisualState(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJukeboxPlay(EntityUid uid, JukeboxComponent component, ref JukeboxPlayingMessage args)
|
||||
{
|
||||
if (Exists(component.AudioStream))
|
||||
{
|
||||
Audio.SetState(component.AudioStream, AudioState.Playing);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.AudioStream = Audio.Stop(component.AudioStream);
|
||||
|
||||
if (string.IsNullOrEmpty(component.SelectedSongId) ||
|
||||
!_protoManager.TryIndex(component.SelectedSongId, out var jukeboxProto))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
component.AudioStream = Audio.PlayPvs(jukeboxProto.Path, uid, AudioParams.Default.WithMaxDistance(10f))?.Entity;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJukeboxPause(Entity<JukeboxComponent> ent, ref JukeboxPauseMessage args)
|
||||
{
|
||||
Audio.SetState(ent.Comp.AudioStream, AudioState.Paused);
|
||||
}
|
||||
|
||||
private void OnJukeboxSetTime(EntityUid uid, JukeboxComponent component, JukeboxSetTimeMessage args)
|
||||
{
|
||||
var offset = (args.Session.Channel.Ping * 1.5f) / 1000f;
|
||||
Audio.SetPlaybackPosition(component.AudioStream, args.SongTime + offset);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(Entity<JukeboxComponent> entity, ref PowerChangedEvent args)
|
||||
{
|
||||
TryUpdateVisualState(entity);
|
||||
|
||||
if (!this.IsPowered(entity.Owner, EntityManager))
|
||||
{
|
||||
Stop(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnJukeboxStop(Entity<JukeboxComponent> entity, ref JukeboxStopMessage args)
|
||||
{
|
||||
Stop(entity);
|
||||
}
|
||||
|
||||
private void Stop(Entity<JukeboxComponent> entity)
|
||||
{
|
||||
Audio.SetState(entity.Comp.AudioStream, AudioState.Stopped);
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
private void OnJukeboxSelected(EntityUid uid, JukeboxComponent component, JukeboxSelectedMessage args)
|
||||
{
|
||||
if (!Audio.IsPlaying(component.AudioStream))
|
||||
{
|
||||
component.SelectedSongId = args.SongId;
|
||||
DirectSetVisualState(uid, JukeboxVisualState.Select);
|
||||
component.Selecting = true;
|
||||
component.AudioStream = Audio.Stop(component.AudioStream);
|
||||
}
|
||||
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<JukeboxComponent>();
|
||||
while (query.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
if (comp.Selecting)
|
||||
{
|
||||
comp.SelectAccumulator += frameTime;
|
||||
if (comp.SelectAccumulator >= 0.5f)
|
||||
{
|
||||
comp.SelectAccumulator = 0f;
|
||||
comp.Selecting = false;
|
||||
|
||||
TryUpdateVisualState(uid, comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnComponentShutdown(EntityUid uid, JukeboxComponent component, ComponentShutdown args)
|
||||
{
|
||||
component.AudioStream = Audio.Stop(component.AudioStream);
|
||||
}
|
||||
|
||||
private void DirectSetVisualState(EntityUid uid, JukeboxVisualState state)
|
||||
{
|
||||
_appearanceSystem.SetData(uid, JukeboxVisuals.VisualState, state);
|
||||
}
|
||||
|
||||
private void TryUpdateVisualState(EntityUid uid, JukeboxComponent? jukeboxComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref jukeboxComponent))
|
||||
return;
|
||||
|
||||
var finalState = JukeboxVisualState.On;
|
||||
|
||||
if (!this.IsPowered(uid, EntityManager))
|
||||
{
|
||||
finalState = JukeboxVisualState.Off;
|
||||
}
|
||||
|
||||
_appearanceSystem.SetData(uid, JukeboxVisuals.VisualState, finalState);
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,16 @@ using Content.Server.Botany.Components;
|
||||
using Content.Server.Botany.Systems;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Server.Botany;
|
||||
|
||||
[Prototype("seed")]
|
||||
public sealed class SeedPrototype : SeedData, IPrototype
|
||||
public sealed partial class SeedPrototype : SeedData, IPrototype
|
||||
{
|
||||
[IdDataField] public string ID { get; private init; } = default!;
|
||||
}
|
||||
|
||||
@@ -184,6 +184,15 @@ namespace Content.Server.Cargo.Systems
|
||||
order.SetApproverData(idCard.Comp?.FullName, idCard.Comp?.JobTitle);
|
||||
_audio.PlayPvs(component.ConfirmSound, uid);
|
||||
|
||||
var approverName = idCard.Comp?.FullName ?? Loc.GetString("access-reader-unknown-id");
|
||||
var approverJob = idCard.Comp?.JobTitle ?? Loc.GetString("access-reader-unknown-id");
|
||||
var message = Loc.GetString("cargo-console-unlock-approved-order-broadcast",
|
||||
("productName", Loc.GetString(order.ProductName)),
|
||||
("orderAmount", order.OrderQuantity),
|
||||
("approverName", approverName),
|
||||
("approverJob", approverJob),
|
||||
("cost", cost));
|
||||
_radio.SendRadioMessage(uid, message, component.AnnouncementChannel, uid, escapeMarkup: false);
|
||||
ConsolePopup(args.Session, Loc.GetString("cargo-console-trade-station", ("destination", MetaData(tradeDestination.Value).EntityName)));
|
||||
|
||||
// Log order approval
|
||||
@@ -327,7 +336,7 @@ namespace Content.Server.Cargo.Systems
|
||||
|
||||
private static CargoOrderData GetOrderData(CargoConsoleAddOrderMessage args, CargoProductPrototype cargoProduct, int id)
|
||||
{
|
||||
return new CargoOrderData(id, cargoProduct.Product, cargoProduct.Cost, args.Amount, args.Requester, args.Reason);
|
||||
return new CargoOrderData(id, cargoProduct.Product, cargoProduct.Name, cargoProduct.Cost, args.Amount, args.Requester, args.Reason);
|
||||
}
|
||||
|
||||
public static int GetOutstandingOrderCount(StationCargoOrderDatabaseComponent component)
|
||||
@@ -376,6 +385,7 @@ namespace Content.Server.Cargo.Systems
|
||||
public bool AddAndApproveOrder(
|
||||
EntityUid dbUid,
|
||||
string spawnId,
|
||||
string name,
|
||||
int cost,
|
||||
int qty,
|
||||
string sender,
|
||||
@@ -388,7 +398,7 @@ namespace Content.Server.Cargo.Systems
|
||||
DebugTools.Assert(_protoMan.HasIndex<EntityPrototype>(spawnId));
|
||||
// Make an order
|
||||
var id = GenerateOrderId(component);
|
||||
var order = new CargoOrderData(id, spawnId, cost, qty, sender, description);
|
||||
var order = new CargoOrderData(id, spawnId, name, cost, qty, sender, description);
|
||||
|
||||
// Approve it now
|
||||
order.SetApproverData(dest, sender);
|
||||
|
||||
@@ -154,7 +154,7 @@ public sealed partial class CargoSystem
|
||||
// We won't be able to fit the whole order on, so make one
|
||||
// which represents the space we do have left:
|
||||
var reducedOrder = new CargoOrderData(order.OrderId,
|
||||
order.ProductId, order.Price, spaceRemaining, order.Requester, order.Reason);
|
||||
order.ProductId, order.ProductName, order.Price, spaceRemaining, order.Requester, order.Reason);
|
||||
orders.Add(reducedOrder);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Server.Stack;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Server.Radio.EntitySystems;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
@@ -42,6 +43,7 @@ public sealed partial class CargoSystem : SharedCargoSystem
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly RadioSystem _radio = default!;
|
||||
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
private EntityQuery<CargoSellBlacklistComponent> _blacklistQuery;
|
||||
|
||||
@@ -199,7 +199,7 @@ public sealed class PricingSystem : EntitySystem
|
||||
/// This fires off an event to calculate the price.
|
||||
/// Calculating the price of an entity that somehow contains itself will likely hang.
|
||||
/// </remarks>
|
||||
public double GetPrice(EntityUid uid)
|
||||
public double GetPrice(EntityUid uid, bool includeContents = true)
|
||||
{
|
||||
var ev = new PriceCalculationEvent();
|
||||
RaiseLocalEvent(uid, ref ev);
|
||||
@@ -222,7 +222,7 @@ public sealed class PricingSystem : EntitySystem
|
||||
price += GetStaticPrice(uid);
|
||||
}
|
||||
|
||||
if (TryComp<ContainerManagerComponent>(uid, out var containers))
|
||||
if (includeContents && TryComp<ContainerManagerComponent>(uid, out var containers))
|
||||
{
|
||||
foreach (var container in containers.Containers.Values)
|
||||
{
|
||||
|
||||
36
Content.Server/Chat/V2/Commands/DeleteChatMessageCommand.cs
Normal file
36
Content.Server/Chat/V2/Commands/DeleteChatMessageCommand.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.Diagnostics;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Chat.V2.Repository;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Chat.V2.Commands;
|
||||
|
||||
[ToolshedCommand, AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class DeleteChatMessageCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _manager = default!;
|
||||
|
||||
[CommandImplementation("id")]
|
||||
public void DeleteChatMessage([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] uint messageId)
|
||||
{
|
||||
if (!_manager.GetEntitySystem<ChatRepositorySystem>().Delete(messageId))
|
||||
{
|
||||
ctx.ReportError(new MessageIdDoesNotExist());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record struct MessageIdDoesNotExist() : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromUnformatted(Loc.GetString("command-error-deletechatmessage-id-notexist"));
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
41
Content.Server/Chat/V2/Commands/NukeChatMessagesCommand.cs
Normal file
41
Content.Server/Chat/V2/Commands/NukeChatMessagesCommand.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System.Diagnostics;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Chat.V2.Repository;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Toolshed.Errors;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Chat.V2.Commands;
|
||||
|
||||
[ToolshedCommand, AdminCommand(AdminFlags.Admin)]
|
||||
public sealed class NukeChatMessagesCommand : ToolshedCommand
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _manager = default!;
|
||||
|
||||
[CommandImplementation("usernames")]
|
||||
public void Command([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] string usernamesCsv)
|
||||
{
|
||||
var usernames = usernamesCsv.Split(',');
|
||||
|
||||
foreach (var username in usernames)
|
||||
{
|
||||
if (!_manager.GetEntitySystem<ChatRepositorySystem>().NukeForUsername(username, out var reason))
|
||||
{
|
||||
ctx.ReportError(new NukeMessagesForUsernameError(reason));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record struct NukeMessagesForUsernameError(string Reason) : IConError
|
||||
{
|
||||
public FormattedMessage DescribeInner()
|
||||
{
|
||||
return FormattedMessage.FromUnformatted(Reason);
|
||||
}
|
||||
|
||||
public string? Expression { get; set; }
|
||||
public Vector2i? IssueSpan { get; set; }
|
||||
public StackTrace? Trace { get; set; }
|
||||
}
|
||||
94
Content.Server/Chat/V2/Messages.cs
Normal file
94
Content.Server/Chat/V2/Messages.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Content.Shared.Chat.Prototypes;
|
||||
using Content.Shared.Chat.V2;
|
||||
using Content.Shared.Radio;
|
||||
|
||||
namespace Content.Server.Chat.V2;
|
||||
|
||||
/// <summary>
|
||||
/// Raised locally when a comms announcement is made.
|
||||
/// </summary>
|
||||
public sealed class CommsAnnouncementCreatedEvent(EntityUid sender, EntityUid console, string message) : IChatEvent
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public EntityUid Sender { get; set; } = sender;
|
||||
public string Message { get; set; } = message;
|
||||
public MessageType Type => MessageType.Announcement;
|
||||
public EntityUid Console = console;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised locally when a character speaks in Dead Chat.
|
||||
/// </summary>
|
||||
public sealed class DeadChatCreatedEvent(EntityUid speaker, string message, bool isAdmin) : IChatEvent
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public EntityUid Sender { get; set; } = speaker;
|
||||
public string Message { get; set; } = message;
|
||||
public MessageType Type => MessageType.DeadChat;
|
||||
public bool IsAdmin = isAdmin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised locally when a character emotes.
|
||||
/// </summary>
|
||||
public sealed class EmoteCreatedEvent(EntityUid sender, string message, float range) : IChatEvent
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public EntityUid Sender { get; set; } = sender;
|
||||
public string Message { get; set; } = message;
|
||||
public MessageType Type => MessageType.Emote;
|
||||
public float Range = range;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised locally when a character talks in local.
|
||||
/// </summary>
|
||||
public sealed class LocalChatCreatedEvent(EntityUid speaker, string message, float range) : IChatEvent
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public EntityUid Sender { get; set; } = speaker;
|
||||
public string Message { get; set; } = message;
|
||||
public MessageType Type => MessageType.Local;
|
||||
public float Range = range;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised locally when a character speaks in LOOC.
|
||||
/// </summary>
|
||||
public sealed class LoocCreatedEvent(EntityUid speaker, string message) : IChatEvent
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public EntityUid Sender { get; set; } = speaker;
|
||||
public string Message { get; set; } = message;
|
||||
public MessageType Type => MessageType.Looc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised locally when a character speaks on the radio.
|
||||
/// </summary>
|
||||
public sealed class RadioCreatedEvent(
|
||||
EntityUid speaker,
|
||||
string message,
|
||||
RadioChannelPrototype channel)
|
||||
: IChatEvent
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public EntityUid Sender { get; set; } = speaker;
|
||||
public string Message { get; set; } = message;
|
||||
public RadioChannelPrototype Channel = channel;
|
||||
public MessageType Type => MessageType.Radio;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised locally when a character whispers.
|
||||
/// </summary>
|
||||
public sealed class WhisperCreatedEvent(EntityUid speaker, string message, float minRange, float maxRange) : IChatEvent
|
||||
{
|
||||
public uint Id { get; set; }
|
||||
public EntityUid Sender { get; set; } = speaker;
|
||||
public string Message { get; set; } = message;
|
||||
public MessageType Type => MessageType.Whisper;
|
||||
public float MinRange = minRange;
|
||||
public float MaxRange = maxRange;
|
||||
}
|
||||
|
||||
196
Content.Server/Chat/V2/Repository/ChatRepository.cs
Normal file
196
Content.Server/Chat/V2/Repository/ChatRepository.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Content.Shared.Chat.V2;
|
||||
using Content.Shared.Chat.V2.Repository;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Replays;
|
||||
|
||||
namespace Content.Server.Chat.V2.Repository;
|
||||
|
||||
/// <summary>
|
||||
/// Stores <see cref="IChatEvent"/>, gives them UIDs, and issues <see cref="MessageCreatedEvent"/>.
|
||||
/// Allows for deletion of messages.
|
||||
/// </summary>
|
||||
public sealed class ChatRepositorySystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IReplayRecordingManager _replay = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
|
||||
// Clocks should start at 1, as 0 indicates "clock not set" or "clock forgotten to be set by bad programmer".
|
||||
private uint _nextMessageId = 1;
|
||||
private Dictionary<uint, ChatRecord> _messages = new();
|
||||
private Dictionary<NetUserId, List<uint>> _playerMessages = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
Refresh();
|
||||
|
||||
_replay.RecordingFinished += _ =>
|
||||
{
|
||||
// TODO: resolve https://github.com/space-wizards/space-station-14/issues/25485 so we can dump the chat to disc.
|
||||
Refresh();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="IChatEvent"/> to the repo and raises it with a UID for consumption elsewhere.
|
||||
/// </summary>
|
||||
/// <param name="ev">The event to store and raise</param>
|
||||
/// <returns>If storing and raising succeeded.</returns>
|
||||
public bool Add(IChatEvent ev)
|
||||
{
|
||||
if (!_player.TryGetSessionByEntity(ev.Sender, out var session))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var messageId = _nextMessageId;
|
||||
|
||||
_nextMessageId++;
|
||||
|
||||
ev.Id = messageId;
|
||||
|
||||
var storedEv = new ChatRecord
|
||||
{
|
||||
UserName = session.Name,
|
||||
UserId = session.UserId,
|
||||
EntityName = Name(ev.Sender),
|
||||
StoredEvent = ev
|
||||
};
|
||||
|
||||
_messages[messageId] = storedEv;
|
||||
|
||||
CollectionsMarshal.GetValueRefOrAddDefault(_playerMessages, storedEv.UserId, out _)?.Add(messageId);
|
||||
|
||||
RaiseLocalEvent(ev.Sender, new MessageCreatedEvent(ev), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the event associated with a UID, if it exists.
|
||||
/// </summary>
|
||||
/// <param name="id">The UID of a event.</param>
|
||||
/// <returns>The event, if it exists.</returns>
|
||||
public IChatEvent? GetEventFor(uint id)
|
||||
{
|
||||
return _messages.TryGetValue(id, out var record) ? record.StoredEvent : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Edits a specific message and issues a <see cref="MessagePatchedEvent"/> that says this happened both locally and
|
||||
/// on the network. Note that this doesn't replay the message (yet), so translators and mutators won't act on it.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID to edit</param>
|
||||
/// <param name="message">The new message to send</param>
|
||||
/// <returns>If patching did anything did anything</returns>
|
||||
/// <remarks>Should be used for admining and admemeing only.</remarks>
|
||||
public bool Patch(uint id, string message)
|
||||
{
|
||||
if (!_messages.TryGetValue(id, out var ev))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ev.StoredEvent.Message = message;
|
||||
|
||||
RaiseLocalEvent(new MessagePatchedEvent(id, message));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a message from the repository and issues a <see cref="MessageDeletedEvent"/> that says this has happened
|
||||
/// both locally and on the network.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID to delete</param>
|
||||
/// <returns>If deletion did anything</returns>
|
||||
/// <remarks>Should only be used for adminning</remarks>
|
||||
public bool Delete(uint id)
|
||||
{
|
||||
if (!_messages.TryGetValue(id, out var ev))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_messages.Remove(id);
|
||||
|
||||
if (_playerMessages.TryGetValue(ev.UserId, out var set))
|
||||
{
|
||||
set.Remove(id);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(new MessageDeletedEvent(id));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nukes a user's entire chat history from the repo and issues a <see cref="MessageDeletedEvent"/> saying this has
|
||||
/// happened.
|
||||
/// </summary>
|
||||
/// <param name="userName">The user ID to nuke.</param>
|
||||
/// <param name="reason">Why nuking failed, if it did.</param>
|
||||
/// <returns>If nuking did anything.</returns>
|
||||
/// <remarks>Note that this could be a <b>very large</b> event, as we send every single event ID over the wire.
|
||||
/// By necessity we can't leak the player-source of chat messages (or if they even have the same origin) because of
|
||||
/// client modders who could use that information to cheat/metagrudge/etc >:(</remarks>
|
||||
public bool NukeForUsername(string userName, [NotNullWhen(false)] out string? reason)
|
||||
{
|
||||
if (!_player.TryGetUserId(userName, out var userId))
|
||||
{
|
||||
reason = Loc.GetString("command-error-nukechatmessages-usernames-usernamenotexist", ("username", userName));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return NukeForUserId(userId, out reason);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nukes a user's entire chat history from the repo and issues a <see cref="MessageDeletedEvent"/> saying this has
|
||||
/// happened.
|
||||
/// </summary>
|
||||
/// <param name="userId">The user ID to nuke.</param>
|
||||
/// <param name="reason">Why nuking failed, if it did.</param>
|
||||
/// <returns>If nuking did anything.</returns>
|
||||
/// <remarks>Note that this could be a <b>very large</b> event, as we send every single event ID over the wire.
|
||||
/// By necessity we can't leak the player-source of chat messages (or if they even have the same origin) because of
|
||||
/// client modders who could use that information to cheat/metagrudge/etc >:(</remarks>
|
||||
public bool NukeForUserId(NetUserId userId, [NotNullWhen(false)] out string? reason)
|
||||
{
|
||||
if (!_playerMessages.TryGetValue(userId, out var dict))
|
||||
{
|
||||
reason = Loc.GetString("command-error-nukechatmessages-usernames-usernamenomessages", ("userId", userId.UserId.ToString()));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var id in dict)
|
||||
{
|
||||
_messages.Remove(id);
|
||||
}
|
||||
|
||||
var ev = new MessagesNukedEvent(dict);
|
||||
|
||||
CollectionsMarshal.GetValueRefOrAddDefault(_playerMessages, userId, out _)?.Clear();
|
||||
|
||||
RaiseLocalEvent(ev);
|
||||
|
||||
reason = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dumps held chat storage data and refreshes the repo.
|
||||
/// </summary>
|
||||
public void Refresh()
|
||||
{
|
||||
_nextMessageId = 1;
|
||||
_messages.Clear();
|
||||
_playerMessages.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Shared.FixedPoint;
|
||||
|
||||
|
||||
namespace Content.Server.Chemistry.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class ReagentTankComponent : Component
|
||||
{
|
||||
[DataField("transferAmount")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public FixedPoint2 TransferAmount { get; set; } = FixedPoint2.New(10);
|
||||
|
||||
[DataField("tankType")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentTankType TankType { get; set; } = ReagentTankType.Unspecified;
|
||||
}
|
||||
|
||||
public enum ReagentTankType : byte
|
||||
{
|
||||
Unspecified,
|
||||
Fuel
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public sealed partial class ReactionMixerSystem : EntitySystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_solutionContainers.TryGetMixableSolution(args.Target.Value, out var solution))
|
||||
if (!_solutionContainers.TryGetMixableSolution(args.Target.Value, out var solution, out _))
|
||||
return;
|
||||
|
||||
_popup.PopupEntity(Loc.GetString(entity.Comp.MixMessage, ("mixed", Identity.Entity(args.Target.Value, EntityManager)), ("mixer", Identity.Entity(entity.Owner, EntityManager))), args.User, args.User);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Inventory.Events;
|
||||
|
||||
namespace Content.Server.Clothing;
|
||||
|
||||
@@ -13,8 +12,8 @@ public sealed class MagbootsSystem : SharedMagbootsSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<MagbootsComponent, GotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<MagbootsComponent, GotUnequippedEvent>(OnGotUnequipped);
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<MagbootsComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
|
||||
}
|
||||
|
||||
protected override void UpdateMagbootEffects(EntityUid parent, EntityUid uid, bool state, MagbootsComponent? component)
|
||||
@@ -38,19 +37,13 @@ public sealed class MagbootsSystem : SharedMagbootsSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, GotUnequippedEvent args)
|
||||
private void OnGotUnequipped(EntityUid uid, MagbootsComponent component, ref ClothingGotUnequippedEvent args)
|
||||
{
|
||||
if (args.Slot == "shoes")
|
||||
{
|
||||
UpdateMagbootEffects(args.Equipee, uid, false, component);
|
||||
}
|
||||
UpdateMagbootEffects(args.Wearer, uid, false, component);
|
||||
}
|
||||
|
||||
private void OnGotEquipped(EntityUid uid, MagbootsComponent component, GotEquippedEvent args)
|
||||
private void OnGotEquipped(EntityUid uid, MagbootsComponent component, ref ClothingGotEquippedEvent args)
|
||||
{
|
||||
if (args.Slot == "shoes")
|
||||
{
|
||||
UpdateMagbootEffects(args.Equipee, uid, true, component);
|
||||
}
|
||||
UpdateMagbootEffects(args.Wearer, uid, true, component);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
using Content.Shared.Tools;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Construction.Components
|
||||
namespace Content.Server.Construction.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Used for something that can be refined by welder.
|
||||
/// For example, glass shard can be refined to glass sheet.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class WelderRefinableComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Used for something that can be refined by welder.
|
||||
/// For example, glass shard can be refined to glass sheet.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class WelderRefinableComponent : Component
|
||||
{
|
||||
[DataField("refineResult")]
|
||||
public HashSet<string>? RefineResult = new();
|
||||
[DataField]
|
||||
public HashSet<EntProtoId>? RefineResult = new();
|
||||
|
||||
[DataField("refineTime")]
|
||||
public float RefineTime = 2f;
|
||||
[DataField]
|
||||
public float RefineTime = 2f;
|
||||
|
||||
[DataField("qualityNeeded", customTypeSerializer:typeof(PrototypeIdSerializer<ToolQualityPrototype>))]
|
||||
public string QualityNeeded = "Welding";
|
||||
}
|
||||
[DataField]
|
||||
public float RefineFuel;
|
||||
|
||||
[DataField]
|
||||
public ProtoId<ToolQualityPrototype> QualityNeeded = "Welding";
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user