Merge remote-tracking branch 'refs/remotes/upstream/master' into 2024/04/21-loadouts
# Conflicts: # Content.Server/IoC/ServerContentIoC.cs # Resources/Prototypes/Roles/Jobs/Medical/chemist.yml
This commit is contained in:
commit
74002a1c4e
|
|
@ -9,20 +9,20 @@ namespace Content.Client.Access;
|
|||
|
||||
public sealed class AccessOverlay : Overlay
|
||||
{
|
||||
private const string TextFontPath = "/Fonts/NotoSans/NotoSans-Regular.ttf";
|
||||
private const int TextFontSize = 12;
|
||||
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedTransformSystem _xform;
|
||||
private readonly SharedTransformSystem _transformSystem;
|
||||
private readonly Font _font;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup, SharedTransformSystem xform)
|
||||
public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, SharedTransformSystem transformSystem)
|
||||
{
|
||||
_entityManager = entManager;
|
||||
_lookup = lookup;
|
||||
_xform = xform;
|
||||
|
||||
_font = cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
|
||||
_entityManager = entityManager;
|
||||
_transformSystem = transformSystem;
|
||||
_font = resourceCache.GetFont(TextFontPath, TextFontSize);
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
|
|
@ -30,52 +30,65 @@ public sealed class AccessOverlay : Overlay
|
|||
if (args.ViewportControl == null)
|
||||
return;
|
||||
|
||||
var readerQuery = _entityManager.GetEntityQuery<AccessReaderComponent>();
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB,
|
||||
LookupFlags.Static | LookupFlags.Approximate))
|
||||
var textBuffer = new StringBuilder();
|
||||
var query = _entityManager.EntityQueryEnumerator<AccessReaderComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var accessReader, out var transform))
|
||||
{
|
||||
if (!readerQuery.TryGetComponent(ent, out var reader) ||
|
||||
!xformQuery.TryGetComponent(ent, out var xform))
|
||||
textBuffer.Clear();
|
||||
|
||||
var entityName = _entityManager.ToPrettyString(uid);
|
||||
textBuffer.AppendLine(entityName.Prototype);
|
||||
textBuffer.Append("UID: ");
|
||||
textBuffer.Append(entityName.Uid.Id);
|
||||
textBuffer.Append(", NUID: ");
|
||||
textBuffer.Append(entityName.Nuid.Id);
|
||||
textBuffer.AppendLine();
|
||||
|
||||
if (!accessReader.Enabled)
|
||||
{
|
||||
textBuffer.AppendLine("-Disabled");
|
||||
continue;
|
||||
}
|
||||
|
||||
var text = new StringBuilder();
|
||||
var index = 0;
|
||||
var a = $"{_entityManager.ToPrettyString(ent)}";
|
||||
text.Append(a);
|
||||
|
||||
foreach (var list in reader.AccessLists)
|
||||
if (accessReader.AccessLists.Count > 0)
|
||||
{
|
||||
a = $"Tag {index}";
|
||||
text.AppendLine(a);
|
||||
|
||||
foreach (var entry in list)
|
||||
var groupNumber = 0;
|
||||
foreach (var accessList in accessReader.AccessLists)
|
||||
{
|
||||
a = $"- {entry}";
|
||||
text.AppendLine(a);
|
||||
groupNumber++;
|
||||
foreach (var entry in accessList)
|
||||
{
|
||||
textBuffer.Append("+Set ");
|
||||
textBuffer.Append(groupNumber);
|
||||
textBuffer.Append(": ");
|
||||
textBuffer.Append(entry.Id);
|
||||
textBuffer.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
string textStr;
|
||||
|
||||
if (text.Length >= 2)
|
||||
{
|
||||
textStr = text.ToString();
|
||||
textStr = textStr[..^2];
|
||||
}
|
||||
else
|
||||
{
|
||||
textStr = "";
|
||||
textBuffer.AppendLine("+Unrestricted");
|
||||
}
|
||||
|
||||
var screenPos = args.ViewportControl.WorldToScreen(_xform.GetWorldPosition(xform));
|
||||
foreach (var key in accessReader.AccessKeys)
|
||||
{
|
||||
textBuffer.Append("+Key ");
|
||||
textBuffer.Append(key.OriginStation);
|
||||
textBuffer.Append(": ");
|
||||
textBuffer.Append(key.Id);
|
||||
textBuffer.AppendLine();
|
||||
}
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold);
|
||||
foreach (var tag in accessReader.DenyTags)
|
||||
{
|
||||
textBuffer.Append("-Tag ");
|
||||
textBuffer.AppendLine(tag.Id);
|
||||
}
|
||||
|
||||
var accessInfoText = textBuffer.ToString();
|
||||
var screenPos = args.ViewportControl.WorldToScreen(_transformSystem.GetWorldPosition(transform));
|
||||
args.ScreenHandle.DrawString(_font, screenPos, accessInfoText, Color.Gold);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,16 @@ namespace Content.Client.Access.Commands;
|
|||
public sealed class ShowAccessReadersCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showaccessreaders";
|
||||
public string Description => "Shows all access readers in the viewport";
|
||||
public string Help => $"{Command}";
|
||||
|
||||
public string Description => "Toggles showing access reader permissions on the map";
|
||||
public string Help => """
|
||||
Overlay Info:
|
||||
-Disabled | The access reader is disabled
|
||||
+Unrestricted | The access reader has no restrictions
|
||||
+Set [Index]: [Tag Name]| A tag in an access set (accessor needs all tags in the set to be allowed by the set)
|
||||
+Key [StationUid]: [StationRecordKeyId] | A StationRecordKey that is allowed
|
||||
-Tag [Tag Name] | A tag that is not allowed (takes priority over other allows)
|
||||
""";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var collection = IoCManager.Instance;
|
||||
|
|
@ -26,10 +34,9 @@ public sealed class ShowAccessReadersCommand : IConsoleCommand
|
|||
|
||||
var entManager = collection.Resolve<IEntityManager>();
|
||||
var cache = collection.Resolve<IResourceCache>();
|
||||
var lookup = entManager.System<EntityLookupSystem>();
|
||||
var xform = entManager.System<SharedTransformSystem>();
|
||||
|
||||
overlay.AddOverlay(new AccessOverlay(entManager, cache, lookup, xform));
|
||||
overlay.AddOverlay(new AccessOverlay(entManager, cache, xform));
|
||||
shell.WriteLine($"Set access reader debug overlay to true");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 = "---";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
|
|||
SendMessage(new BountyPrintLabelMessage(id));
|
||||
};
|
||||
|
||||
_menu.OnSkipButtonPressed += id =>
|
||||
{
|
||||
SendMessage(new BountySkipMessage(id));
|
||||
};
|
||||
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
|
|
@ -37,7 +42,7 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
|
|||
if (message is not CargoBountyConsoleState state)
|
||||
return;
|
||||
|
||||
_menu?.UpdateEntries(state.Bounties);
|
||||
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,18 @@
|
|||
</BoxContainer>
|
||||
<Control MinWidth="10"/>
|
||||
<BoxContainer Orientation="Vertical" MinWidth="120">
|
||||
<Button Name="PrintButton" Text="{Loc 'bounty-console-label-button-text'}" HorizontalExpand="False" HorizontalAlignment="Right"/>
|
||||
<BoxContainer Orientation="Horizontal" MinWidth="120">
|
||||
<Button Name="PrintButton"
|
||||
Text="{Loc 'bounty-console-label-button-text'}"
|
||||
HorizontalExpand="False"
|
||||
HorizontalAlignment="Right"
|
||||
StyleClasses="OpenRight"/>
|
||||
<Button Name="SkipButton"
|
||||
Text="{Loc 'bounty-console-skip-button-text'}"
|
||||
HorizontalExpand="False"
|
||||
HorizontalAlignment="Right"
|
||||
StyleClasses="OpenLeft"/>
|
||||
</BoxContainer>
|
||||
<RichTextLabel Name="IdLabel" HorizontalAlignment="Right" Margin="0 0 5 0"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
using Content.Client.Message;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Random;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Serilog;
|
||||
|
||||
namespace Content.Client.Cargo.UI;
|
||||
|
||||
|
|
@ -14,15 +16,19 @@ public sealed partial class BountyEntry : BoxContainer
|
|||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public Action? OnButtonPressed;
|
||||
public Action? OnLabelButtonPressed;
|
||||
public Action? OnSkipButtonPressed;
|
||||
|
||||
public TimeSpan EndTime;
|
||||
public TimeSpan UntilNextSkip;
|
||||
|
||||
public BountyEntry(CargoBountyData bounty)
|
||||
public BountyEntry(CargoBountyData bounty, TimeSpan untilNextSkip)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
UntilNextSkip = untilNextSkip;
|
||||
|
||||
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
|
||||
return;
|
||||
|
||||
|
|
@ -38,6 +44,27 @@ public sealed partial class BountyEntry : BoxContainer
|
|||
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
|
||||
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
|
||||
|
||||
PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
|
||||
PrintButton.OnPressed += _ => OnLabelButtonPressed?.Invoke();
|
||||
SkipButton.OnPressed += _ => OnSkipButtonPressed?.Invoke();
|
||||
}
|
||||
|
||||
private void UpdateSkipButton(float deltaSeconds)
|
||||
{
|
||||
UntilNextSkip -= TimeSpan.FromSeconds(deltaSeconds);
|
||||
if (UntilNextSkip > TimeSpan.Zero)
|
||||
{
|
||||
SkipButton.Label.Text = UntilNextSkip.ToString("mm\\:ss");
|
||||
SkipButton.Disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SkipButton.Label.Text = Loc.GetString("bounty-console-skip-button-text");
|
||||
SkipButton.Disabled = false;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
UpdateSkipButton(args.DeltaSeconds);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,19 +10,21 @@ namespace Content.Client.Cargo.UI;
|
|||
public sealed partial class CargoBountyMenu : FancyWindow
|
||||
{
|
||||
public Action<string>? OnLabelButtonPressed;
|
||||
public Action<string>? OnSkipButtonPressed;
|
||||
|
||||
public CargoBountyMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void UpdateEntries(List<CargoBountyData> bounties)
|
||||
public void UpdateEntries(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
|
||||
{
|
||||
BountyEntriesContainer.Children.Clear();
|
||||
foreach (var b in bounties)
|
||||
{
|
||||
var entry = new BountyEntry(b);
|
||||
entry.OnButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
|
||||
var entry = new BountyEntry(b, untilNextSkip);
|
||||
entry.OnLabelButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
|
||||
entry.OnSkipButtonPressed += () => OnSkipButtonPressed?.Invoke(b.Id);
|
||||
|
||||
BountyEntriesContainer.AddChild(entry);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Inventory.Events;
|
||||
|
||||
namespace Content.Client.Clothing.Systems;
|
||||
|
||||
public sealed class WaddleClothingSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<WaddleWhenWornComponent, GotEquippedEvent>(OnGotEquipped);
|
||||
SubscribeLocalEvent<WaddleWhenWornComponent, GotUnequippedEvent>(OnGotUnequipped);
|
||||
}
|
||||
|
||||
private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, GotEquippedEvent args)
|
||||
{
|
||||
var waddleAnimComp = EnsureComp<WaddleAnimationComponent>(args.Equipee);
|
||||
|
||||
waddleAnimComp.AnimationLength = comp.AnimationLength;
|
||||
waddleAnimComp.HopIntensity = comp.HopIntensity;
|
||||
waddleAnimComp.RunAnimationLengthMultiplier = comp.RunAnimationLengthMultiplier;
|
||||
waddleAnimComp.TumbleIntensity = comp.TumbleIntensity;
|
||||
}
|
||||
|
||||
private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, GotUnequippedEvent args)
|
||||
{
|
||||
RemComp<WaddleAnimationComponent>(args.Equipee);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
|
|
@ -79,7 +80,7 @@ namespace Content.Client.HealthAnalyzer.UI
|
|||
);
|
||||
|
||||
Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text",
|
||||
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - 273f:F1} °C ({msg.Temperature:F1} °K)")
|
||||
("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)")
|
||||
);
|
||||
|
||||
BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -104,41 +104,12 @@ public sealed partial class LatheMenu : DefaultWindow
|
|||
RecipeList.Children.Clear();
|
||||
foreach (var prototype in sortedRecipesToShow)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
var first = true;
|
||||
foreach (var (id, amount) in prototype.RequiredMaterials)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<MaterialPrototype>(id, out var proto))
|
||||
continue;
|
||||
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
sb.Append('\n');
|
||||
|
||||
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, component.MaterialUseMultiplier);
|
||||
var sheetVolume = _materialStorage.GetSheetVolume(proto);
|
||||
|
||||
var unit = Loc.GetString(proto.Unit);
|
||||
// rounded in locale not here
|
||||
var sheets = adjustedAmount / (float) sheetVolume;
|
||||
var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
|
||||
var name = Loc.GetString(proto.Name);
|
||||
sb.Append(Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText)));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(prototype.Description))
|
||||
{
|
||||
sb.Append('\n');
|
||||
sb.Append(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
|
||||
}
|
||||
|
||||
var icon = prototype.Icon == null
|
||||
? _spriteSystem.GetPrototypeIcon(prototype.Result).Default
|
||||
: _spriteSystem.Frame0(prototype.Icon);
|
||||
var canProduce = _lathe.CanProduce(_owner, prototype, quantity);
|
||||
|
||||
var control = new RecipeControl(prototype, sb.ToString(), canProduce, icon);
|
||||
var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, icon);
|
||||
control.OnButtonPressed += s =>
|
||||
{
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
|
||||
|
|
@ -149,6 +120,51 @@ public sealed partial class LatheMenu : DefaultWindow
|
|||
}
|
||||
}
|
||||
|
||||
private string GenerateTooltipText(LatheRecipePrototype prototype)
|
||||
{
|
||||
StringBuilder sb = new();
|
||||
|
||||
foreach (var (id, amount) in prototype.RequiredMaterials)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<MaterialPrototype>(id, out var proto))
|
||||
continue;
|
||||
|
||||
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, _entityManager.GetComponent<LatheComponent>(_owner).MaterialUseMultiplier);
|
||||
var sheetVolume = _materialStorage.GetSheetVolume(proto);
|
||||
|
||||
var unit = Loc.GetString(proto.Unit);
|
||||
var sheets = adjustedAmount / (float) sheetVolume;
|
||||
|
||||
var availableAmount = _materialStorage.GetMaterialAmount(_owner, id);
|
||||
var missingAmount = Math.Max(0, adjustedAmount - availableAmount);
|
||||
var missingSheets = missingAmount / (float) sheetVolume;
|
||||
|
||||
var name = Loc.GetString(proto.Name);
|
||||
|
||||
string tooltipText;
|
||||
if (missingSheets > 0)
|
||||
{
|
||||
tooltipText = Loc.GetString("lathe-menu-material-amount-missing", ("amount", sheets), ("missingAmount", missingSheets), ("unit", unit), ("material", name));
|
||||
}
|
||||
else
|
||||
{
|
||||
var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
|
||||
tooltipText = Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText));
|
||||
}
|
||||
|
||||
sb.AppendLine(tooltipText);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(prototype.Description))
|
||||
sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
|
||||
|
||||
// Remove last newline
|
||||
if (sb.Length > 0)
|
||||
sb.Remove(sb.Length - 1, 1);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public void UpdateCategories()
|
||||
{
|
||||
var currentCategories = new List<ProtoId<LatheCategoryPrototype>>();
|
||||
|
|
|
|||
|
|
@ -11,17 +11,16 @@ namespace Content.Client.Lathe.UI;
|
|||
public sealed partial class RecipeControl : Control
|
||||
{
|
||||
public Action<string>? OnButtonPressed;
|
||||
public Func<string> TooltipTextSupplier;
|
||||
|
||||
public string TooltipText;
|
||||
|
||||
public RecipeControl(LatheRecipePrototype recipe, string tooltip, bool canProduce, Texture? texture = null)
|
||||
public RecipeControl(LatheRecipePrototype recipe, Func<string> tooltipTextSupplier, bool canProduce, Texture? texture = null)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
RecipeName.Text = recipe.Name;
|
||||
RecipeTexture.Texture = texture;
|
||||
Button.Disabled = !canProduce;
|
||||
TooltipText = tooltip;
|
||||
TooltipTextSupplier = tooltipTextSupplier;
|
||||
Button.TooltipSupplier = SupplyTooltip;
|
||||
|
||||
Button.OnPressed += (_) =>
|
||||
|
|
@ -32,6 +31,6 @@ public sealed partial class RecipeControl : Control
|
|||
|
||||
private Control? SupplyTooltip(Control sender)
|
||||
{
|
||||
return new RecipeTooltip(TooltipText);
|
||||
return new RecipeTooltip(TooltipTextSupplier());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
using System.Numerics;
|
||||
using Content.Client.Buckle;
|
||||
using Content.Client.Gravity;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Events;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Movement.Systems;
|
||||
|
||||
public sealed class WaddleAnimationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
|
||||
[Dependency] private readonly GravitySystem _gravity = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly BuckleSystem _buckle = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, MoveInputEvent>(OnMovementInput);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, StartedWaddlingEvent>(OnStartedWalking);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, StoppedWaddlingEvent>(OnStoppedWalking);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, StunnedEvent>(OnStunned);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, KnockedDownEvent>(OnKnockedDown);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, BuckleChangeEvent>(OnBuckleChange);
|
||||
}
|
||||
|
||||
private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args)
|
||||
{
|
||||
// Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
|
||||
// they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling)
|
||||
{
|
||||
var stopped = new StoppedWaddlingEvent(entity);
|
||||
|
||||
RaiseLocalEvent(entity, ref stopped);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Only start waddling if we're not currently AND we're actually moving.
|
||||
if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement)
|
||||
return;
|
||||
|
||||
var started = new StartedWaddlingEvent(entity);
|
||||
|
||||
RaiseLocalEvent(entity, ref started);
|
||||
}
|
||||
|
||||
private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args)
|
||||
{
|
||||
if (_animation.HasRunningAnimation(uid, component.KeyName))
|
||||
return;
|
||||
|
||||
if (!TryComp<InputMoverComponent>(uid, out var mover))
|
||||
return;
|
||||
|
||||
if (_gravity.IsWeightless(uid))
|
||||
return;
|
||||
|
||||
|
||||
if (!_actionBlocker.CanMove(uid, mover))
|
||||
return;
|
||||
|
||||
// Do nothing if buckled in
|
||||
if (_buckle.IsBuckled(uid))
|
||||
return;
|
||||
|
||||
// Do nothing if crit or dead (for obvious reasons)
|
||||
if (_mobState.IsIncapacitated(uid))
|
||||
return;
|
||||
|
||||
var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
|
||||
var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
|
||||
|
||||
component.LastStep = !component.LastStep;
|
||||
component.IsCurrentlyWaddling = true;
|
||||
|
||||
var anim = new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(len),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Rotation),
|
||||
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
|
||||
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
|
||||
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
|
||||
}
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Offset),
|
||||
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
|
||||
new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2),
|
||||
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_animation.Play(uid, anim, component.KeyName);
|
||||
}
|
||||
|
||||
private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
|
||||
{
|
||||
var started = new StartedWaddlingEvent(uid);
|
||||
|
||||
RaiseLocalEvent(uid, ref started);
|
||||
}
|
||||
|
||||
private void OnStunned(EntityUid uid, WaddleAnimationComponent component, StunnedEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void OnKnockedDown(EntityUid uid, WaddleAnimationComponent component, KnockedDownEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void OnBuckleChange(EntityUid uid, WaddleAnimationComponent component, BuckleChangeEvent args)
|
||||
{
|
||||
StopWaddling(uid, component);
|
||||
}
|
||||
|
||||
private void StopWaddling(EntityUid uid, WaddleAnimationComponent component)
|
||||
{
|
||||
if (!component.IsCurrentlyWaddling)
|
||||
return;
|
||||
|
||||
_animation.Stop(uid, component.KeyName);
|
||||
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sprite.Offset = new Vector2();
|
||||
sprite.Rotation = Angle.FromDegrees(0);
|
||||
|
||||
component.IsCurrentlyWaddling = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,8 +59,10 @@ namespace Content.Client.Options.UI.Tabs
|
|||
UpdateApplyButton();
|
||||
};
|
||||
|
||||
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel.UserData.PatronTier is { } patron;
|
||||
|
||||
// Channel can be null in replays so.
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
|
||||
|
||||
HudThemeOption.OnItemSelected += OnHudThemeChanged;
|
||||
DiscordRich.OnToggled += OnCheckBoxToggled;
|
||||
ShowOocPatronColor.OnToggled += OnCheckBoxToggled;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -184,6 +184,12 @@ namespace Content.Client.Popups
|
|||
PopupEntity(message, uid, recipient.Value, type);
|
||||
}
|
||||
|
||||
public override void PopupPredicted(string? recipientMessage, string? othersMessage, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
|
||||
{
|
||||
if (recipient != null && _timing.IsFirstTimePredicted)
|
||||
PopupEntity(recipientMessage, uid, recipient.Value, type);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Network Event Handlers
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.RCD;
|
||||
using Content.Shared.RCD.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
|
@ -16,17 +18,24 @@ public sealed partial class RCDMenu : RadialMenu
|
|||
{
|
||||
[Dependency] private readonly EntityManager _entManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
private readonly SharedPopupSystem _popup;
|
||||
|
||||
public event Action<ProtoId<RCDPrototype>>? SendRCDSystemMessageAction;
|
||||
|
||||
private EntityUid _owner;
|
||||
|
||||
public RCDMenu(EntityUid owner, RCDMenuBoundUserInterface bui)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||
_popup = _entManager.System<SharedPopupSystem>();
|
||||
|
||||
_owner = owner;
|
||||
|
||||
// Find the main radial container
|
||||
var main = FindControl<RadialContainer>("Main");
|
||||
|
|
@ -51,14 +60,21 @@ public sealed partial class RCDMenu : RadialMenu
|
|||
if (parent == null)
|
||||
continue;
|
||||
|
||||
var name = Loc.GetString(proto.SetName);
|
||||
name = char.ToUpper(name[0]) + name.Remove(0, 1);
|
||||
var tooltip = Loc.GetString(proto.SetName);
|
||||
|
||||
if ((proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject) &&
|
||||
proto.Prototype != null && _protoManager.TryIndex(proto.Prototype, out var entProto))
|
||||
{
|
||||
tooltip = Loc.GetString(entProto.Name);
|
||||
}
|
||||
|
||||
tooltip = char.ToUpper(tooltip[0]) + tooltip.Remove(0, 1);
|
||||
|
||||
var button = new RCDMenuButton()
|
||||
{
|
||||
StyleClasses = { "RadialMenuButton" },
|
||||
SetSize = new Vector2(64f, 64f),
|
||||
ToolTip = name,
|
||||
ToolTip = tooltip,
|
||||
ProtoId = protoId,
|
||||
};
|
||||
|
||||
|
|
@ -120,6 +136,27 @@ public sealed partial class RCDMenu : RadialMenu
|
|||
castChild.OnButtonUp += _ =>
|
||||
{
|
||||
SendRCDSystemMessageAction?.Invoke(castChild.ProtoId);
|
||||
|
||||
if (_playerManager.LocalSession?.AttachedEntity != null &&
|
||||
_protoManager.TryIndex(castChild.ProtoId, out var proto))
|
||||
{
|
||||
var msg = Loc.GetString("rcd-component-change-mode", ("mode", Loc.GetString(proto.SetName)));
|
||||
|
||||
if (proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject)
|
||||
{
|
||||
var name = Loc.GetString(proto.SetName);
|
||||
|
||||
if (proto.Prototype != null &&
|
||||
_protoManager.TryIndex(proto.Prototype, out var entProto))
|
||||
name = entProto.Name;
|
||||
|
||||
msg = Loc.GetString("rcd-component-change-build-mode", ("name", name));
|
||||
}
|
||||
|
||||
// Popup message
|
||||
_popup.PopupClient(msg, _owner, _playerManager.LocalSession.AttachedEntity);
|
||||
}
|
||||
|
||||
Close();
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
|||
private string _windowName = Loc.GetString("store-ui-default-title");
|
||||
|
||||
[ViewVariables]
|
||||
private string _search = "";
|
||||
private string _search = string.Empty;
|
||||
|
||||
[ViewVariables]
|
||||
private HashSet<ListingData> _listings = new();
|
||||
|
|
@ -41,7 +41,7 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
|||
_menu.OnCategoryButtonPressed += (_, category) =>
|
||||
{
|
||||
_menu.CurrentCategory = category;
|
||||
SendMessage(new StoreRequestUpdateInterfaceMessage());
|
||||
_menu?.UpdateListing();
|
||||
};
|
||||
|
||||
_menu.OnWithdrawAttempt += (_, type, amount) =>
|
||||
|
|
@ -49,11 +49,6 @@ public sealed class StoreBoundUserInterface : BoundUserInterface
|
|||
SendMessage(new StoreRequestWithdrawMessage(type, amount));
|
||||
};
|
||||
|
||||
_menu.OnRefreshButtonPressed += (_) =>
|
||||
{
|
||||
SendMessage(new StoreRequestUpdateInterfaceMessage());
|
||||
};
|
||||
|
||||
_menu.SearchTextUpdated += (_, search) =>
|
||||
{
|
||||
_search = search.Trim().ToLowerInvariant();
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
Margin="0,0,4,0"
|
||||
MinSize="48 48"
|
||||
Stretch="KeepAspectCentered" />
|
||||
<Control MinWidth="5"/>
|
||||
<RichTextLabel Name="StoreItemDescription" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,91 @@
|
|||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared.Store;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Store.Ui;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StoreListingControl : Control
|
||||
{
|
||||
public StoreListingControl(string itemName, string itemDescription,
|
||||
string price, bool canBuy, Texture? texture = null)
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
private readonly ClientGameTicker _ticker;
|
||||
|
||||
private readonly ListingData _data;
|
||||
|
||||
private readonly bool _hasBalance;
|
||||
private readonly string _price;
|
||||
public StoreListingControl(ListingData data, string price, bool hasBalance, Texture? texture = null)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
StoreItemName.Text = itemName;
|
||||
StoreItemDescription.SetMessage(itemDescription);
|
||||
_ticker = _entity.System<ClientGameTicker>();
|
||||
|
||||
StoreItemBuyButton.Text = price;
|
||||
StoreItemBuyButton.Disabled = !canBuy;
|
||||
_data = data;
|
||||
_hasBalance = hasBalance;
|
||||
_price = price;
|
||||
|
||||
StoreItemName.Text = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype);
|
||||
StoreItemDescription.SetMessage(ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(_data, _prototype));
|
||||
|
||||
UpdateBuyButtonText();
|
||||
StoreItemBuyButton.Disabled = !CanBuy();
|
||||
|
||||
StoreItemTexture.Texture = texture;
|
||||
}
|
||||
|
||||
private bool CanBuy()
|
||||
{
|
||||
if (!_hasBalance)
|
||||
return false;
|
||||
|
||||
var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
|
||||
if (_data.RestockTime > stationTime)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UpdateBuyButtonText()
|
||||
{
|
||||
var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
|
||||
if (_data.RestockTime > stationTime)
|
||||
{
|
||||
var timeLeftToBuy = stationTime - _data.RestockTime;
|
||||
StoreItemBuyButton.Text = timeLeftToBuy.Duration().ToString(@"mm\:ss");
|
||||
}
|
||||
else
|
||||
{
|
||||
StoreItemBuyButton.Text = _price;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateName()
|
||||
{
|
||||
var name = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype);
|
||||
|
||||
var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
|
||||
if (_data.RestockTime > stationTime)
|
||||
{
|
||||
name += Loc.GetString("store-ui-button-out-of-stock");
|
||||
}
|
||||
|
||||
StoreItemName.Text = name;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
UpdateBuyButtonText();
|
||||
UpdateName();
|
||||
StoreItemBuyButton.Disabled = !CanBuy();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,6 @@
|
|||
HorizontalAlignment="Left"
|
||||
Access="Public"
|
||||
HorizontalExpand="True" />
|
||||
<Button
|
||||
Name="RefreshButton"
|
||||
MinWidth="64"
|
||||
HorizontalAlignment="Right"
|
||||
Text="Refresh" />
|
||||
<Button
|
||||
Name="WithdrawButton"
|
||||
MinWidth="64"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System.Linq;
|
||||
using Content.Client.Actions;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Client.Message;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Store;
|
||||
|
|
@ -11,7 +10,6 @@ using Robust.Client.UserInterface.Controls;
|
|||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Store.Ui;
|
||||
|
||||
|
|
@ -20,9 +18,6 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||
private readonly ClientGameTicker _gameTicker;
|
||||
|
||||
private StoreWithdrawWindow? _withdrawWindow;
|
||||
|
||||
|
|
@ -30,21 +25,19 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
public event Action<BaseButton.ButtonEventArgs, ListingData>? OnListingButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
|
||||
public event Action<BaseButton.ButtonEventArgs>? OnRefreshButtonPressed;
|
||||
public event Action<BaseButton.ButtonEventArgs>? OnRefundAttempt;
|
||||
|
||||
public Dictionary<string, FixedPoint2> Balance = new();
|
||||
public Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> Balance = new();
|
||||
public string CurrentCategory = string.Empty;
|
||||
|
||||
private List<ListingData> _cachedListings = new();
|
||||
|
||||
public StoreMenu(string name)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_gameTicker = _entitySystem.GetEntitySystem<ClientGameTicker>();
|
||||
|
||||
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
|
||||
RefreshButton.OnButtonDown += OnRefreshButtonDown;
|
||||
RefundButton.OnButtonDown += OnRefundButtonDown;
|
||||
SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text);
|
||||
|
||||
|
|
@ -52,12 +45,12 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
Window.Title = name;
|
||||
}
|
||||
|
||||
public void UpdateBalance(Dictionary<string, FixedPoint2> balance)
|
||||
public void UpdateBalance(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> balance)
|
||||
{
|
||||
Balance = balance;
|
||||
|
||||
var currency = balance.ToDictionary(type =>
|
||||
(type.Key, type.Value), type => _prototypeManager.Index<CurrencyPrototype>(type.Key));
|
||||
(type.Key, type.Value), type => _prototypeManager.Index(type.Key));
|
||||
|
||||
var balanceStr = string.Empty;
|
||||
foreach (var ((_, amount), proto) in currency)
|
||||
|
|
@ -80,7 +73,13 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
|
||||
public void UpdateListing(List<ListingData> listings)
|
||||
{
|
||||
var sorted = listings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
|
||||
_cachedListings = listings;
|
||||
UpdateListing();
|
||||
}
|
||||
|
||||
public void UpdateListing()
|
||||
{
|
||||
var sorted = _cachedListings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
|
||||
|
||||
// should probably chunk these out instead. to-do if this clogs the internet tubes.
|
||||
// maybe read clients prototypes instead?
|
||||
|
|
@ -96,12 +95,6 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
TraitorFooter.Visible = visible;
|
||||
}
|
||||
|
||||
|
||||
private void OnRefreshButtonDown(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
OnRefreshButtonPressed?.Invoke(args);
|
||||
}
|
||||
|
||||
private void OnWithdrawButtonDown(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
// check if window is already open
|
||||
|
|
@ -129,10 +122,8 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
if (!listing.Categories.Contains(CurrentCategory))
|
||||
return;
|
||||
|
||||
var listingName = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listing, _prototypeManager);
|
||||
var listingDesc = ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listing, _prototypeManager);
|
||||
var listingPrice = listing.Cost;
|
||||
var canBuy = CanBuyListing(Balance, listingPrice);
|
||||
var hasBalance = HasListingPrice(Balance, listingPrice);
|
||||
|
||||
var spriteSys = _entityManager.EntitySysManager.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
|
|
@ -154,39 +145,15 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
texture = spriteSys.Frame0(action.Icon);
|
||||
}
|
||||
}
|
||||
var listingInStock = ListingInStock(listing);
|
||||
if (listingInStock != GetListingPriceString(listing))
|
||||
{
|
||||
listingName += " (Out of stock)";
|
||||
canBuy = false;
|
||||
}
|
||||
|
||||
var newListing = new StoreListingControl(listingName, listingDesc, listingInStock, canBuy, texture);
|
||||
var newListing = new StoreListingControl(listing, GetListingPriceString(listing), hasBalance, texture);
|
||||
newListing.StoreItemBuyButton.OnButtonDown += args
|
||||
=> OnListingButtonPressed?.Invoke(args, listing);
|
||||
|
||||
StoreListingsContainer.AddChild(newListing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return time until available or the cost.
|
||||
/// </summary>
|
||||
/// <param name="listing"></param>
|
||||
/// <returns></returns>
|
||||
public string ListingInStock(ListingData listing)
|
||||
{
|
||||
var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
|
||||
|
||||
TimeSpan restockTimeSpan = TimeSpan.FromMinutes(listing.RestockTime);
|
||||
if (restockTimeSpan > stationTime)
|
||||
{
|
||||
var timeLeftToBuy = stationTime - restockTimeSpan;
|
||||
return timeLeftToBuy.Duration().ToString(@"mm\:ss");
|
||||
}
|
||||
|
||||
return GetListingPriceString(listing);
|
||||
}
|
||||
public bool CanBuyListing(Dictionary<string, FixedPoint2> currency, Dictionary<string, FixedPoint2> price)
|
||||
public bool HasListingPrice(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> currency, Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> price)
|
||||
{
|
||||
foreach (var type in price)
|
||||
{
|
||||
|
|
@ -208,7 +175,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
{
|
||||
foreach (var (type, amount) in listing.Cost)
|
||||
{
|
||||
var currency = _prototypeManager.Index<CurrencyPrototype>(type);
|
||||
var currency = _prototypeManager.Index(type);
|
||||
text += Loc.GetString("store-ui-price-display", ("amount", amount),
|
||||
("currency", Loc.GetString(currency.DisplayName, ("amount", amount))));
|
||||
}
|
||||
|
|
@ -229,7 +196,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
{
|
||||
foreach (var cat in listing.Categories)
|
||||
{
|
||||
var proto = _prototypeManager.Index<StoreCategoryPrototype>(cat);
|
||||
var proto = _prototypeManager.Index(cat);
|
||||
if (!allCategories.Contains(proto))
|
||||
allCategories.Add(proto);
|
||||
}
|
||||
|
|
@ -248,12 +215,17 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
if (allCategories.Count < 1)
|
||||
return;
|
||||
|
||||
var group = new ButtonGroup();
|
||||
foreach (var proto in allCategories)
|
||||
{
|
||||
var catButton = new StoreCategoryButton
|
||||
{
|
||||
Text = Loc.GetString(proto.Name),
|
||||
Id = proto.ID
|
||||
Id = proto.ID,
|
||||
Pressed = proto.ID == CurrentCategory,
|
||||
Group = group,
|
||||
ToggleMode = true,
|
||||
StyleClasses = { "OpenBoth" }
|
||||
};
|
||||
|
||||
catButton.OnPressed += args => OnCategoryButtonPressed?.Invoke(args, catButton.Id);
|
||||
|
|
@ -269,7 +241,7 @@ public sealed partial class StoreMenu : DefaultWindow
|
|||
|
||||
public void UpdateRefund(bool allowRefund)
|
||||
{
|
||||
RefundButton.Disabled = !allowRefund;
|
||||
RefundButton.Visible = allowRefund;
|
||||
}
|
||||
|
||||
private sealed class StoreCategoryButton : Button
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@ public sealed partial class StoreWithdrawWindow : DefaultWindow
|
|||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public void CreateCurrencyButtons(Dictionary<string, FixedPoint2> balance)
|
||||
public void CreateCurrencyButtons(Dictionary<ProtoId<CurrencyPrototype>, FixedPoint2> balance)
|
||||
{
|
||||
_validCurrencies.Clear();
|
||||
foreach (var currency in balance)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<CurrencyPrototype>(currency.Key, out var proto))
|
||||
if (!_prototypeManager.TryIndex(currency.Key, out var proto))
|
||||
continue;
|
||||
|
||||
_validCurrencies.Add(currency.Value, proto);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public sealed class CraftingTests : InteractionTest
|
|||
|
||||
// Player's hands should be full of the remaining rods, except those dropped during the failed crafting attempt.
|
||||
// Spear and left over stacks should be on the floor.
|
||||
await AssertEntityLookup((Rod, 2), (Cable, 8), (ShardGlass, 2), (Spear, 1));
|
||||
await AssertEntityLookup((Rod, 2), (Cable, 7), (ShardGlass, 2), (Spear, 1));
|
||||
}
|
||||
|
||||
// The following is wrapped in an if DEBUG. This is because of cursed state handling bugs. Tests don't (de)serialize
|
||||
|
|
@ -100,7 +100,7 @@ public sealed class CraftingTests : InteractionTest
|
|||
Assert.That(sys.IsEntityInContainer(rods), Is.False);
|
||||
Assert.That(sys.IsEntityInContainer(wires), Is.False);
|
||||
Assert.That(rodStack, Has.Count.EqualTo(8));
|
||||
Assert.That(wireStack, Has.Count.EqualTo(8));
|
||||
Assert.That(wireStack, Has.Count.EqualTo(7));
|
||||
|
||||
await FindEntity(Spear, shouldSucceed: false);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,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];
|
||||
|
||||
|
|
|
|||
|
|
@ -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, "Edge");
|
||||
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
|
||||
|
|
|
|||
1766
Content.Server.Database/Migrations/Postgres/20240409013837_FixRoundStartDateNullability.Designer.cs
generated
Normal file
1766
Content.Server.Database/Migrations/Postgres/20240409013837_FixRoundStartDateNullability.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Postgres
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixRoundStartDateNullability : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "round",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldDefaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.Sql("UPDATE round SET start_date = NULL WHERE start_date = '-Infinity';");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "round",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamp with time zone",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -913,10 +913,8 @@ namespace Content.Server.Database.Migrations.Postgres
|
|||
.HasColumnType("integer")
|
||||
.HasColumnName("server_id");
|
||||
|
||||
b.Property<DateTime>("StartDate")
|
||||
.ValueGeneratedOnAdd()
|
||||
b.Property<DateTime?>("StartDate")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
||||
.HasColumnName("start_date");
|
||||
|
||||
b.HasKey("Id")
|
||||
|
|
|
|||
1697
Content.Server.Database/Migrations/Sqlite/20240409013832_FixRoundStartDateNullability.Designer.cs
generated
Normal file
1697
Content.Server.Database/Migrations/Sqlite/20240409013832_FixRoundStartDateNullability.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,38 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixRoundStartDateNullability : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "round",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT",
|
||||
oldDefaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "start_date",
|
||||
table: "round",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
1697
Content.Server.Database/Migrations/Sqlite/20240409014937_FixRoundStartDateNullability2.Designer.cs
generated
Normal file
1697
Content.Server.Database/Migrations/Sqlite/20240409014937_FixRoundStartDateNullability2.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,25 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Content.Server.Database.Migrations.Sqlite
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixRoundStartDateNullability2 : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// This needs to be its own separate migration,
|
||||
// because EF Core re-arranges the order of the commands if it's a single migration...
|
||||
// (only relevant for SQLite since it needs cursed shit to do ALTER COLUMN)
|
||||
migrationBuilder.Sql("UPDATE round SET start_date = NULL WHERE start_date = '0001-01-01 00:00:00';");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -858,10 +858,8 @@ namespace Content.Server.Database.Migrations.Sqlite
|
|||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("server_id");
|
||||
|
||||
b.Property<DateTime>("StartDate")
|
||||
.ValueGeneratedOnAdd()
|
||||
b.Property<DateTime?>("StartDate")
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValue(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified))
|
||||
.HasColumnName("start_date");
|
||||
|
||||
b.HasKey("Id")
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue