mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-15 01:15:13 +01:00
Merge remote-tracking branch 'upstream/master' into upstream
This commit is contained in:
@@ -9,13 +9,14 @@ using Content.IntegrationTests.Pair;
|
||||
using Content.Shared.Clothing.Components;
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Item;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
@@ -32,7 +33,6 @@ public class ComponentQueryBenchmark
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private MapId _mapId = new(10);
|
||||
private EntityQuery<ItemComponent> _itemQuery;
|
||||
private EntityQuery<ClothingComponent> _clothingQuery;
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
@@ -54,10 +54,10 @@ public class ComponentQueryBenchmark
|
||||
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
|
||||
_pair.Server.WaitPost(() =>
|
||||
{
|
||||
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
|
||||
if (!success)
|
||||
var map = new ResPath(Map);
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
if (!_entMan.System<MapLoaderSystem>().TryLoadMap(map, out _, out _, opts))
|
||||
throw new Exception("Map load failed");
|
||||
_pair.Server.MapMan.DoMapInitialize(_mapId);
|
||||
}).GetAwaiter().GetResult();
|
||||
|
||||
_items = new EntityUid[_entMan.Count<ItemComponent>()];
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Content.Benchmarks
|
||||
for (var i = 0; i < Aabbs1.Length; i++)
|
||||
{
|
||||
var aabb = Aabbs1[i];
|
||||
_b2Tree.CreateProxy(aabb, i);
|
||||
_b2Tree.CreateProxy(aabb, uint.MaxValue, i);
|
||||
_tree.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@ using BenchmarkDotNet.Attributes;
|
||||
using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Maps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
@@ -20,7 +21,7 @@ public class MapLoadBenchmark
|
||||
{
|
||||
private TestPair _pair = default!;
|
||||
private MapLoaderSystem _mapLoader = default!;
|
||||
private IMapManager _mapManager = default!;
|
||||
private SharedMapSystem _mapSys = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
@@ -36,7 +37,7 @@ public class MapLoadBenchmark
|
||||
.ToDictionary(x => x.ID, x => x.MapPath.ToString());
|
||||
|
||||
_mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
|
||||
_mapManager = server.ResolveDependency<IMapManager>();
|
||||
_mapSys = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedMapSystem>();
|
||||
}
|
||||
|
||||
[GlobalCleanup]
|
||||
@@ -52,17 +53,19 @@ public class MapLoadBenchmark
|
||||
public string Map;
|
||||
|
||||
public Dictionary<string, string> Paths;
|
||||
private MapId _mapId;
|
||||
|
||||
[Benchmark]
|
||||
public async Task LoadMap()
|
||||
{
|
||||
var mapPath = Paths[Map];
|
||||
var mapPath = new ResPath(Paths[Map]);
|
||||
var server = _pair.Server;
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var success = _mapLoader.TryLoad(new MapId(10), mapPath, out _);
|
||||
var success = _mapLoader.TryLoadMap(mapPath, out var map, out _);
|
||||
if (!success)
|
||||
throw new Exception("Map load failed");
|
||||
_mapId = map.Value.Comp.MapId;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,9 +73,7 @@ public class MapLoadBenchmark
|
||||
public void IterationCleanup()
|
||||
{
|
||||
var server = _pair.Server;
|
||||
server.WaitPost(() =>
|
||||
{
|
||||
_mapManager.DeleteMap(new MapId(10));
|
||||
}).Wait();
|
||||
server.WaitPost(() => _mapSys.DeleteMap(_mapId))
|
||||
.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,15 @@ using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Warps;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
using Robust.Shared.EntitySerialization.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Benchmarks;
|
||||
|
||||
@@ -34,7 +36,6 @@ public class PvsBenchmark
|
||||
|
||||
private TestPair _pair = default!;
|
||||
private IEntityManager _entMan = default!;
|
||||
private MapId _mapId = new(10);
|
||||
private ICommonSession[] _players = default!;
|
||||
private EntityCoordinates[] _spawns = default!;
|
||||
public int _cycleOffset = 0;
|
||||
@@ -65,10 +66,10 @@ public class PvsBenchmark
|
||||
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
|
||||
await _pair.Server.WaitPost(() =>
|
||||
{
|
||||
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
|
||||
if (!success)
|
||||
var path = new ResPath(Map);
|
||||
var opts = DeserializationOptions.Default with {InitializeMaps = true};
|
||||
if (!_entMan.System<MapLoaderSystem>().TryLoadMap(path, out _, out _, opts))
|
||||
throw new Exception("Map load failed");
|
||||
_pair.Server.MapMan.DoMapInitialize(_mapId);
|
||||
});
|
||||
|
||||
// Get list of ghost warp positions
|
||||
|
||||
@@ -50,6 +50,8 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
|
||||
//TODO make this adjustable via GUI
|
||||
var classic = _config.GetCVar(CCVars.AdminOverlayClassic);
|
||||
var playTime = _config.GetCVar(CCVars.AdminOverlayPlaytime);
|
||||
var startingJob = _config.GetCVar(CCVars.AdminOverlayStartingJob);
|
||||
|
||||
foreach (var playerInfo in _system.PlayerList)
|
||||
{
|
||||
@@ -76,25 +78,44 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
}
|
||||
|
||||
var uiScale = _userInterfaceManager.RootControl.UIScale;
|
||||
var lineoffset = new Vector2(0f, 11f) * uiScale;
|
||||
var lineoffset = new Vector2(0f, 14f) * uiScale;
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
|
||||
var currentOffset = Vector2.Zero;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
if (!string.IsNullOrEmpty(playerInfo.PlaytimeString) && playTime)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.PlaytimeString, uiScale, playerInfo.Connected ? Color.Orange : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(playerInfo.StartingJob) && startingJob)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, Loc.GetString(playerInfo.StartingJob), uiScale, playerInfo.Connected ? Color.GreenYellow : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
if (classic && playerInfo.Antag)
|
||||
{
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), _antagLabelClassic, uiScale, _antagColorClassic);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, _antagLabelClassic, uiScale, Color.OrangeRed);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
else if (!classic && _filter.Contains(playerInfo.RoleProto.ID))
|
||||
else if (!classic && _filter.Contains(playerInfo.RoleProto))
|
||||
{
|
||||
var label = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
|
||||
var color = playerInfo.RoleProto.Color;
|
||||
var label = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
|
||||
var color = playerInfo.RoleProto.Color;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + (lineoffset * 2), label, uiScale, color);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + lineoffset, playerInfo.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : Color.White);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ namespace Content.Client.Administration.Systems
|
||||
OnBwoinkTextMessageRecieved?.Invoke(this, message);
|
||||
}
|
||||
|
||||
public void Send(NetUserId channelId, string text, bool playSound)
|
||||
public void Send(NetUserId channelId, string text, bool playSound, bool adminOnly)
|
||||
{
|
||||
// Reuse the channel ID as the 'true sender'.
|
||||
// Server will ignore this and if someone makes it not ignore this (which is bad, allows impersonation!!!), that will help.
|
||||
RaiseNetworkEvent(new BwoinkTextMessage(channelId, channelId, text, playSound: playSound));
|
||||
RaiseNetworkEvent(new BwoinkTextMessage(channelId, channelId, text, playSound: playSound, adminOnly: adminOnly));
|
||||
SendInputTextUpdated(channelId, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,22 +2,26 @@
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
<PanelContainer StyleClasses="BackgroundDark">
|
||||
<SplitContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<cc:PlayerListControl Access="Public" Name="ChannelSelector" HorizontalExpand="True" SizeFlagsStretchRatio="1" />
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<CheckBox Visible="True" Name="PlaySound" Access="Public" Text="{Loc 'admin-bwoink-play-sound'}" Pressed="True" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Follow" Text="{Loc 'admin-player-actions-follow'}" StyleClasses="OpenLeft" />
|
||||
<SplitContainer Orientation="Vertical">
|
||||
<SplitContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
<cc:PlayerListControl Access="Public" Name="ChannelSelector" HorizontalExpand="True" SizeFlagsStretchRatio="2" />
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Access="Public" Name="BwoinkArea" VerticalExpand="True" />
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<CheckBox Name="AdminOnly" Access="Public" Text="{Loc 'admin-ahelp-admin-only'}" ToolTip="{Loc 'admin-ahelp-admin-only-tooltip'}" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<CheckBox Name="PlaySound" Access="Public" Text="{Loc 'admin-bwoink-play-sound'}" Pressed="True" />
|
||||
<Control HorizontalExpand="True" MinWidth="5" />
|
||||
<Button Visible="True" Name="PopOut" Access="Public" Text="{Loc 'admin-logs-pop-out'}" StyleClasses="OpenBoth" HorizontalAlignment="Left" />
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Follow" Text="{Loc 'admin-player-actions-follow'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -36,6 +36,9 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
var newPlayerThreshold = 0;
|
||||
_cfg.OnValueChanged(CCVars.NewPlayerThreshold, (val) => { newPlayerThreshold = val; }, true);
|
||||
|
||||
var uiController = _ui.GetUIController<AHelpUIController>();
|
||||
if (uiController.UIHelper is not AdminAHelpUIHandler helper)
|
||||
return;
|
||||
@@ -45,6 +48,8 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
_adminManager.AdminStatusUpdated += UpdateButtons;
|
||||
UpdateButtons();
|
||||
|
||||
AdminOnly.OnToggled += args => PlaySound.Disabled = args.Pressed;
|
||||
|
||||
ChannelSelector.OnSelectionChanged += sel =>
|
||||
{
|
||||
_currentPlayer = sel;
|
||||
@@ -57,9 +62,9 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (info.Connected)
|
||||
sb.Append('●');
|
||||
sb.Append(info.ActiveThisRound ? '⚫' : '◐');
|
||||
else
|
||||
sb.Append(info.ActiveThisRound ? '○' : '·');
|
||||
sb.Append(info.ActiveThisRound ? '⭘' : '·');
|
||||
|
||||
sb.Append(' ');
|
||||
if (AHelpHelper.TryGetChannel(info.SessionId, out var panel) && panel.Unread > 0)
|
||||
@@ -71,10 +76,12 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
// Mark antagonists with symbol
|
||||
if (info.Antag && info.ActiveThisRound)
|
||||
sb.Append(new Rune(0x1F5E1)); // 🗡
|
||||
|
||||
if (info.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
|
||||
// Mark new players with symbol
|
||||
if (IsNewPlayer(info))
|
||||
sb.Append(new Rune(0x23F2)); // ⏲
|
||||
|
||||
sb.AppendFormat("\"{0}\"", text);
|
||||
@@ -82,6 +89,19 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
return sb.ToString();
|
||||
};
|
||||
|
||||
// <summary>
|
||||
// Returns true if the player's overall playtime is under the set threshold
|
||||
// </summary>
|
||||
bool IsNewPlayer(PlayerInfo info)
|
||||
{
|
||||
// Don't show every disconnected player as new, don't show 0-minute players as new if threshold is
|
||||
if (newPlayerThreshold <= 0 || info.OverallPlaytime is null && !info.Connected)
|
||||
return false;
|
||||
|
||||
return (info.OverallPlaytime is null
|
||||
|| info.OverallPlaytime < TimeSpan.FromMinutes(newPlayerThreshold));
|
||||
}
|
||||
|
||||
ChannelSelector.Comparison = (a, b) =>
|
||||
{
|
||||
var ach = AHelpHelper.EnsurePanel(a.SessionId);
|
||||
@@ -91,31 +111,37 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
if (a.IsPinned != b.IsPinned)
|
||||
return a.IsPinned ? -1 : 1;
|
||||
|
||||
// First, sort by unread. Any chat with unread messages appears first.
|
||||
// Then, any chat with unread messages.
|
||||
var aUnread = ach.Unread > 0;
|
||||
var bUnread = bch.Unread > 0;
|
||||
if (aUnread != bUnread)
|
||||
return aUnread ? -1 : 1;
|
||||
|
||||
// Sort by recent messages during the current round.
|
||||
// Then, any chat with recent messages from the current round
|
||||
var aRecent = a.ActiveThisRound && ach.LastMessage != DateTime.MinValue;
|
||||
var bRecent = b.ActiveThisRound && bch.LastMessage != DateTime.MinValue;
|
||||
if (aRecent != bRecent)
|
||||
return aRecent ? -1 : 1;
|
||||
|
||||
// Next, sort by connection status. Any disconnected players are grouped towards the end.
|
||||
// Sort by connection status. Disconnected players will be last.
|
||||
if (a.Connected != b.Connected)
|
||||
return a.Connected ? -1 : 1;
|
||||
|
||||
// Sort connected players by New Player status, then by Antag status
|
||||
// Sort connected players by whether they have joined the round, then by New Player status, then by Antag status
|
||||
if (a.Connected && b.Connected)
|
||||
{
|
||||
var aNewPlayer = a.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
|
||||
var bNewPlayer = b.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold));
|
||||
var aNewPlayer = IsNewPlayer(a);
|
||||
var bNewPlayer = IsNewPlayer(b);
|
||||
|
||||
// Players who have joined the round will be listed before players in the lobby
|
||||
if (a.ActiveThisRound != b.ActiveThisRound)
|
||||
return a.ActiveThisRound ? -1 : 1;
|
||||
|
||||
// Within both the joined group and lobby group, new players will be grouped and listed first
|
||||
if (aNewPlayer != bNewPlayer)
|
||||
return aNewPlayer ? -1 : 1;
|
||||
|
||||
// Within all four previous groups, antagonists will be listed first.
|
||||
if (a.Antag != b.Antag)
|
||||
return a.Antag ? -1 : 1;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,9 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
return;
|
||||
}
|
||||
|
||||
Title = $"{sel.CharacterName} / {sel.Username}";
|
||||
Title = $"{sel.CharacterName} / {sel.Username} | {Loc.GetString("generic-playtime-title")}: ";
|
||||
|
||||
if (sel.OverallPlaytime != null)
|
||||
{
|
||||
Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
|
||||
}
|
||||
Title += sel.OverallPlaytime != null ? sel.PlaytimeString : Loc.GetString("generic-unknown-title");
|
||||
};
|
||||
|
||||
OnOpen += () =>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Advertise.Systems;
|
||||
|
||||
namespace Content.Client.Advertise.Systems;
|
||||
|
||||
public sealed class SpeakOnUIClosedSystem : SharedSpeakOnUIClosedSystem;
|
||||
@@ -512,39 +512,15 @@ public sealed partial class AtmosAlertsComputerWindow : FancyWindow
|
||||
if (scroll == null)
|
||||
return;
|
||||
|
||||
if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (!TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
return;
|
||||
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
scroll.VScrollTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
if (MathHelper.CloseToPercent(scroll.VScroll, scroll.VScrollTarget))
|
||||
_autoScrollActive = false;
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var child in scroll.Children)
|
||||
{
|
||||
if (child is not VScrollBar)
|
||||
continue;
|
||||
|
||||
var castChild = child as VScrollBar;
|
||||
|
||||
if (castChild != null)
|
||||
{
|
||||
vScrollBar = castChild;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
|
||||
{
|
||||
nextScrollPosition = null;
|
||||
|
||||
@@ -350,35 +350,15 @@ public sealed partial class AtmosMonitoringConsoleWindow : FancyWindow
|
||||
if (scroll == null)
|
||||
return;
|
||||
|
||||
if (!TryGetVerticalScrollbar(scroll, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (!TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
return;
|
||||
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
scroll.VScrollTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
if (MathHelper.CloseToPercent(scroll.VScroll, scroll.VScrollTarget))
|
||||
_autoScrollActive = false;
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var control in scroll.Children)
|
||||
{
|
||||
if (control is not VScrollBar)
|
||||
continue;
|
||||
|
||||
vScrollBar = (VScrollBar)control;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
|
||||
{
|
||||
nextScrollPosition = null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Content.Shared.Atmos.Components.GasAnalyzerComponent;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
@@ -16,9 +17,7 @@ namespace Content.Client.Atmos.UI
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new GasAnalyzerWindow();
|
||||
_window.OnClose += OnClose;
|
||||
_window.OpenCenteredLeft();
|
||||
_window = this.CreateWindowCenteredLeft<GasAnalyzerWindow>();
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
|
||||
@@ -66,7 +66,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
{
|
||||
if(!_adminAudioEnabled) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
var stream = _audio.PlayGlobal(soundEvent.Specifier, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_adminAudio.Add(stream?.Entity);
|
||||
}
|
||||
|
||||
@@ -75,13 +75,13 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
// Either the cvar is disabled or it's already playing
|
||||
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
var stream = _audio.PlayGlobal(soundEvent.Specifier, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_eventAudio.Add(soundEvent.Type, stream?.Entity);
|
||||
}
|
||||
|
||||
private void PlayGameSound(GameGlobalSoundEvent soundEvent)
|
||||
{
|
||||
_audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_audio.PlayGlobal(soundEvent.Specifier, Filter.Local(), false, soundEvent.AudioParams);
|
||||
}
|
||||
|
||||
private void StopStationEventMusic(StopStationEventMusic soundEvent)
|
||||
|
||||
@@ -218,7 +218,7 @@ public sealed partial class ContentAudioSystem
|
||||
return;
|
||||
|
||||
var file = _gameTicker.RestartSound;
|
||||
if (string.IsNullOrEmpty(file))
|
||||
if (ResolvedSoundSpecifier.IsNullOrEmpty(file))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
@@ -13,16 +14,23 @@ public sealed partial class LogProbeUi : UIFragment
|
||||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
public override void Setup(BoundUserInterface ui, EntityUid? fragmentOwner)
|
||||
{
|
||||
_fragment = new LogProbeUiFragment();
|
||||
|
||||
_fragment.OnPrintPressed += () =>
|
||||
{
|
||||
var ev = new LogProbePrintMessage();
|
||||
var message = new CartridgeUiMessage(ev);
|
||||
ui.SendMessage(message);
|
||||
};
|
||||
}
|
||||
|
||||
public override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
if (state is not LogProbeUiState logProbeUiState)
|
||||
if (state is not LogProbeUiState cast)
|
||||
return;
|
||||
|
||||
_fragment?.UpdateState(logProbeUiState.PulledLogs);
|
||||
_fragment?.UpdateState(cast.EntityName, cast.PulledLogs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,9 @@
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="True">
|
||||
<BoxContainer Orientation="Vertical" Name="ProbedDeviceContainer"/>
|
||||
</ScrollContainer>
|
||||
<BoxContainer Orientation="Horizontal" Margin="4 8">
|
||||
<Button Name="PrintButton" HorizontalAlignment="Left" Text="{Loc 'log-probe-print-button'}" Disabled="True"/>
|
||||
<BoxContainer HorizontalExpand="True"/>
|
||||
<Label Name="EntityName" Align="Right"/>
|
||||
</BoxContainer>
|
||||
</cartridges:LogProbeUiFragment>
|
||||
|
||||
@@ -8,17 +8,24 @@ namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class LogProbeUiFragment : BoxContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Action invoked when the print button gets pressed.
|
||||
/// </summary>
|
||||
public Action? OnPrintPressed;
|
||||
|
||||
public LogProbeUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
PrintButton.OnPressed += _ => OnPrintPressed?.Invoke();
|
||||
}
|
||||
|
||||
public void UpdateState(List<PulledAccessLog> logs)
|
||||
public void UpdateState(string name, List<PulledAccessLog> logs)
|
||||
{
|
||||
ProbedDeviceContainer.RemoveAllChildren();
|
||||
EntityName.Text = name;
|
||||
PrintButton.Disabled = string.IsNullOrEmpty(name);
|
||||
|
||||
//Reverse the list so the oldest entries appear at the bottom
|
||||
logs.Reverse();
|
||||
ProbedDeviceContainer.RemoveAllChildren();
|
||||
|
||||
var count = 1;
|
||||
foreach (var log in logs)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
public sealed class NanoTaskCartridgeSystem : SharedNanoTaskCartridgeSystem;
|
||||
@@ -0,0 +1,32 @@
|
||||
<Control xmlns="https://spacestation14.io" xmlns:system="clr-namespace:System;assembly=System.Runtime">
|
||||
<BoxContainer Name="MainContainer"
|
||||
Orientation="Horizontal"
|
||||
SetWidth="250">
|
||||
<Button Name="MainButton"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
StyleClasses="ButtonSquare"
|
||||
Margin="-1 0 0 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="-5 0 0 0">
|
||||
<Label Name="TaskLabel"
|
||||
StyleClasses="LabelSubText" />
|
||||
<Label Name="TaskForLabel"
|
||||
StyleClasses="LabelSubText"
|
||||
Margin="0 -5 0 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
<Button Name="DoneButton"
|
||||
VerticalExpand="True"
|
||||
Text="{Loc 'nano-task-ui-done'}">
|
||||
<Button.StyleClasses>
|
||||
<system:String>ButtonSmall</system:String>
|
||||
<system:String>OpenLeft</system:String>
|
||||
</Button.StyleClasses>
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,33 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Maths;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single control for a single NanoTask item
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoTaskItemControl : Control
|
||||
{
|
||||
public Action<int>? OnMainPressed;
|
||||
public Action<int>? OnDonePressed;
|
||||
|
||||
public NanoTaskItemControl(NanoTaskItemAndId item)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
TaskLabel.Text = item.Data.Description;
|
||||
TaskLabel.FontColorOverride = Color.White;
|
||||
TaskForLabel.Text = item.Data.TaskIsFor;
|
||||
|
||||
MainButton.OnPressed += _ => OnMainPressed?.Invoke(item.Id);
|
||||
DoneButton.OnPressed += _ => OnDonePressed?.Invoke(item.Id);
|
||||
|
||||
MainButton.Disabled = item.Data.IsTaskDone;
|
||||
DoneButton.Text = item.Data.IsTaskDone ? Loc.GetString("nano-task-ui-revert-done") : Loc.GetString("nano-task-ui-done");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc nano-task-ui-item-title}"
|
||||
MinSize="300 300">
|
||||
<PanelContainer StyleClasses="AngleRect">
|
||||
<BoxContainer Orientation="Vertical" Margin="4">
|
||||
<!-- Task Description Input -->
|
||||
<BoxContainer Orientation="Vertical" Margin="0 4">
|
||||
<Label Text="{Loc nano-task-ui-description-label}"
|
||||
StyleClasses="LabelHeading" />
|
||||
<PanelContainer StyleClasses="ButtonSquare">
|
||||
<LineEdit Name="DescriptionInput"
|
||||
PlaceHolder="{Loc nano-task-ui-description-placeholder}" />
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Task Requester Input -->
|
||||
<BoxContainer Orientation="Vertical" Margin="0 4">
|
||||
<Label Text="{Loc nano-task-ui-requester-label}"
|
||||
StyleClasses="LabelHeading" />
|
||||
<PanelContainer StyleClasses="ButtonSquare">
|
||||
<LineEdit Name="RequesterInput"
|
||||
PlaceHolder="{Loc nano-task-ui-requester-placeholder}" />
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Severity Buttons -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalAlignment="Center"
|
||||
Margin="0 8 0 0">
|
||||
<Button Name="LowButton"
|
||||
Text="{Loc nano-task-ui-priority-low}"
|
||||
StyleClasses="OpenRight"
|
||||
MinSize="60 0" />
|
||||
<Button Name="MediumButton"
|
||||
Text="{Loc nano-task-ui-priority-medium}"
|
||||
StyleClasses="ButtonSquare"
|
||||
MinSize="60 0" />
|
||||
<Button Name="HighButton"
|
||||
Text="{Loc nano-task-ui-priority-high}"
|
||||
StyleClasses="OpenLeft"
|
||||
MinSize="60 0" />
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Verb Buttons -->
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="0 8 0 0">
|
||||
<Button Name="CancelButton"
|
||||
Text="{Loc nano-task-ui-cancel}"
|
||||
StyleClasses="OpenRight"
|
||||
MinSize="60 0" />
|
||||
<Button Name="DeleteButton"
|
||||
Text="{Loc nano-task-ui-delete}"
|
||||
StyleClasses="ButtonSquare"
|
||||
MinSize="60 0" />
|
||||
<Button Name="PrintButton"
|
||||
Text="{Loc nano-task-ui-print}"
|
||||
StyleClasses="ButtonSquare"
|
||||
MinSize="60 0" />
|
||||
<Button Name="SaveButton"
|
||||
Text="{Loc nano-task-ui-save}"
|
||||
StyleClasses="OpenLeft"
|
||||
MinSize="60 0" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,109 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
/// <summary>
|
||||
/// Popup displayed to edit a NanoTask item
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoTaskItemPopup : DefaultWindow
|
||||
{
|
||||
private readonly ButtonGroup _priorityGroup = new();
|
||||
private int? _editingTaskId = null;
|
||||
|
||||
public Action<int, NanoTaskItem>? TaskSaved;
|
||||
public Action<int>? TaskDeleted;
|
||||
public Action<NanoTaskItem>? TaskCreated;
|
||||
public Action<NanoTaskItem>? TaskPrinted;
|
||||
|
||||
private NanoTaskItem MakeItem()
|
||||
{
|
||||
return new(
|
||||
description: DescriptionInput.Text,
|
||||
taskIsFor: RequesterInput.Text,
|
||||
isTaskDone: false,
|
||||
priority: _priorityGroup.Pressed switch {
|
||||
var item when item == LowButton => NanoTaskPriority.Low,
|
||||
var item when item == MediumButton => NanoTaskPriority.Medium,
|
||||
var item when item == HighButton => NanoTaskPriority.High,
|
||||
_ => NanoTaskPriority.Medium,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public NanoTaskItemPopup()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
LowButton.Group = _priorityGroup;
|
||||
MediumButton.Group = _priorityGroup;
|
||||
HighButton.Group = _priorityGroup;
|
||||
|
||||
CancelButton.OnPressed += _ => Close();
|
||||
DeleteButton.OnPressed += _ =>
|
||||
{
|
||||
if (_editingTaskId is int id)
|
||||
{
|
||||
TaskDeleted?.Invoke(id);
|
||||
}
|
||||
};
|
||||
PrintButton.OnPressed += _ =>
|
||||
{
|
||||
TaskPrinted?.Invoke(MakeItem());
|
||||
};
|
||||
SaveButton.OnPressed += _ =>
|
||||
{
|
||||
if (_editingTaskId is int id)
|
||||
{
|
||||
TaskSaved?.Invoke(id, MakeItem());
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskCreated?.Invoke(MakeItem());
|
||||
}
|
||||
};
|
||||
|
||||
DescriptionInput.OnTextChanged += args =>
|
||||
{
|
||||
if (args.Text.Length > NanoTaskItem.MaximumStringLength)
|
||||
DescriptionInput.Text = args.Text[..NanoTaskItem.MaximumStringLength];
|
||||
};
|
||||
RequesterInput.OnTextChanged += args =>
|
||||
{
|
||||
if (args.Text.Length > NanoTaskItem.MaximumStringLength)
|
||||
RequesterInput.Text = args.Text[..NanoTaskItem.MaximumStringLength];
|
||||
};
|
||||
}
|
||||
|
||||
public void SetEditingTaskId(int? id)
|
||||
{
|
||||
_editingTaskId = id;
|
||||
DeleteButton.Visible = id is not null;
|
||||
}
|
||||
|
||||
public void ResetInputs(NanoTaskItem? item)
|
||||
{
|
||||
if (item is NanoTaskItem task)
|
||||
{
|
||||
var button = task.Priority switch {
|
||||
NanoTaskPriority.High => HighButton,
|
||||
NanoTaskPriority.Medium => MediumButton,
|
||||
NanoTaskPriority.Low => LowButton,
|
||||
};
|
||||
button.Pressed = true;
|
||||
DescriptionInput.Text = task.Description;
|
||||
RequesterInput.Text = task.TaskIsFor;
|
||||
}
|
||||
else
|
||||
{
|
||||
MediumButton.Pressed = true;
|
||||
DescriptionInput.Text = "";
|
||||
RequesterInput.Text = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs
Normal file
82
Content.Client/CartridgeLoader/Cartridges/NanoTaskUi.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
/// <summary>
|
||||
/// UI fragment responsible for displaying NanoTask controls in a PDA and coordinating with the NanoTaskCartridgeSystem for state
|
||||
/// </summary>
|
||||
public sealed partial class NanoTaskUi : UIFragment
|
||||
{
|
||||
private NanoTaskUiFragment? _fragment;
|
||||
private NanoTaskItemPopup? _popup;
|
||||
|
||||
public override Control GetUIFragmentRoot()
|
||||
{
|
||||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
{
|
||||
_fragment = new NanoTaskUiFragment();
|
||||
_popup = new NanoTaskItemPopup();
|
||||
_fragment.NewTask += () =>
|
||||
{
|
||||
_popup.ResetInputs(null);
|
||||
_popup.SetEditingTaskId(null);
|
||||
_popup.OpenCentered();
|
||||
};
|
||||
_fragment.OpenTask += id =>
|
||||
{
|
||||
if (_fragment.Tasks.Find(task => task.Id == id) is not NanoTaskItemAndId task)
|
||||
return;
|
||||
|
||||
_popup.ResetInputs(task.Data);
|
||||
_popup.SetEditingTaskId(task.Id);
|
||||
_popup.OpenCentered();
|
||||
};
|
||||
_fragment.ToggleTaskCompletion += id =>
|
||||
{
|
||||
if (_fragment.Tasks.Find(task => task.Id == id) is not NanoTaskItemAndId task)
|
||||
return;
|
||||
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, new(
|
||||
description: task.Data.Description,
|
||||
taskIsFor: task.Data.TaskIsFor,
|
||||
isTaskDone: !task.Data.IsTaskDone,
|
||||
priority: task.Data.Priority
|
||||
))))));
|
||||
};
|
||||
_popup.TaskSaved += (id, data) =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskUpdateTask(new(id, data)))));
|
||||
_popup.Close();
|
||||
};
|
||||
_popup.TaskDeleted += id =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskDeleteTask(id))));
|
||||
_popup.Close();
|
||||
};
|
||||
_popup.TaskCreated += data =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskAddTask(data))));
|
||||
_popup.Close();
|
||||
};
|
||||
_popup.TaskPrinted += data =>
|
||||
{
|
||||
userInterface.SendMessage(new CartridgeUiMessage(new NanoTaskUiMessageEvent(new NanoTaskPrintTask(data))));
|
||||
};
|
||||
}
|
||||
|
||||
public override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
if (state is not NanoTaskUiState nanoTaskState)
|
||||
return;
|
||||
|
||||
_fragment?.UpdateState(nanoTaskState.Tasks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<cartridges:NanoTaskUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns="https://spacestation14.io" Margin="1 0 2 0">
|
||||
|
||||
<PanelContainer StyleClasses="BackgroundDark"></PanelContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Vertical" Margin="8" SeparationOverride="8">
|
||||
<!-- Heading for High Priority Items -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<PanelContainer SetWidth="7" Margin="0 0 8 0">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#e93d58"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<Label Name="HighPriority" StyleClasses="LabelHeading"/>
|
||||
</BoxContainer>
|
||||
<!-- Location for High Priority Items -->
|
||||
<GridContainer Name="HighContainer"
|
||||
HorizontalExpand="True"
|
||||
Access="Public"
|
||||
Columns="2" />
|
||||
|
||||
<!-- Heading for Medium Priority Items -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<PanelContainer SetWidth="7" Margin="0 0 8 0">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#ef973c"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<Label Name="MediumPriority" StyleClasses="LabelHeading"/>
|
||||
</BoxContainer>
|
||||
<!-- Location for Medium Priority Items -->
|
||||
<GridContainer Name="MediumContainer"
|
||||
HorizontalExpand="True"
|
||||
Access="Public"
|
||||
Columns="2" />
|
||||
|
||||
<!-- Location for Low Priority Items -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<PanelContainer SetWidth="7" Margin="0 0 8 0">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#3dd425"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<Label Name="LowPriority" StyleClasses="LabelHeading"/>
|
||||
</BoxContainer>
|
||||
<!-- Location for Low Priority Items -->
|
||||
<GridContainer Name="LowContainer"
|
||||
HorizontalExpand="True"
|
||||
Access="Public"
|
||||
Columns="2" />
|
||||
|
||||
<Button Name="NewTaskButton" Text="{Loc 'nano-task-ui-new-task'}" HorizontalAlignment="Right"/>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</cartridges:NanoTaskUiFragment>
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Linq;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
/// <summary>
|
||||
/// Class displaying the main UI of NanoTask
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class NanoTaskUiFragment : BoxContainer
|
||||
{
|
||||
public Action<int>? OpenTask;
|
||||
public Action<int>? ToggleTaskCompletion;
|
||||
public Action? NewTask;
|
||||
public List<NanoTaskItemAndId> Tasks = new();
|
||||
|
||||
public NanoTaskUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
HorizontalExpand = true;
|
||||
VerticalExpand = true;
|
||||
NewTaskButton.OnPressed += _ => NewTask?.Invoke();
|
||||
}
|
||||
public void UpdateState(List<NanoTaskItemAndId> tasks)
|
||||
{
|
||||
Tasks = tasks;
|
||||
HighContainer.RemoveAllChildren();
|
||||
MediumContainer.RemoveAllChildren();
|
||||
LowContainer.RemoveAllChildren();
|
||||
|
||||
HighPriority.Text = Loc.GetString("nano-task-ui-heading-high-priority-tasks", ("amount", tasks.Count(task => task.Data.Priority == NanoTaskPriority.High)));
|
||||
MediumPriority.Text = Loc.GetString("nano-task-ui-heading-medium-priority-tasks", ("amount", tasks.Count(task => task.Data.Priority == NanoTaskPriority.Medium)));
|
||||
LowPriority.Text = Loc.GetString("nano-task-ui-heading-low-priority-tasks", ("amount", tasks.Count(task => task.Data.Priority == NanoTaskPriority.Low)));
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
var container = task.Data.Priority switch {
|
||||
NanoTaskPriority.High => HighContainer,
|
||||
NanoTaskPriority.Medium => MediumContainer,
|
||||
NanoTaskPriority.Low => LowContainer,
|
||||
};
|
||||
var control = new NanoTaskItemControl(task);
|
||||
container.AddChild(control);
|
||||
control.OnMainPressed += id => OpenTask?.Invoke(id);
|
||||
control.OnDonePressed += id => ToggleTaskCompletion?.Invoke(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
@@ -125,6 +126,27 @@ namespace Content.Client.Changelog
|
||||
_sawmill = _logManager.GetSawmill(SawmillName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to return a human-readable version number from the build.json file
|
||||
/// </summary>
|
||||
public string GetClientVersion()
|
||||
{
|
||||
var fork = _configManager.GetCVar(CVars.BuildForkId);
|
||||
var version = _configManager.GetCVar(CVars.BuildVersion);
|
||||
|
||||
// This trimming might become annoying if down the line some codebases want to switch to a real
|
||||
// version format like "104.11.3" while others are still using the git hashes
|
||||
if (version.Length > 7)
|
||||
version = version[..7];
|
||||
|
||||
if (string.IsNullOrEmpty(version) || string.IsNullOrEmpty(fork))
|
||||
return Loc.GetString("changelog-version-unknown");
|
||||
|
||||
return Loc.GetString("changelog-version-tag",
|
||||
("fork", fork),
|
||||
("version", version));
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Changelog
|
||||
{
|
||||
|
||||
@@ -70,22 +70,7 @@ namespace Content.Client.Changelog
|
||||
Tabs.SetTabTitle(i++, Loc.GetString($"changelog-tab-title-{changelog.Name}"));
|
||||
}
|
||||
|
||||
// Try to get the current version from the build.json file
|
||||
var version = _cfg.GetCVar(CVars.BuildVersion);
|
||||
var forkId = _cfg.GetCVar(CVars.BuildForkId);
|
||||
|
||||
var versionText = Loc.GetString("changelog-version-unknown");
|
||||
|
||||
// Make sure these aren't empty, like in a dev env
|
||||
if (!string.IsNullOrEmpty(version) && !string.IsNullOrEmpty(forkId))
|
||||
{
|
||||
versionText = Loc.GetString("changelog-version-tag",
|
||||
("fork", forkId),
|
||||
("version", version[..7])); // Only show the first 7 characters
|
||||
}
|
||||
|
||||
// if else statements are ugly, shut up
|
||||
VersionLabel.Text = versionText;
|
||||
VersionLabel.Text = _changelog.GetClientVersion();
|
||||
|
||||
TabsUpdated();
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ namespace Content.Client.Chat.UI
|
||||
{
|
||||
StyleClasses = { "speechBox", speechStyleClass },
|
||||
Children = { label },
|
||||
ModulateSelfOverride = Color.White.WithAlpha(0.75f)
|
||||
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity))
|
||||
};
|
||||
|
||||
return panel;
|
||||
@@ -247,21 +247,23 @@ namespace Content.Client.Chat.UI
|
||||
{
|
||||
StyleClasses = { "speechBox", speechStyleClass },
|
||||
Children = { label },
|
||||
ModulateSelfOverride = Color.White.WithAlpha(0.75f)
|
||||
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity)),
|
||||
};
|
||||
return unfanciedPanel;
|
||||
}
|
||||
|
||||
var bubbleHeader = new RichTextLabel
|
||||
{
|
||||
Margin = new Thickness(1, 1, 1, 1)
|
||||
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleSpeakerOpacity)),
|
||||
Margin = new Thickness(1, 1, 1, 1),
|
||||
};
|
||||
|
||||
var bubbleContent = new RichTextLabel
|
||||
{
|
||||
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleTextOpacity)),
|
||||
MaxWidth = SpeechMaxWidth,
|
||||
Margin = new Thickness(2, 6, 2, 2),
|
||||
StyleClasses = { "bubbleContent" }
|
||||
StyleClasses = { "bubbleContent" },
|
||||
};
|
||||
|
||||
//We'll be honest. *Yes* this is hacky. Doing this in a cleaner way would require a bottom-up refactor of how saycode handles sending chat messages. -Myr
|
||||
@@ -273,7 +275,7 @@ namespace Content.Client.Chat.UI
|
||||
{
|
||||
StyleClasses = { "speechBox", speechStyleClass },
|
||||
Children = { bubbleContent },
|
||||
ModulateSelfOverride = Color.White.WithAlpha(0.75f),
|
||||
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity)),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Bottom,
|
||||
Margin = new Thickness(4, 14, 4, 2)
|
||||
@@ -283,7 +285,7 @@ namespace Content.Client.Chat.UI
|
||||
{
|
||||
StyleClasses = { "speechBox", speechStyleClass },
|
||||
Children = { bubbleHeader },
|
||||
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.ChatFancyNameBackground) ? 0.75f : 0f),
|
||||
ModulateSelfOverride = Color.White.WithAlpha(ConfigManager.GetCVar(CCVars.ChatFancyNameBackground) ? ConfigManager.GetCVar(CCVars.SpeechBubbleBackgroundOpacity) : 0f),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
VerticalAlignment = VAlignment.Top
|
||||
};
|
||||
|
||||
@@ -94,7 +94,7 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
||||
if (entProto.Abstract || usedNames.Contains(entProto.Name))
|
||||
continue;
|
||||
|
||||
if (!entProto.TryGetComponent<ExtractableComponent>(out var extractableComponent))
|
||||
if (!entProto.TryGetComponent<ExtractableComponent>(out var extractableComponent, EntityManager.ComponentFactory))
|
||||
continue;
|
||||
|
||||
//these bloat the hell out of blood/fat
|
||||
@@ -121,7 +121,7 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
||||
|
||||
|
||||
if (extractableComponent.GrindableSolution is { } grindableSolutionId &&
|
||||
entProto.TryGetComponent<SolutionContainerManagerComponent>(out var manager) &&
|
||||
entProto.TryGetComponent<SolutionContainerManagerComponent>(out var manager, EntityManager.ComponentFactory) &&
|
||||
_solutionContainer.TryGetSolution(manager, grindableSolutionId, out var grindableSolution))
|
||||
{
|
||||
var data = new ReagentEntitySourceData(
|
||||
@@ -141,6 +141,11 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
|
||||
{
|
||||
return _reagentSources.GetValueOrDefault(id) ?? new List<ReagentSourceData>();
|
||||
}
|
||||
|
||||
// Is handled on server and updated on client via ReagentGuideRegistryChangedEvent
|
||||
public override void ReloadAllReagentPrototypes()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -46,6 +46,8 @@ namespace Content.Client.Chemistry.UI
|
||||
_window.CreateBottleButton.OnPressed += _ => SendMessage(
|
||||
new ChemMasterOutputToBottleMessage(
|
||||
(uint) _window.BottleDosage.Value, _window.LabelLine));
|
||||
_window.BufferSortButton.OnPressed += _ => SendMessage(
|
||||
new ChemMasterSortingTypeCycleMessage());
|
||||
|
||||
for (uint i = 0; i < _window.PillTypeButtons.Length; i++)
|
||||
{
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<Label Text="{Loc 'chem-master-window-buffer-text'}" />
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button MinSize="80 0" Name="BufferTransferButton" Access="Public" Text="{Loc 'chem-master-window-transfer-button'}" ToggleMode="True" StyleClasses="OpenRight" />
|
||||
<Button MinSize="80 0" Name="BufferSortButton" Access="Public" Text="{Loc 'chem-master-window-sort-type-none'}" StyleClasses="OpenBoth" />
|
||||
<Button MinSize="80 0" Name="BufferDiscardButton" Access="Public" Text="{Loc 'chem-master-window-discard-button'}" ToggleMode="True" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
|
||||
|
||||
@@ -140,17 +140,17 @@ namespace Content.Client.Chemistry.UI
|
||||
|
||||
// Ensure the Panel Info is updated, including UI elements for Buffer Volume, Output Container and so on
|
||||
UpdatePanelInfo(castState);
|
||||
|
||||
|
||||
BufferCurrentVolume.Text = $" {castState.BufferCurrentVolume?.Int() ?? 0}u";
|
||||
|
||||
|
||||
InputEjectButton.Disabled = castState.InputContainerInfo is null;
|
||||
OutputEjectButton.Disabled = castState.OutputContainerInfo is null;
|
||||
CreateBottleButton.Disabled = castState.OutputContainerInfo?.Reagents == null;
|
||||
CreatePillButton.Disabled = castState.OutputContainerInfo?.Entities == null;
|
||||
|
||||
|
||||
UpdateDosageFields(castState);
|
||||
}
|
||||
|
||||
|
||||
//assign default values for pill and bottle fields.
|
||||
private void UpdateDosageFields(ChemMasterBoundUserInterfaceState castState)
|
||||
{
|
||||
@@ -162,8 +162,9 @@ namespace Content.Client.Chemistry.UI
|
||||
var bufferVolume = castState.BufferCurrentVolume?.Int() ?? 0;
|
||||
|
||||
PillDosage.Value = (int)Math.Min(bufferVolume, castState.PillDosageLimit);
|
||||
|
||||
|
||||
PillTypeButtons[castState.SelectedPillType].Pressed = true;
|
||||
|
||||
PillNumber.IsValid = x => x >= 0 && x <= pillNumberMax;
|
||||
PillDosage.IsValid = x => x > 0 && x <= castState.PillDosageLimit;
|
||||
BottleDosage.IsValid = x => x >= 0 && x <= bottleAmountMax;
|
||||
@@ -213,6 +214,17 @@ namespace Content.Client.Chemistry.UI
|
||||
|
||||
BufferInfo.Children.Clear();
|
||||
|
||||
// This has to happen here due to people possibly
|
||||
// setting sorting before putting any chemicals
|
||||
BufferSortButton.Text = state.SortingType switch
|
||||
{
|
||||
ChemMasterSortingType.Alphabetical => Loc.GetString("chem-master-window-sort-type-alphabetical"),
|
||||
ChemMasterSortingType.Quantity => Loc.GetString("chem-master-window-sort-type-quantity"),
|
||||
ChemMasterSortingType.Latest => Loc.GetString("chem-master-window-sort-type-latest"),
|
||||
_ => Loc.GetString("chem-master-window-sort-type-none")
|
||||
};
|
||||
|
||||
|
||||
if (!state.BufferReagents.Any())
|
||||
{
|
||||
BufferInfo.Children.Add(new Label { Text = Loc.GetString("chem-master-window-buffer-empty-text") });
|
||||
@@ -235,19 +247,48 @@ namespace Content.Client.Chemistry.UI
|
||||
};
|
||||
bufferHBox.AddChild(bufferVol);
|
||||
|
||||
// initialises rowCount to allow for striped rows
|
||||
|
||||
var rowCount = 0;
|
||||
// This sets up the needed data for sorting later in a list
|
||||
// Its done this way to not repeat having to use same code twice (once for sorting
|
||||
// and once for displaying)
|
||||
var reagentList = new List<(ReagentId reagentId, string name, Color color, FixedPoint2 quantity)>();
|
||||
foreach (var (reagent, quantity) in state.BufferReagents)
|
||||
{
|
||||
var reagentId = reagent;
|
||||
_prototypeManager.TryIndex(reagentId.Prototype, out ReagentPrototype? proto);
|
||||
var name = proto?.LocalizedName ?? Loc.GetString("chem-master-window-unknown-reagent-text");
|
||||
var reagentColor = proto?.SubstanceColor ?? default(Color);
|
||||
BufferInfo.Children.Add(BuildReagentRow(reagentColor, rowCount++, name, reagentId, quantity, true, true));
|
||||
reagentList.Add(new (reagentId, name, reagentColor, quantity));
|
||||
}
|
||||
|
||||
// We sort here since we need sorted list to be filled first.
|
||||
// You can easily add any new params you need to it.
|
||||
switch (state.SortingType)
|
||||
{
|
||||
case ChemMasterSortingType.Alphabetical:
|
||||
reagentList = reagentList.OrderBy(x => x.name).ToList();
|
||||
break;
|
||||
|
||||
case ChemMasterSortingType.Quantity:
|
||||
reagentList = reagentList.OrderByDescending(x => x.quantity).ToList();
|
||||
break;
|
||||
case ChemMasterSortingType.Latest:
|
||||
reagentList = Enumerable.Reverse(reagentList).ToList();
|
||||
break;
|
||||
|
||||
case ChemMasterSortingType.None:
|
||||
default:
|
||||
// This case is pointless but it is there for readability
|
||||
break;
|
||||
}
|
||||
|
||||
// initialises rowCount to allow for striped rows
|
||||
var rowCount = 0;
|
||||
foreach (var reagent in reagentList)
|
||||
{
|
||||
BufferInfo.Children.Add(BuildReagentRow(reagent.color, rowCount++, reagent.name, reagent.reagentId, reagent.quantity, true, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void BuildContainerUI(Control control, ContainerInfo? info, bool addReagentButtons)
|
||||
{
|
||||
control.Children.Clear();
|
||||
@@ -295,7 +336,7 @@ namespace Content.Client.Chemistry.UI
|
||||
_prototypeManager.TryIndex(reagent.Reagent.Prototype, out ReagentPrototype? proto);
|
||||
var name = proto?.LocalizedName ?? Loc.GetString("chem-master-window-unknown-reagent-text");
|
||||
var reagentColor = proto?.SubstanceColor ?? default(Color);
|
||||
|
||||
|
||||
control.Children.Add(BuildReagentRow(reagentColor, rowCount++, name, reagent.Reagent, reagent.Quantity, false, addReagentButtons));
|
||||
}
|
||||
}
|
||||
@@ -315,7 +356,7 @@ namespace Content.Client.Chemistry.UI
|
||||
}
|
||||
//this calls the separated button builder, and stores the return to render after labels
|
||||
var reagentButtonConstructors = CreateReagentTransferButtons(reagent, isBuffer, addReagentButtons);
|
||||
|
||||
|
||||
// Create the row layout with the color panel
|
||||
var rowContainer = new BoxContainer
|
||||
{
|
||||
@@ -358,7 +399,7 @@ namespace Content.Client.Chemistry.UI
|
||||
Children = { rowContainer }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public string LabelLine
|
||||
{
|
||||
get => LabelLineEdit.Text;
|
||||
|
||||
@@ -28,7 +28,6 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
||||
private void OnMapInit(EntityUid uid, SolutionContainerVisualsComponent component, MapInitEvent args)
|
||||
{
|
||||
var meta = MetaData(uid);
|
||||
component.InitialName = meta.EntityName;
|
||||
component.InitialDescription = meta.EntityDescription;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,29 +36,6 @@ internal sealed class ShowSubFloor : LocalizedCommands
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowSubFloorForever : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
public const string CommandName = "showsubfloorforever";
|
||||
public override string Command => CommandName;
|
||||
|
||||
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<SubFloorHideSystem>().ShowAll = true;
|
||||
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var components = entMan.EntityQuery<SubFloorHideComponent, SpriteComponent>(true);
|
||||
|
||||
foreach (var (_, sprite) in components)
|
||||
{
|
||||
sprite.DrawDepth = (int) DrawDepth.Overlays;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class NotifyCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
||||
{
|
||||
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
|
||||
_lightManager.Enabled = false;
|
||||
shell.ExecuteCommand("showsubfloorforever");
|
||||
shell.ExecuteCommand("showsubfloor");
|
||||
_entitySystemManager.GetEntitySystem<ActionsSystem>().LoadActionAssignments("/mapping_actions.yml", false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Content.Client.Construction
|
||||
[RegisterComponent]
|
||||
public sealed partial class ConstructionGhostComponent : Component
|
||||
{
|
||||
public int GhostId { get; set; }
|
||||
[ViewVariables] public ConstructionPrototype? Prototype { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,12 @@ namespace Content.Client.Construction
|
||||
.Register<ConstructionSystem>();
|
||||
|
||||
SubscribeLocalEvent<ConstructionGhostComponent, ExaminedEvent>(HandleConstructionGhostExamined);
|
||||
SubscribeLocalEvent<ConstructionGhostComponent, ComponentShutdown>(HandleGhostComponentShutdown);
|
||||
}
|
||||
|
||||
private void HandleGhostComponentShutdown(EntityUid uid, ConstructionGhostComponent component, ComponentShutdown args)
|
||||
{
|
||||
ClearGhost(component.GhostId);
|
||||
}
|
||||
|
||||
private void OnConstructionGuideReceived(ResponseConstructionGuide ev)
|
||||
@@ -205,8 +211,9 @@ namespace Content.Client.Construction
|
||||
ghost = EntityManager.SpawnEntity("constructionghost", loc);
|
||||
var comp = EntityManager.GetComponent<ConstructionGhostComponent>(ghost.Value);
|
||||
comp.Prototype = prototype;
|
||||
comp.GhostId = ghost.GetHashCode();
|
||||
EntityManager.GetComponent<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
|
||||
_ghosts.Add(ghost.GetHashCode(), ghost.Value);
|
||||
_ghosts.Add(comp.GhostId, ghost.Value);
|
||||
var sprite = EntityManager.GetComponent<SpriteComponent>(ghost.Value);
|
||||
sprite.Color = new Color(48, 255, 48, 128);
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class FlatpackSystem : SharedFlatpackSystem
|
||||
if (!PrototypeManager.TryIndex<EntityPrototype>(machineBoardId, out var machineBoardPrototype))
|
||||
return;
|
||||
|
||||
if (!machineBoardPrototype.TryGetComponent<SpriteComponent>(out var sprite))
|
||||
if (!machineBoardPrototype.TryGetComponent<SpriteComponent>(out var sprite, EntityManager.ComponentFactory))
|
||||
return;
|
||||
|
||||
Color? color = null;
|
||||
|
||||
@@ -83,14 +83,16 @@ public sealed class TTSSystem : EntitySystem
|
||||
.WithVolume(AdjustVolume(ev.IsWhisper))
|
||||
.WithMaxDistance(AdjustDistance(ev.IsWhisper));
|
||||
|
||||
var soundSpecifier = new ResolvedPathSpecifier(Prefix / filePath);
|
||||
|
||||
if (ev.SourceUid != null)
|
||||
{
|
||||
var sourceUid = GetEntity(ev.SourceUid.Value);
|
||||
_audio.PlayEntity(audioResource.AudioStream, sourceUid, audioParams);
|
||||
_audio.PlayEntity(audioResource.AudioStream, sourceUid, soundSpecifier, audioParams);
|
||||
}
|
||||
else
|
||||
{
|
||||
_audio.PlayGlobal(audioResource.AudioStream, audioParams);
|
||||
_audio.PlayGlobal(audioResource.AudioStream, soundSpecifier, audioParams);
|
||||
}
|
||||
|
||||
_contentRoot.RemoveFile(filePath);
|
||||
|
||||
@@ -21,11 +21,10 @@ namespace Content.Client.Crayon.UI
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_menu = this.CreateWindow<CrayonWindow>();
|
||||
_menu = this.CreateWindowCenteredLeft<CrayonWindow>();
|
||||
_menu.OnColorSelected += SelectColor;
|
||||
_menu.OnSelected += Select;
|
||||
PopulateCrayons();
|
||||
_menu.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
private void PopulateCrayons()
|
||||
|
||||
@@ -58,9 +58,7 @@
|
||||
StyleClasses="LabelBig" />
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
Margin="0 0 0 5">
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-job'}:"
|
||||
FontColorOverride="DarkGray" />
|
||||
<Label Text=":"
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-job'}"
|
||||
FontColorOverride="DarkGray" />
|
||||
<TextureRect Name="PersonJobIcon"
|
||||
TextureScale="2 2"
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CriminalRecords;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Security;
|
||||
using Content.Shared.StationRecords;
|
||||
using Robust.Client.AutoGenerated;
|
||||
@@ -32,7 +33,7 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
public readonly EntityUid Console;
|
||||
|
||||
[ValidatePrototypeId<DatasetPrototype>]
|
||||
[ValidatePrototypeId<LocalizedDatasetPrototype>]
|
||||
private const string ReasonPlaceholders = "CriminalRecordsWantedReasonPlaceholders";
|
||||
|
||||
public Action<uint?>? OnKeySelected;
|
||||
@@ -333,8 +334,8 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
|
||||
var field = "reason";
|
||||
var title = Loc.GetString("criminal-records-status-" + status.ToString().ToLower());
|
||||
var placeholders = _proto.Index<DatasetPrototype>(ReasonPlaceholders);
|
||||
var placeholder = Loc.GetString("criminal-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders.Values))); // just funny it doesn't actually get used
|
||||
var placeholders = _proto.Index<LocalizedDatasetPrototype>(ReasonPlaceholders);
|
||||
var placeholder = Loc.GetString("criminal-records-console-reason-placeholder", ("placeholder", _random.Pick(placeholders))); // just funny it doesn't actually get used
|
||||
var prompt = Loc.GetString("criminal-records-console-reason");
|
||||
var entry = new QuickDialogEntry(field, QuickDialogEntryType.LongText, prompt, placeholder);
|
||||
var entries = new List<QuickDialogEntry>() { entry };
|
||||
|
||||
5
Content.Client/Delivery/DeliverySystem.cs
Normal file
5
Content.Client/Delivery/DeliverySystem.cs
Normal file
@@ -0,0 +1,5 @@
|
||||
using Content.Shared.Delivery;
|
||||
|
||||
namespace Content.Client.Delivery;
|
||||
|
||||
public sealed class DeliverySystem : SharedDeliverySystem;
|
||||
45
Content.Client/Delivery/DeliveryVisualizerSystem.cs
Normal file
45
Content.Client/Delivery/DeliveryVisualizerSystem.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using Content.Shared.Delivery;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Delivery;
|
||||
|
||||
public sealed class DeliveryVisualizerSystem : VisualizerSystem<DeliveryComponent>
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
private static readonly ProtoId<JobIconPrototype> UnknownIcon = "JobIconUnknown";
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, DeliveryComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
_appearance.TryGetData(uid, DeliveryVisuals.JobIcon, out string job, args.Component);
|
||||
|
||||
if (string.IsNullOrEmpty(job))
|
||||
job = UnknownIcon;
|
||||
|
||||
if (!_prototype.TryIndex<JobIconPrototype>(job, out var icon))
|
||||
{
|
||||
args.Sprite.LayerSetTexture(DeliveryVisualLayers.JobStamp, _sprite.Frame0(_prototype.Index("JobIconUnknown")));
|
||||
return;
|
||||
}
|
||||
|
||||
args.Sprite.LayerSetTexture(DeliveryVisualLayers.JobStamp, _sprite.Frame0(icon.Icon));
|
||||
}
|
||||
}
|
||||
|
||||
public enum DeliveryVisualLayers : byte
|
||||
{
|
||||
Icon,
|
||||
Lock,
|
||||
FragileStamp,
|
||||
JobStamp,
|
||||
PriorityTape,
|
||||
Breakage,
|
||||
Trash,
|
||||
}
|
||||
@@ -5,17 +5,24 @@ namespace Content.Client.Dice;
|
||||
|
||||
public sealed class DiceSystem : SharedDiceSystem
|
||||
{
|
||||
protected override void UpdateVisuals(EntityUid uid, DiceComponent? die = null)
|
||||
public override void Initialize()
|
||||
{
|
||||
if (!Resolve(uid, ref die) || !TryComp(uid, out SpriteComponent? sprite))
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DiceComponent, AfterAutoHandleStateEvent>(OnDiceAfterHandleState);
|
||||
}
|
||||
|
||||
private void OnDiceAfterHandleState(Entity<DiceComponent> entity, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(entity, out var sprite))
|
||||
return;
|
||||
|
||||
// TODO maybe just move each diue to its own RSI?
|
||||
// TODO maybe just move each die to its own RSI?
|
||||
var state = sprite.LayerGetState(0).Name;
|
||||
if (state == null)
|
||||
return;
|
||||
|
||||
var prefix = state.Substring(0, state.IndexOf('_'));
|
||||
sprite.LayerSetState(0, $"{prefix}_{die.CurrentValue}");
|
||||
sprite.LayerSetState(0, $"{prefix}_{entity.Comp.CurrentValue}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
{
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(unit.FlushSound), 0)
|
||||
new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(unit.FlushSound), 0)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
|
||||
namespace Content.Client.GameTicking.Managers
|
||||
{
|
||||
@@ -26,7 +27,7 @@ namespace Content.Client.GameTicking.Managers
|
||||
|
||||
[ViewVariables] public bool AreWeReady { get; private set; }
|
||||
[ViewVariables] public bool IsGameStarted { get; private set; }
|
||||
[ViewVariables] public string? RestartSound { get; private set; }
|
||||
[ViewVariables] public ResolvedSoundSpecifier? RestartSound { get; private set; }
|
||||
[ViewVariables] public string? LobbyBackground { get; private set; }
|
||||
[ViewVariables] public bool DisallowedLateJoin { get; private set; }
|
||||
[ViewVariables] public string? ServerInfoBlob { get; private set; }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Changelog;
|
||||
using Content.Client.Hands;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Screens;
|
||||
@@ -7,6 +9,7 @@ using Content.Shared.CCVar;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -20,9 +23,11 @@ namespace Content.Client.Gameplay
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
[Dependency] private readonly ChangelogManager _changelog = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
private FpsCounter _fpsCounter = default!;
|
||||
private Label _version = default!;
|
||||
|
||||
public MainViewport Viewport => _uiManager.ActiveScreen!.GetWidget<MainViewport>()!;
|
||||
|
||||
@@ -40,6 +45,7 @@ namespace Content.Client.Gameplay
|
||||
base.Startup();
|
||||
|
||||
LoadMainScreen();
|
||||
_configurationManager.OnValueChanged(CCVars.UILayout, ReloadMainScreenValueChange);
|
||||
|
||||
// Add the hand-item overlay.
|
||||
_overlayManager.AddOverlay(new ShowHandItemOverlay());
|
||||
@@ -50,7 +56,24 @@ namespace Content.Client.Gameplay
|
||||
UserInterfaceManager.PopupRoot.AddChild(_fpsCounter);
|
||||
_fpsCounter.Visible = _configurationManager.GetCVar(CCVars.HudFpsCounterVisible);
|
||||
_configurationManager.OnValueChanged(CCVars.HudFpsCounterVisible, (show) => { _fpsCounter.Visible = show; });
|
||||
_configurationManager.OnValueChanged(CCVars.UILayout, ReloadMainScreenValueChange);
|
||||
|
||||
// Version number watermark.
|
||||
_version = new Label();
|
||||
_version.FontColorOverride = Color.FromHex("#FFFFFF20");
|
||||
_version.Text = _changelog.GetClientVersion();
|
||||
UserInterfaceManager.PopupRoot.AddChild(_version);
|
||||
_configurationManager.OnValueChanged(CCVars.HudVersionWatermark, (show) => { _version.Visible = VersionVisible(); }, true);
|
||||
_configurationManager.OnValueChanged(CCVars.ForceClientHudVersionWatermark, (show) => { _version.Visible = VersionVisible(); }, true);
|
||||
// TODO make this centered or something
|
||||
LayoutContainer.SetPosition(_version, new Vector2(70, 0));
|
||||
}
|
||||
|
||||
// This allows servers to force the watermark on clients
|
||||
private bool VersionVisible()
|
||||
{
|
||||
var client = _configurationManager.GetCVar(CCVars.HudVersionWatermark);
|
||||
var server = _configurationManager.GetCVar(CCVars.ForceClientHudVersionWatermark);
|
||||
return client || server;
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
|
||||
@@ -219,10 +219,12 @@ namespace Content.Client.Gameplay
|
||||
{
|
||||
entityToClick = GetClickedEntity(mousePosWorld);
|
||||
}
|
||||
var transformSystem = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
|
||||
var mapSystem = _entitySystemManager.GetEntitySystem<MapSystem>();
|
||||
|
||||
coordinates = _mapManager.TryFindGridAt(mousePosWorld, out _, out var grid) ?
|
||||
grid.MapToGrid(mousePosWorld) :
|
||||
EntityCoordinates.FromMap(_mapManager, mousePosWorld);
|
||||
coordinates = _mapManager.TryFindGridAt(mousePosWorld, out var uid, out _) ?
|
||||
mapSystem.MapToGrid(uid, mousePosWorld) :
|
||||
transformSystem.ToCoordinates(mousePosWorld);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace Content.Client.Ghost
|
||||
private void OnGhostState(EntityUid uid, GhostComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (TryComp<SpriteComponent>(uid, out var sprite))
|
||||
sprite.LayerSetColor(0, component.color);
|
||||
sprite.LayerSetColor(0, component.Color);
|
||||
|
||||
if (uid != _playerManager.LocalEntity)
|
||||
return;
|
||||
|
||||
35
Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml
Normal file
35
Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<PanelContainer xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Margin="5 5 5 5">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BorderThickness="1" BorderColor="#777777"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<PanelContainer HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<BoxContainer Name="IconContainer"/>
|
||||
<RichTextLabel Name="ResultName"/>
|
||||
</BoxContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#393c3f"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
|
||||
<GridContainer Columns="2" Margin="10">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'guidebook-microwave-ingredients-header'}"/>
|
||||
<GridContainer Columns="3" Name="IngredientsGrid"/>
|
||||
</BoxContainer>
|
||||
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||
<Label Text="{Loc 'guidebook-microwave-cook-time-header'}"/>
|
||||
<RichTextLabel Name="CookTimeLabel"/>
|
||||
</BoxContainer>
|
||||
</GridContainer>
|
||||
|
||||
<BoxContainer Margin="10">
|
||||
<RichTextLabel Name="ResultDescription" HorizontalAlignment="Left"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
183
Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml.cs
Normal file
183
Content.Client/Guidebook/Controls/GuideMicrowaveEmbed.xaml.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.Richtext;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.UserInterface.ControlExtensions;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Kitchen;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Guidebook.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Control for embedding a microwave recipe into a guidebook.
|
||||
/// </summary>
|
||||
[UsedImplicitly, GenerateTypedNameReferences]
|
||||
public sealed partial class GuideMicrowaveEmbed : PanelContainer, IDocumentTag, ISearchableControl
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public GuideMicrowaveEmbed()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
_sawmill = _logManager.GetSawmill("guidemicrowaveembed");
|
||||
}
|
||||
|
||||
public GuideMicrowaveEmbed(string recipe) : this()
|
||||
{
|
||||
GenerateControl(_prototype.Index<FoodRecipePrototype>(recipe));
|
||||
}
|
||||
|
||||
public GuideMicrowaveEmbed(FoodRecipePrototype recipe) : this()
|
||||
{
|
||||
GenerateControl(recipe);
|
||||
}
|
||||
|
||||
public bool CheckMatchesSearch(string query)
|
||||
{
|
||||
return this.ChildrenContainText(query);
|
||||
}
|
||||
|
||||
public void SetHiddenState(bool state, string query)
|
||||
{
|
||||
Visible = CheckMatchesSearch(query) ? state : !state;
|
||||
}
|
||||
|
||||
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
|
||||
{
|
||||
control = null;
|
||||
if (!args.TryGetValue("Recipe", out var id))
|
||||
{
|
||||
_sawmill.Error("Recipe embed tag is missing recipe prototype argument");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_prototype.TryIndex<FoodRecipePrototype>(id, out var recipe))
|
||||
{
|
||||
_sawmill.Error($"Specified recipe prototype \"{id}\" is not a valid recipe prototype");
|
||||
return false;
|
||||
}
|
||||
|
||||
GenerateControl(recipe);
|
||||
|
||||
control = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void GenerateHeader(FoodRecipePrototype recipe)
|
||||
{
|
||||
var entity = _prototype.Index<EntityPrototype>(recipe.Result);
|
||||
|
||||
IconContainer.AddChild(new GuideEntityEmbed(recipe.Result, false, false));
|
||||
ResultName.SetMarkup(entity.Name);
|
||||
ResultDescription.SetMarkup(entity.Description);
|
||||
}
|
||||
|
||||
private void GenerateSolidIngredients(FoodRecipePrototype recipe)
|
||||
{
|
||||
foreach (var (product, amount) in recipe.IngredientsSolids.OrderByDescending(p => p.Value))
|
||||
{
|
||||
var ingredient = _prototype.Index<EntityPrototype>(product);
|
||||
|
||||
IngredientsGrid.AddChild(new GuideEntityEmbed(product, false, false));
|
||||
|
||||
// solid name
|
||||
|
||||
var solidNameMsg = new FormattedMessage();
|
||||
solidNameMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-solid-name-display", ("ingredient", ingredient.Name)));
|
||||
solidNameMsg.Pop();
|
||||
|
||||
var solidNameLabel = new RichTextLabel();
|
||||
solidNameLabel.SetMessage(solidNameMsg);
|
||||
|
||||
IngredientsGrid.AddChild(solidNameLabel);
|
||||
|
||||
// solid quantity
|
||||
|
||||
var solidQuantityMsg = new FormattedMessage();
|
||||
solidQuantityMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-solid-quantity-display", ("amount", amount)));
|
||||
solidQuantityMsg.Pop();
|
||||
|
||||
var solidQuantityLabel = new RichTextLabel();
|
||||
solidQuantityLabel.SetMessage(solidQuantityMsg);
|
||||
|
||||
IngredientsGrid.AddChild(solidQuantityLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateLiquidIngredients(FoodRecipePrototype recipe)
|
||||
{
|
||||
foreach (var (product, amount) in recipe.IngredientsReagents.OrderByDescending(p => p.Value))
|
||||
{
|
||||
var reagent = _prototype.Index<ReagentPrototype>(product);
|
||||
|
||||
// liquid color
|
||||
|
||||
var liquidColorMsg = new FormattedMessage();
|
||||
liquidColorMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-color-display", ("color", reagent.SubstanceColor)));
|
||||
liquidColorMsg.Pop();
|
||||
|
||||
var liquidColorLabel = new RichTextLabel();
|
||||
liquidColorLabel.SetMessage(liquidColorMsg);
|
||||
liquidColorLabel.HorizontalAlignment = Control.HAlignment.Center;
|
||||
|
||||
IngredientsGrid.AddChild(liquidColorLabel);
|
||||
|
||||
// liquid name
|
||||
|
||||
var liquidNameMsg = new FormattedMessage();
|
||||
liquidNameMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-name-display", ("reagent", reagent.LocalizedName)));
|
||||
liquidNameMsg.Pop();
|
||||
|
||||
var liquidNameLabel = new RichTextLabel();
|
||||
liquidNameLabel.SetMessage(liquidNameMsg);
|
||||
|
||||
IngredientsGrid.AddChild(liquidNameLabel);
|
||||
|
||||
// liquid quantity
|
||||
|
||||
var liquidQuantityMsg = new FormattedMessage();
|
||||
liquidQuantityMsg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-reagent-quantity-display", ("amount", amount)));
|
||||
liquidQuantityMsg.Pop();
|
||||
|
||||
var liquidQuantityLabel = new RichTextLabel();
|
||||
liquidQuantityLabel.SetMessage(liquidQuantityMsg);
|
||||
|
||||
IngredientsGrid.AddChild(liquidQuantityLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateIngredients(FoodRecipePrototype recipe)
|
||||
{
|
||||
GenerateLiquidIngredients(recipe);
|
||||
GenerateSolidIngredients(recipe);
|
||||
}
|
||||
|
||||
private void GenerateCookTime(FoodRecipePrototype recipe)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
msg.AddMarkupOrThrow(Loc.GetString("guidebook-microwave-cook-time", ("time", recipe.CookTime)));
|
||||
msg.Pop();
|
||||
|
||||
CookTimeLabel.SetMessage(msg);
|
||||
}
|
||||
|
||||
private void GenerateControl(FoodRecipePrototype recipe)
|
||||
{
|
||||
GenerateHeader(recipe);
|
||||
GenerateIngredients(recipe);
|
||||
GenerateCookTime(recipe);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.Richtext;
|
||||
using Content.Shared.Kitchen;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Guidebook.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Control for listing microwave recipes in a guidebook
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed partial class GuideMicrowaveGroupEmbed : BoxContainer, IDocumentTag
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
public GuideMicrowaveGroupEmbed()
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
IoCManager.InjectDependencies(this);
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
}
|
||||
|
||||
public GuideMicrowaveGroupEmbed(string group) : this()
|
||||
{
|
||||
CreateEntries(group);
|
||||
}
|
||||
|
||||
public bool TryParseTag(Dictionary<string, string> args, [NotNullWhen(true)] out Control? control)
|
||||
{
|
||||
control = null;
|
||||
if (!args.TryGetValue("Group", out var group))
|
||||
{
|
||||
Logger.Error("Microwave group embed tag is missing group argument");
|
||||
return false;
|
||||
}
|
||||
|
||||
CreateEntries(group);
|
||||
|
||||
control = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CreateEntries(string group)
|
||||
{
|
||||
var prototypes = _prototype.EnumeratePrototypes<FoodRecipePrototype>()
|
||||
.Where(p => p.Group.Equals(group))
|
||||
.OrderBy(p => p.Name);
|
||||
|
||||
foreach (var recipe in prototypes)
|
||||
{
|
||||
var embed = new GuideMicrowaveEmbed(recipe);
|
||||
AddChild(embed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,34 @@
|
||||
<BoxContainer xmlns="https://spacestation14.io"
|
||||
Orientation="Horizontal"
|
||||
Orientation="Vertical"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalExpand="True"
|
||||
Margin="0 0 0 5">
|
||||
<BoxContainer Name="ReactantsContainer" Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
|
||||
<RichTextLabel Name="ReactantsLabel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Access="Public"
|
||||
Visible="False"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextureRect TexturePath="/Textures/Interface/Misc/beakerlarge.png"
|
||||
HorizontalAlignment="Center"
|
||||
Name="MixTexture"
|
||||
Access="Public"/>
|
||||
<RichTextLabel Name="MixLabel"
|
||||
HorizontalAlignment="Center"
|
||||
Access="Public"
|
||||
Margin="2 0 0 0"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
|
||||
<RichTextLabel Name="ProductsLabel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Access="Public"
|
||||
Visible="False"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer Name="ReactantsContainer" Orientation="Vertical" HorizontalExpand="True"
|
||||
VerticalAlignment="Center">
|
||||
<RichTextLabel Name="ReactantsLabel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Access="Public"
|
||||
Visible="False" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<TextureRect TexturePath="/Textures/Interface/Misc/beakerlarge.png"
|
||||
HorizontalAlignment="Center"
|
||||
Name="MixTexture"
|
||||
Access="Public" />
|
||||
<RichTextLabel Name="MixLabel"
|
||||
HorizontalAlignment="Center"
|
||||
Access="Public"
|
||||
Margin="2 0 0 0" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalAlignment="Center">
|
||||
<RichTextLabel Name="ProductsLabel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Access="Public"
|
||||
Visible="False" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
|
||||
</BoxContainer>
|
||||
|
||||
@@ -133,24 +133,14 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
HashSet<ProtoId<GuideEntryPrototype>> entries = new(_entries.Keys);
|
||||
foreach (var entry in _entries.Values)
|
||||
{
|
||||
if (entry.Children.Count > 0)
|
||||
{
|
||||
var sortedChildren = entry.Children
|
||||
.Select(childId => _entries[childId])
|
||||
.OrderBy(childEntry => childEntry.Priority)
|
||||
.ThenBy(childEntry => Loc.GetString(childEntry.Name))
|
||||
.Select(childEntry => new ProtoId<GuideEntryPrototype>(childEntry.Id))
|
||||
.ToList();
|
||||
|
||||
entry.Children = sortedChildren;
|
||||
}
|
||||
|
||||
entries.ExceptWith(entry.Children);
|
||||
}
|
||||
|
||||
rootEntries = entries.ToList();
|
||||
}
|
||||
|
||||
// Only roots need to be sorted.
|
||||
// As defined in the SS14 Dev Wiki, children are already sorted based on their child field order within their parent's prototype definition.
|
||||
// Roots are sorted by priority. If there is no defined priority for a root then it is by definition sorted undefined.
|
||||
return rootEntries
|
||||
.Select(rootEntryId => _entries[rootEntryId])
|
||||
.OrderBy(rootEntry => rootEntry.Priority)
|
||||
|
||||
@@ -109,7 +109,17 @@ public sealed partial class DocumentParsingManager
|
||||
.Cast<Control>()))
|
||||
.Labelled("subheader");
|
||||
|
||||
private static readonly Parser<char, Control> TryHeaderControl = OneOf(SubHeaderControlParser, HeaderControlParser);
|
||||
private static readonly Parser<char, Control> TertiaryHeaderControlParser = Try(String("###"))
|
||||
.Then(SkipWhitespaces.Then(Map(text => new Label
|
||||
{
|
||||
Text = text,
|
||||
StyleClasses = { "LabelKeyText" }
|
||||
},
|
||||
AnyCharExcept('\n').AtLeastOnceString())
|
||||
.Cast<Control>()))
|
||||
.Labelled("tertiaryheader");
|
||||
|
||||
private static readonly Parser<char, Control> TryHeaderControl = OneOf(TertiaryHeaderControlParser, SubHeaderControlParser, HeaderControlParser);
|
||||
|
||||
private static readonly Parser<char, Control> ListControlParser = Try(Char('-'))
|
||||
.Then(SkipWhitespaces)
|
||||
|
||||
@@ -150,7 +150,9 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
if (!TryComp<SpeechComponent>(uid, out var speech) || speech.SpeechSounds is null)
|
||||
return;
|
||||
|
||||
_audioSystem.PlayGlobal(speech.SpeechSounds, Filter.Local(), false, speech.AudioParams);
|
||||
// This code is broken because SpeechSounds isn't a file name or sound specifier directly.
|
||||
// Commenting out to avoid compile failure with https://github.com/space-wizards/RobustToolbox/pull/5540
|
||||
// _audioSystem.PlayGlobal(speech.SpeechSounds, Filter.Local(), false, speech.AudioParams);
|
||||
}
|
||||
|
||||
public void FakeClientActivateInWorld(EntityUid activated)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Humanoid.Markings;
|
||||
using Content.Shared.Humanoid.Prototypes;
|
||||
using Content.Shared.Preferences;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -12,12 +14,15 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MarkingManager _markingManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HumanoidAppearanceComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||
Subs.CVar(_configurationManager, CCVars.AccessibilityClientCensorNudity, OnCvarChanged, true);
|
||||
Subs.CVar(_configurationManager, CCVars.AccessibilityServerCensorNudity, OnCvarChanged, true);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, HumanoidAppearanceComponent component, ref AfterAutoHandleStateEvent args)
|
||||
@@ -25,6 +30,15 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
UpdateSprite(component, Comp<SpriteComponent>(uid));
|
||||
}
|
||||
|
||||
private void OnCvarChanged(bool value)
|
||||
{
|
||||
var humanoidQuery = EntityManager.AllEntityQueryEnumerator<HumanoidAppearanceComponent, SpriteComponent>();
|
||||
while (humanoidQuery.MoveNext(out var _, out var humanoidComp, out var spriteComp))
|
||||
{
|
||||
UpdateSprite(humanoidComp, spriteComp);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSprite(HumanoidAppearanceComponent component, SpriteComponent sprite)
|
||||
{
|
||||
UpdateLayers(component, sprite);
|
||||
@@ -207,16 +221,30 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
// Really, markings should probably be a separate component altogether.
|
||||
ClearAllMarkings(humanoid, sprite);
|
||||
|
||||
var censorNudity = _configurationManager.GetCVar(CCVars.AccessibilityClientCensorNudity) ||
|
||||
_configurationManager.GetCVar(CCVars.AccessibilityServerCensorNudity);
|
||||
// The reason we're splitting this up is in case the character already has undergarment equipped in that slot.
|
||||
var applyUndergarmentTop = censorNudity;
|
||||
var applyUndergarmentBottom = censorNudity;
|
||||
|
||||
foreach (var markingList in humanoid.MarkingSet.Markings.Values)
|
||||
{
|
||||
foreach (var marking in markingList)
|
||||
{
|
||||
if (_markingManager.TryGetMarking(marking, out var markingPrototype))
|
||||
{
|
||||
ApplyMarking(markingPrototype, marking.MarkingColors, marking.Visible, humanoid, sprite);
|
||||
if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentTop)
|
||||
applyUndergarmentTop = false;
|
||||
else if (markingPrototype.BodyPart == HumanoidVisualLayers.UndergarmentBottom)
|
||||
applyUndergarmentBottom = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
humanoid.ClientOldMarkings = new MarkingSet(humanoid.MarkingSet);
|
||||
|
||||
AddUndergarments(humanoid, sprite, applyUndergarmentTop, applyUndergarmentBottom);
|
||||
}
|
||||
|
||||
private void ClearAllMarkings(HumanoidAppearanceComponent humanoid, SpriteComponent sprite)
|
||||
@@ -264,6 +292,31 @@ public sealed class HumanoidAppearanceSystem : SharedHumanoidAppearanceSystem
|
||||
spriteComp.RemoveLayer(index);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddUndergarments(HumanoidAppearanceComponent humanoid, SpriteComponent sprite, bool undergarmentTop, bool undergarmentBottom)
|
||||
{
|
||||
if (undergarmentTop && humanoid.UndergarmentTop != null)
|
||||
{
|
||||
var marking = new Marking(humanoid.UndergarmentTop, new List<Color> { new Color() });
|
||||
if (_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
// Markings are added to ClientOldMarkings because otherwise it causes issues when toggling the feature on/off.
|
||||
humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentTop, new List<Marking>{ marking });
|
||||
ApplyMarking(prototype, null, true, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
|
||||
if (undergarmentBottom && humanoid.UndergarmentBottom != null)
|
||||
{
|
||||
var marking = new Marking(humanoid.UndergarmentBottom, new List<Color> { new Color() });
|
||||
if (_markingManager.TryGetMarking(marking, out var prototype))
|
||||
{
|
||||
humanoid.ClientOldMarkings.Markings.Add(MarkingCategories.UndergarmentBottom, new List<Marking>{ marking });
|
||||
ApplyMarking(prototype, null, true, humanoid, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyMarking(MarkingPrototype markingPrototype,
|
||||
IReadOnlyList<Color>? colors,
|
||||
bool visible,
|
||||
|
||||
@@ -21,14 +21,12 @@ public sealed class HumanoidMarkingModifierBoundUserInterface : BoundUserInterfa
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<HumanoidMarkingModifierWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<HumanoidMarkingModifierWindow>();
|
||||
_window.OnMarkingAdded += SendMarkingSet;
|
||||
_window.OnMarkingRemoved += SendMarkingSet;
|
||||
_window.OnMarkingColorChange += SendMarkingSetNoResend;
|
||||
_window.OnMarkingRankChange += SendMarkingSet;
|
||||
_window.OnLayerInfoModified += SendBaseLayer;
|
||||
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -26,6 +26,12 @@ namespace Content.Client.IconSmoothing
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("key")]
|
||||
public string? SmoothKey { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional keys to smooth with.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<string> AdditionalKeys = new();
|
||||
|
||||
/// <summary>
|
||||
/// Prepended to the RSI state.
|
||||
/// </summary>
|
||||
|
||||
@@ -376,7 +376,8 @@ namespace Content.Client.IconSmoothing
|
||||
while (candidates.MoveNext(out var entity))
|
||||
{
|
||||
if (smoothQuery.TryGetComponent(entity, out var other) &&
|
||||
other.SmoothKey == smooth.SmoothKey &&
|
||||
other.SmoothKey != null &&
|
||||
(other.SmoothKey == smooth.SmoothKey || smooth.AdditionalKeys.Contains(other.SmoothKey)) &&
|
||||
other.Enabled)
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -2,11 +2,15 @@
|
||||
using Content.Client.Items;
|
||||
using Content.Shared.Implants;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Implants;
|
||||
|
||||
public sealed class ImplanterSystem : SharedImplanterSystem
|
||||
{
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -17,6 +21,18 @@ public sealed class ImplanterSystem : SharedImplanterSystem
|
||||
|
||||
private void OnHandleImplanterState(EntityUid uid, ImplanterComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (_uiSystem.TryGetOpenUi<DeimplantBoundUserInterface>(uid, DeimplantUiKey.Key, out var bui))
|
||||
{
|
||||
Dictionary<string, string> implants = new();
|
||||
foreach (var implant in component.DeimplantWhitelist)
|
||||
{
|
||||
if (_proto.TryIndex(implant, out var proto))
|
||||
implants.Add(proto.ID, proto.Name);
|
||||
}
|
||||
|
||||
bui.UpdateState(implants, component.DeimplantChosen);
|
||||
}
|
||||
|
||||
component.UiUpdateNeeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
35
Content.Client/Implants/UI/DeimplantBoundUserInterface.cs
Normal file
35
Content.Client/Implants/UI/DeimplantBoundUserInterface.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Content.Shared.Implants;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Implants.UI;
|
||||
|
||||
public sealed class DeimplantBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protomanager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private DeimplantChoiceWindow? _window;
|
||||
|
||||
public DeimplantBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<DeimplantChoiceWindow>();
|
||||
|
||||
_window.OnImplantChange += implant => SendMessage(new DeimplantChangeVerbMessage(implant));
|
||||
}
|
||||
|
||||
public void UpdateState(Dictionary<string, string> implantList, string? implant)
|
||||
{
|
||||
if (_window != null)
|
||||
{
|
||||
_window.UpdateImplantList(implantList);
|
||||
_window.UpdateState(implant);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Content.Client/Implants/UI/DeimplantChoiceWindow.xaml
Normal file
12
Content.Client/Implants/UI/DeimplantChoiceWindow.xaml
Normal file
@@ -0,0 +1,12 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'implanter-set-draw-window'}"
|
||||
MinSize="5 30">
|
||||
<BoxContainer Orientation="Vertical" Margin="10 5">
|
||||
<Label Text="{Loc 'implanter-set-draw-info'}" Margin="0 0 0 5"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'implanter-set-draw-type'}" Margin="0 0 5 0"/>
|
||||
<OptionButton Name="ImplantSelector"/> <!-- Populated in LoadVerbs -->
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
53
Content.Client/Implants/UI/DeimplantChoiceWindow.xaml.cs
Normal file
53
Content.Client/Implants/UI/DeimplantChoiceWindow.xaml.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Implants.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DeimplantChoiceWindow : FancyWindow
|
||||
{
|
||||
public Action<string?>? OnImplantChange;
|
||||
|
||||
private Dictionary<string, string> _implants = new();
|
||||
|
||||
private string? _chosenImplant;
|
||||
|
||||
public DeimplantChoiceWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
ImplantSelector.OnItemSelected += args =>
|
||||
{
|
||||
OnImplantChange?.Invoke(_implants.ElementAt(args.Id).Key);
|
||||
ImplantSelector.SelectId(args.Id);
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateImplantList(Dictionary<string, string> implants)
|
||||
{
|
||||
_implants = implants;
|
||||
int i = 0;
|
||||
ImplantSelector.Clear();
|
||||
foreach (var implantDict in _implants)
|
||||
{
|
||||
ImplantSelector.AddItem(implantDict.Value, i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateState(string? implant)
|
||||
{
|
||||
_chosenImplant = implant;
|
||||
|
||||
for (int id = 0; id < ImplantSelector.ItemCount; id++)
|
||||
{
|
||||
if (_implants.ElementAt(id).Key.Equals(_chosenImplant))
|
||||
{
|
||||
ImplantSelector.SelectId(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,20 @@ using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Implants.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Implants.UI;
|
||||
|
||||
public sealed class ImplanterStatusControl : Control
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
private readonly ImplanterComponent _parent;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
public ImplanterStatusControl(ImplanterComponent parent)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_parent = parent;
|
||||
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||
_label.MaxWidth = 350;
|
||||
@@ -43,12 +46,25 @@ public sealed class ImplanterStatusControl : Control
|
||||
_ => Loc.GetString("injector-invalid-injector-toggle-mode")
|
||||
};
|
||||
|
||||
var implantName = _parent.ImplanterSlot.HasItem
|
||||
? _parent.ImplantData.Item1
|
||||
: Loc.GetString("implanter-empty-text");
|
||||
if (_parent.CurrentMode == ImplanterToggleMode.Draw)
|
||||
{
|
||||
string implantName = _parent.DeimplantChosen != null
|
||||
? (_prototype.TryIndex(_parent.DeimplantChosen.Value, out EntityPrototype? implantProto) ? implantProto.Name : Loc.GetString("implanter-empty-text"))
|
||||
: Loc.GetString("implanter-empty-text");
|
||||
|
||||
_label.SetMarkup(Loc.GetString("implanter-label",
|
||||
("implantName", implantName),
|
||||
("modeString", modeStringLocalized)));
|
||||
_label.SetMarkup(Loc.GetString("implanter-label-draw",
|
||||
("implantName", implantName),
|
||||
("modeString", modeStringLocalized)));
|
||||
}
|
||||
else
|
||||
{
|
||||
var implantName = _parent.ImplanterSlot.HasItem
|
||||
? _parent.ImplantData.Item1
|
||||
: Loc.GetString("implanter-empty-text");
|
||||
|
||||
_label.SetMarkup(Loc.GetString("implanter-label-inject",
|
||||
("implantName", implantName),
|
||||
("modeString", modeStringLocalized)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +64,9 @@ namespace Content.Client.Inventory
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_strippingMenu = this.CreateWindow<StrippingMenu>();
|
||||
_strippingMenu = this.CreateWindowCenteredLeft<StrippingMenu>();
|
||||
_strippingMenu.OnDirty += UpdateMenu;
|
||||
_strippingMenu.Title = Loc.GetString("strippable-bound-user-interface-stripping-menu-title", ("ownerName", Identity.Name(Owner, EntMan)));
|
||||
|
||||
_strippingMenu?.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
||||
@@ -18,9 +18,8 @@ namespace Content.Client.Lathe.UI
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<LatheMenu>();
|
||||
_menu = this.CreateWindowCenteredRight<LatheMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.OpenCenteredRight();
|
||||
|
||||
_menu.OnServerListButtonPressed += _ =>
|
||||
{
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
PlaceHolder="0"
|
||||
Text="1"
|
||||
HorizontalExpand="True" />
|
||||
<Label Name="RecipeCount" Margin="8 0 8 0" MinWidth="90" Align="Right" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
@@ -94,8 +94,17 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
if (!_prototypeManager.TryIndex(recipe, out var proto))
|
||||
continue;
|
||||
|
||||
if (CurrentCategory != null && proto.Category != CurrentCategory)
|
||||
continue;
|
||||
// Category filtering
|
||||
if (CurrentCategory != null)
|
||||
{
|
||||
if (proto.Categories.Count <= 0)
|
||||
continue;
|
||||
|
||||
var validRecipe = proto.Categories.Any(category => category == CurrentCategory);
|
||||
|
||||
if (!validRecipe)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SearchBar.Text.Trim().Length != 0)
|
||||
{
|
||||
@@ -111,6 +120,8 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
if (!int.TryParse(AmountLineEdit.Text, out var quantity) || quantity <= 0)
|
||||
quantity = 1;
|
||||
|
||||
RecipeCount.Text = Loc.GetString("lathe-menu-recipe-count", ("count", recipesToShow.Count));
|
||||
|
||||
var sortedRecipesToShow = recipesToShow.OrderBy(_lathe.GetRecipeName);
|
||||
RecipeList.Children.Clear();
|
||||
_entityManager.TryGetComponent(Entity, out LatheComponent? lathe);
|
||||
@@ -179,18 +190,22 @@ public sealed partial class LatheMenu : DefaultWindow
|
||||
|
||||
public void UpdateCategories()
|
||||
{
|
||||
// Get categories from recipes
|
||||
var currentCategories = new List<ProtoId<LatheCategoryPrototype>>();
|
||||
foreach (var recipeId in Recipes)
|
||||
{
|
||||
var recipe = _prototypeManager.Index(recipeId);
|
||||
|
||||
if (recipe.Category == null)
|
||||
if (recipe.Categories.Count <= 0)
|
||||
continue;
|
||||
|
||||
if (currentCategories.Contains(recipe.Category.Value))
|
||||
continue;
|
||||
foreach (var category in recipe.Categories)
|
||||
{
|
||||
if (currentCategories.Contains(category))
|
||||
continue;
|
||||
|
||||
currentCategories.Add(recipe.Category.Value);
|
||||
currentCategories.Add(category);
|
||||
}
|
||||
}
|
||||
|
||||
if (Categories != null && (Categories.Count == currentCategories.Count || !Categories.All(currentCategories.Contains)))
|
||||
|
||||
59
Content.Client/Light/AfterLightTargetOverlay.cs
Normal file
59
Content.Client/Light/AfterLightTargetOverlay.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
/// <summary>
|
||||
/// This exists just to copy <see cref="BeforeLightTargetOverlay"/> to the light render target
|
||||
/// </summary>
|
||||
public sealed class AfterLightTargetOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
|
||||
public const int ContentZIndex = LightBlurOverlay.ContentZIndex + 1;
|
||||
|
||||
public AfterLightTargetOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.Viewport;
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
if (viewport.Eye == null)
|
||||
return;
|
||||
|
||||
var lightOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var bounds = args.WorldBounds;
|
||||
|
||||
// at 1-1 render scale it's mostly fine but at 4x4 it's way too fkn big
|
||||
var lightScale = viewport.LightRenderTarget.Size / (Vector2) viewport.Size;
|
||||
var newScale = viewport.RenderScale / (Vector2.One / lightScale);
|
||||
|
||||
var localMatrix =
|
||||
viewport.LightRenderTarget.GetWorldToLocalMatrix(viewport.Eye, newScale);
|
||||
var diff = (lightOverlay.EnlargedLightTarget.Size - viewport.LightRenderTarget.Size);
|
||||
var halfDiff = diff / 2;
|
||||
|
||||
// Pixels -> Metres -> Half distance.
|
||||
// If we're zoomed in need to enlarge the bounds further.
|
||||
args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget,
|
||||
() =>
|
||||
{
|
||||
// We essentially need to draw the cropped version onto the lightrendertarget.
|
||||
var subRegion = new UIBox2i(halfDiff.X,
|
||||
halfDiff.Y,
|
||||
viewport.LightRenderTarget.Size.X + halfDiff.X,
|
||||
viewport.LightRenderTarget.Size.Y + halfDiff.Y);
|
||||
|
||||
worldHandle.SetTransform(localMatrix);
|
||||
worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
|
||||
}, Color.Transparent);
|
||||
}
|
||||
}
|
||||
51
Content.Client/Light/BeforeLightTargetOverlay.cs
Normal file
51
Content.Client/Light/BeforeLightTargetOverlay.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
/// <summary>
|
||||
/// Handles an enlarged lighting target so content can use large blur radii.
|
||||
/// </summary>
|
||||
public sealed class BeforeLightTargetOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
|
||||
public IRenderTexture EnlargedLightTarget = default!;
|
||||
public Box2Rotated EnlargedBounds;
|
||||
|
||||
/// <summary>
|
||||
/// In metres
|
||||
/// </summary>
|
||||
private float _skirting = 2f;
|
||||
|
||||
public const int ContentZIndex = -10;
|
||||
|
||||
public BeforeLightTargetOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
// Code is weird but I don't think engine should be enlarging the lighting render target arbitrarily either, maybe via cvar?
|
||||
// The problem is the blur has no knowledge of pixels outside the viewport so with a large enough blur radius you get sampling issues.
|
||||
var size = args.Viewport.LightRenderTarget.Size + (int) (_skirting * EyeManager.PixelsPerMeter);
|
||||
EnlargedBounds = args.WorldBounds.Enlarged(_skirting / 2f);
|
||||
|
||||
// This just exists to copy the lightrendertarget and write back to it.
|
||||
if (EnlargedLightTarget?.Size != size)
|
||||
{
|
||||
EnlargedLightTarget = _clyde
|
||||
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-copy");
|
||||
}
|
||||
|
||||
args.WorldHandle.RenderInRenderTarget(EnlargedLightTarget,
|
||||
() =>
|
||||
{
|
||||
}, _clyde.GetClearColor(args.MapUid));
|
||||
}
|
||||
}
|
||||
38
Content.Client/Light/EntitySystems/PlanetLightSystem.cs
Normal file
38
Content.Client/Light/EntitySystems/PlanetLightSystem.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Robust.Client.Graphics;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
public sealed class PlanetLightSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayMan = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GetClearColorEvent>(OnClearColor);
|
||||
|
||||
_overlayMan.AddOverlay(new BeforeLightTargetOverlay());
|
||||
_overlayMan.AddOverlay(new RoofOverlay(EntityManager));
|
||||
_overlayMan.AddOverlay(new TileEmissionOverlay(EntityManager));
|
||||
_overlayMan.AddOverlay(new LightBlurOverlay());
|
||||
_overlayMan.AddOverlay(new SunShadowOverlay());
|
||||
_overlayMan.AddOverlay(new AfterLightTargetOverlay());
|
||||
}
|
||||
|
||||
private void OnClearColor(ref GetClearColorEvent ev)
|
||||
{
|
||||
ev.Color = Color.Transparent;
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayMan.RemoveOverlay<BeforeLightTargetOverlay>();
|
||||
_overlayMan.RemoveOverlay<RoofOverlay>();
|
||||
_overlayMan.RemoveOverlay<TileEmissionOverlay>();
|
||||
_overlayMan.RemoveOverlay<LightBlurOverlay>();
|
||||
_overlayMan.RemoveOverlay<SunShadowOverlay>();
|
||||
_overlayMan.RemoveOverlay<AfterLightTargetOverlay>();
|
||||
}
|
||||
}
|
||||
9
Content.Client/Light/EntitySystems/RoofSystem.cs
Normal file
9
Content.Client/Light/EntitySystems/RoofSystem.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class RoofSystem : SharedRoofSystem
|
||||
{
|
||||
|
||||
}
|
||||
92
Content.Client/Light/EntitySystems/SunShadowSystem.cs
Normal file
92
Content.Client/Light/EntitySystems/SunShadowSystem.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
public sealed class SunShadowSystem : SharedSunShadowSystem
|
||||
{
|
||||
[Dependency] private readonly ClientGameTicker _ticker = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var mapQuery = AllEntityQuery<SunShadowCycleComponent, SunShadowComponent>();
|
||||
while (mapQuery.MoveNext(out var uid, out var cycle, out var shadow))
|
||||
{
|
||||
if (!cycle.Running || cycle.Directions.Count == 0)
|
||||
continue;
|
||||
|
||||
var pausedTime = _metadata.GetPauseTime(uid);
|
||||
|
||||
var time = (float)(_timing.CurTime
|
||||
.Add(cycle.Offset)
|
||||
.Subtract(_ticker.RoundStartTimeSpan)
|
||||
.Subtract(pausedTime)
|
||||
.TotalSeconds % cycle.Duration.TotalSeconds);
|
||||
|
||||
var (direction, alpha) = GetShadow((uid, cycle), time);
|
||||
shadow.Direction = direction;
|
||||
shadow.Alpha = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public (Vector2 Direction, float Alpha) GetShadow(Entity<SunShadowCycleComponent> entity, float time)
|
||||
{
|
||||
// So essentially the values are stored as the percentages of the total duration just so it adjusts the speed
|
||||
// dynamically and we don't have to manually handle it.
|
||||
// It will lerp from each value to the next one with angle and length handled separately
|
||||
var ratio = (float) (time / entity.Comp.Duration.TotalSeconds);
|
||||
|
||||
for (var i = entity.Comp.Directions.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var dir = entity.Comp.Directions[i];
|
||||
|
||||
if (ratio > dir.Ratio)
|
||||
{
|
||||
var next = entity.Comp.Directions[(i + 1) % entity.Comp.Directions.Count];
|
||||
float nextRatio;
|
||||
|
||||
// Last entry
|
||||
if (i == entity.Comp.Directions.Count - 1)
|
||||
{
|
||||
nextRatio = next.Ratio + 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextRatio = next.Ratio;
|
||||
}
|
||||
|
||||
var range = nextRatio - dir.Ratio;
|
||||
var diff = (ratio - dir.Ratio) / range;
|
||||
DebugTools.Assert(diff is >= 0f and <= 1f);
|
||||
|
||||
// We lerp angle + length separately as we don't want a straight-line lerp and want the rotation to be consistent.
|
||||
var currentAngle = dir.Direction.ToAngle();
|
||||
var nextAngle = next.Direction.ToAngle();
|
||||
|
||||
var angle = Angle.Lerp(currentAngle, nextAngle, diff);
|
||||
// This is to avoid getting weird issues where the angle gets pretty close but length still noticeably catches up.
|
||||
var lengthDiff = MathF.Pow(diff, 1f / 2f);
|
||||
var length = float.Lerp(dir.Direction.Length(), next.Direction.Length(), lengthDiff);
|
||||
|
||||
var vector = angle.ToVec() * length;
|
||||
var alpha = float.Lerp(dir.Alpha, next.Alpha, diff);
|
||||
return (vector, alpha);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
44
Content.Client/Light/LightBlurOverlay.cs
Normal file
44
Content.Client/Light/LightBlurOverlay.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
/// <summary>
|
||||
/// Essentially handles blurring for content-side light overlays.
|
||||
/// </summary>
|
||||
public sealed class LightBlurOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
|
||||
public const int ContentZIndex = TileEmissionOverlay.ContentZIndex + 1;
|
||||
|
||||
private IRenderTarget? _blurTarget;
|
||||
|
||||
public LightBlurOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.Viewport.Eye == null)
|
||||
return;
|
||||
|
||||
var beforeOverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var size = beforeOverlay.EnlargedLightTarget.Size;
|
||||
|
||||
if (_blurTarget?.Size != size)
|
||||
{
|
||||
_blurTarget = _clyde
|
||||
.CreateRenderTarget(size, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "enlarged-light-blur");
|
||||
}
|
||||
|
||||
var target = beforeOverlay.EnlargedLightTarget;
|
||||
// Yeah that's all this does keep walkin.
|
||||
_clyde.BlurRenderTarget(args.Viewport, target, _blurTarget, args.Viewport.Eye, 14f * 5f);
|
||||
}
|
||||
}
|
||||
44
Content.Client/Light/LightCycleSystem.cs
Normal file
44
Content.Client/Light/LightCycleSystem.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Content.Client.GameTicking.Managers;
|
||||
using Content.Shared;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class LightCycleSystem : SharedLightCycleSystem
|
||||
{
|
||||
[Dependency] private readonly ClientGameTicker _ticker = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metadata = default!;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
var mapQuery = AllEntityQuery<LightCycleComponent, MapLightComponent>();
|
||||
while (mapQuery.MoveNext(out var uid, out var cycle, out var map))
|
||||
{
|
||||
if (!cycle.Running)
|
||||
continue;
|
||||
|
||||
// We still iterate paused entities as we still want to override the lighting color and not have
|
||||
// it apply the server state
|
||||
var pausedTime = _metadata.GetPauseTime(uid);
|
||||
|
||||
var time = (float) _timing.CurTime
|
||||
.Add(cycle.Offset)
|
||||
.Subtract(_ticker.RoundStartTimeSpan)
|
||||
.Subtract(pausedTime)
|
||||
.TotalSeconds;
|
||||
|
||||
var color = GetColor((uid, cycle), cycle.OriginalColor, time);
|
||||
map.AmbientLightColor = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
112
Content.Client/Light/RoofOverlay.cs
Normal file
112
Content.Client/Light/RoofOverlay.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Light.EntitySystems;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
public sealed class RoofOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedRoofSystem _roof = default!;
|
||||
private readonly SharedTransformSystem _xformSystem;
|
||||
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
public const int ContentZIndex = BeforeLightTargetOverlay.ContentZIndex + 1;
|
||||
|
||||
public RoofOverlay(IEntityManager entManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_lookup = _entManager.System<EntityLookupSystem>();
|
||||
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||
_roof = _entManager.System<SharedRoofSystem>();
|
||||
_xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.Viewport.Eye == null)
|
||||
return;
|
||||
|
||||
var viewport = args.Viewport;
|
||||
var eye = args.Viewport.Eye;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var bounds = lightoverlay.EnlargedBounds;
|
||||
var target = lightoverlay.EnlargedLightTarget;
|
||||
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(args.MapId, bounds, ref _grids);
|
||||
|
||||
for (var i = _grids.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var grid = _grids[i];
|
||||
|
||||
if (_entManager.HasComponent<RoofComponent>(grid.Owner))
|
||||
continue;
|
||||
|
||||
_grids.RemoveAt(i);
|
||||
}
|
||||
|
||||
if (_grids.Count == 0)
|
||||
return;
|
||||
|
||||
var lightScale = viewport.LightRenderTarget.Size / (Vector2) viewport.Size;
|
||||
var scale = viewport.RenderScale / (Vector2.One / lightScale);
|
||||
|
||||
worldHandle.RenderInRenderTarget(target,
|
||||
() =>
|
||||
{
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
if (!_entManager.TryGetComponent(grid.Owner, out RoofComponent? roof))
|
||||
continue;
|
||||
|
||||
var invMatrix = target.GetWorldToLocalMatrix(eye, scale);
|
||||
|
||||
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
|
||||
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
||||
|
||||
worldHandle.SetTransform(matty);
|
||||
|
||||
var tileEnumerator = _mapSystem.GetTilesEnumerator(grid.Owner, grid, bounds);
|
||||
var roofEnt = (grid.Owner, grid.Comp, roof);
|
||||
|
||||
// Due to stencilling we essentially draw on unrooved tiles
|
||||
while (tileEnumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
var color = _roof.GetColor(roofEnt, tileRef.GridIndices);
|
||||
|
||||
if (color == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var local = _lookup.GetLocalBounds(tileRef, grid.Comp.TileSize);
|
||||
worldHandle.DrawRect(local, color.Value);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
160
Content.Client/Light/SunShadowOverlay.cs
Normal file
160
Content.Client/Light/SunShadowOverlay.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
public sealed class SunShadowOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedTransformSystem _xformSys;
|
||||
|
||||
private readonly HashSet<Entity<SunShadowCastComponent>> _shadows = new();
|
||||
|
||||
private IRenderTexture? _blurTarget;
|
||||
private IRenderTexture? _target;
|
||||
|
||||
public SunShadowOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_xformSys = _entManager.System<SharedTransformSystem>();
|
||||
_lookup = _entManager.System<EntityLookupSystem>();
|
||||
ZIndex = AfterLightTargetOverlay.ContentZIndex + 1;
|
||||
}
|
||||
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = args.Viewport;
|
||||
var eye = viewport.Eye;
|
||||
|
||||
if (eye == null)
|
||||
return;
|
||||
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(args.MapId,
|
||||
args.WorldBounds.Enlarged(SunShadowComponent.MaxLength),
|
||||
ref _grids);
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var mapId = args.MapId;
|
||||
var worldBounds = args.WorldBounds;
|
||||
var targetSize = viewport.LightRenderTarget.Size;
|
||||
|
||||
if (_target?.Size != targetSize)
|
||||
{
|
||||
_target = _clyde
|
||||
.CreateRenderTarget(targetSize,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb),
|
||||
name: "sun-shadow-target");
|
||||
|
||||
if (_blurTarget?.Size != targetSize)
|
||||
{
|
||||
_blurTarget = _clyde
|
||||
.CreateRenderTarget(targetSize, new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb), name: "sun-shadow-blur");
|
||||
}
|
||||
}
|
||||
|
||||
var lightScale = viewport.LightRenderTarget.Size / (Vector2)viewport.Size;
|
||||
var scale = viewport.RenderScale / (Vector2.One / lightScale);
|
||||
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
if (!_entManager.TryGetComponent(grid.Owner, out SunShadowComponent? sun))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var direction = sun.Direction;
|
||||
var alpha = Math.Clamp(sun.Alpha, 0f, 1f);
|
||||
|
||||
// Nowhere to cast to so ignore it.
|
||||
if (direction.Equals(Vector2.Zero) || alpha == 0f)
|
||||
continue;
|
||||
|
||||
// Feature todo: dynamic shadows for mobs and trees. Also ideally remove the fake tree shadows.
|
||||
// TODO: Jittering still not quite perfect
|
||||
|
||||
var expandedBounds = worldBounds.Enlarged(direction.Length() + 0.01f);
|
||||
_shadows.Clear();
|
||||
|
||||
// Draw shadow polys to stencil
|
||||
args.WorldHandle.RenderInRenderTarget(_target,
|
||||
() =>
|
||||
{
|
||||
var invMatrix =
|
||||
_target.GetWorldToLocalMatrix(eye, scale);
|
||||
var indices = new Vector2[PhysicsConstants.MaxPolygonVertices * 2];
|
||||
|
||||
// Go through shadows in range.
|
||||
|
||||
// For each one we:
|
||||
// - Get the original vertices.
|
||||
// - Extrapolate these along the sun direction.
|
||||
// - Combine the above into 1 single polygon to draw.
|
||||
|
||||
// Note that this is range-limited for accuracy; if you set it too high it will clip through walls or other undesirable entities.
|
||||
// This is probably not noticeable most of the time but if you want something "accurate" you'll want to code a solution.
|
||||
// Ideally the CPU would have its own shadow-map copy that we could just ray-cast each vert into though
|
||||
// You might need to batch verts or the likes as this could get expensive.
|
||||
_lookup.GetEntitiesIntersecting(mapId, expandedBounds, _shadows);
|
||||
|
||||
foreach (var ent in _shadows)
|
||||
{
|
||||
var xform = _entManager.GetComponent<TransformComponent>(ent.Owner);
|
||||
var (worldPos, worldRot) = _xformSys.GetWorldPositionRotation(xform);
|
||||
// Need no rotation on matrix as sun shadow direction doesn't care.
|
||||
var worldMatrix = Matrix3x2.CreateTranslation(worldPos);
|
||||
var renderMatrix = Matrix3x2.Multiply(worldMatrix, invMatrix);
|
||||
var pointCount = ent.Comp.Points.Length;
|
||||
|
||||
Array.Copy(ent.Comp.Points, indices, pointCount);
|
||||
|
||||
for (var i = 0; i < pointCount; i++)
|
||||
{
|
||||
// Update point based on entity rotation.
|
||||
indices[i] = worldRot.RotateVec(indices[i]);
|
||||
|
||||
// Add the offset point by the sun shadow direction.
|
||||
indices[pointCount + i] = indices[i] + direction;
|
||||
}
|
||||
|
||||
var points = PhysicsHull.ComputePoints(indices, pointCount * 2);
|
||||
worldHandle.SetTransform(renderMatrix);
|
||||
|
||||
worldHandle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, points, Color.White);
|
||||
}
|
||||
},
|
||||
Color.Transparent);
|
||||
|
||||
// Slightly blur it just to avoid aliasing issues on the later viewport-wide blur.
|
||||
_clyde.BlurRenderTarget(viewport, _target, _blurTarget!, eye, 1f);
|
||||
|
||||
// Draw stencil (see roofoverlay).
|
||||
args.WorldHandle.RenderInRenderTarget(viewport.LightRenderTarget,
|
||||
() =>
|
||||
{
|
||||
var invMatrix =
|
||||
viewport.LightRenderTarget.GetWorldToLocalMatrix(eye, scale);
|
||||
worldHandle.SetTransform(invMatrix);
|
||||
|
||||
var maskShader = _protoManager.Index<ShaderPrototype>("Mix").Instance();
|
||||
worldHandle.UseShader(maskShader);
|
||||
|
||||
worldHandle.DrawTextureRect(_target.Texture, worldBounds, Color.Black.WithAlpha(alpha));
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Content.Client/Light/TileEmissionOverlay.cs
Normal file
96
Content.Client/Light/TileEmissionOverlay.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Client.Light;
|
||||
|
||||
public sealed class TileEmissionOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
|
||||
private SharedMapSystem _mapSystem;
|
||||
private SharedTransformSystem _xformSystem;
|
||||
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
|
||||
private readonly EntityQuery<TransformComponent> _xformQuery;
|
||||
private readonly HashSet<Entity<TileEmissionComponent>> _entities = new();
|
||||
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public const int ContentZIndex = RoofOverlay.ContentZIndex + 1;
|
||||
|
||||
public TileEmissionOverlay(IEntityManager entManager)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_lookup = entManager.System<EntityLookupSystem>();
|
||||
_mapSystem = entManager.System<SharedMapSystem>();
|
||||
_xformSystem = entManager.System<SharedTransformSystem>();
|
||||
|
||||
_xformQuery = entManager.GetEntityQuery<TransformComponent>();
|
||||
ZIndex = ContentZIndex;
|
||||
}
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.Viewport.Eye == null)
|
||||
return;
|
||||
|
||||
var mapId = args.MapId;
|
||||
var worldHandle = args.WorldHandle;
|
||||
var lightoverlay = _overlay.GetOverlay<BeforeLightTargetOverlay>();
|
||||
var bounds = lightoverlay.EnlargedBounds;
|
||||
var target = lightoverlay.EnlargedLightTarget;
|
||||
var viewport = args.Viewport;
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(mapId, bounds, ref _grids, approx: true);
|
||||
|
||||
if (_grids.Count == 0)
|
||||
return;
|
||||
|
||||
var lightScale = viewport.LightRenderTarget.Size / (Vector2) viewport.Size;
|
||||
var scale = viewport.RenderScale / (Vector2.One / lightScale);
|
||||
|
||||
args.WorldHandle.RenderInRenderTarget(target,
|
||||
() =>
|
||||
{
|
||||
var invMatrix = target.GetWorldToLocalMatrix(viewport.Eye, scale);
|
||||
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
var gridInvMatrix = _xformSystem.GetInvWorldMatrix(grid);
|
||||
var localBounds = gridInvMatrix.TransformBox(bounds);
|
||||
_entities.Clear();
|
||||
_lookup.GetLocalEntitiesIntersecting(grid.Owner, localBounds, _entities);
|
||||
|
||||
if (_entities.Count == 0)
|
||||
continue;
|
||||
|
||||
var gridMatrix = _xformSystem.GetWorldMatrix(grid.Owner);
|
||||
|
||||
foreach (var ent in _entities)
|
||||
{
|
||||
var xform = _xformQuery.Comp(ent);
|
||||
|
||||
var tile = _mapSystem.LocalToTile(grid.Owner, grid, xform.Coordinates);
|
||||
var matty = Matrix3x2.Multiply(gridMatrix, invMatrix);
|
||||
|
||||
worldHandle.SetTransform(matty);
|
||||
|
||||
// Yes I am fully aware this leads to overlap. If you really want to have alpha then you'll need
|
||||
// to turn the squares into polys.
|
||||
// Additionally no shadows so if you make it too big it's going to go through a 1x wall.
|
||||
var local = _lookup.GetLocalBounds(tile, grid.Comp.TileSize).Enlarged(ent.Comp.Range);
|
||||
worldHandle.DrawRect(local, ent.Comp.Color);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
}
|
||||
}
|
||||
@@ -122,7 +122,7 @@ public sealed class PoweredLightVisualizerSystem : VisualizerSystem<PoweredLight
|
||||
|
||||
if (comp.BlinkingSound != null)
|
||||
{
|
||||
var sound = _audio.GetSound(comp.BlinkingSound);
|
||||
var sound = _audio.ResolveSound(comp.BlinkingSound);
|
||||
blinkingAnim.AnimationTracks.Add(new AnimationTrackPlaySound()
|
||||
{
|
||||
KeyFrames =
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
<SpriteView Scale="2 2"
|
||||
Margin="0 4 4 4"
|
||||
OverrideDirection="South"
|
||||
Name="View"/>
|
||||
Name="View"
|
||||
SetSize="64 64"/>
|
||||
<Label Name="DescriptionLabel"
|
||||
ClipText="True"
|
||||
HorizontalExpand="True"/>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
MinSize="800 128">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<BoxContainer Name="RoleNameBox" Orientation="Vertical" Margin="10">
|
||||
<Label Name="LoadoutNameLabel" Text="{Loc 'loadout-name-edit-label'}"/>
|
||||
<Label Name="LoadoutNameLabel"/>
|
||||
<PanelContainer HorizontalExpand="True" SetHeight="24">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
|
||||
@@ -39,6 +39,10 @@ public sealed partial class LoadoutWindow : FancyWindow
|
||||
{
|
||||
var name = loadout.EntityName;
|
||||
|
||||
LoadoutNameLabel.Text = proto.NameDataset == null ?
|
||||
Loc.GetString("loadout-name-edit-label") :
|
||||
Loc.GetString("loadout-name-edit-label-dataset");
|
||||
|
||||
RoleNameEdit.ToolTip = Loc.GetString(
|
||||
"loadout-name-edit-tooltip",
|
||||
("max", HumanoidCharacterProfile.MaxLoadoutNameLength));
|
||||
|
||||
@@ -21,9 +21,8 @@ public sealed class MechBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<MechMenu>();
|
||||
_menu = this.CreateWindowCenteredLeft<MechMenu>();
|
||||
_menu.SetEntity(Owner);
|
||||
_menu.OpenCenteredLeft();
|
||||
|
||||
_menu.OnRemoveButtonPressed += uid =>
|
||||
{
|
||||
|
||||
@@ -372,14 +372,11 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
if (!_tryToScrollToListFocus)
|
||||
return;
|
||||
|
||||
if (!TryGetVerticalScrollbar(SensorScroller, out var vScrollbar))
|
||||
return;
|
||||
|
||||
if (TryGetNextScrollPosition(out float? nextScrollPosition))
|
||||
{
|
||||
vScrollbar.ValueTarget = nextScrollPosition.Value;
|
||||
SensorScroller.VScrollTarget = nextScrollPosition.Value;
|
||||
|
||||
if (MathHelper.CloseToPercent(vScrollbar.Value, vScrollbar.ValueTarget))
|
||||
if (MathHelper.CloseToPercent(SensorScroller.VScroll, SensorScroller.VScrollTarget))
|
||||
{
|
||||
_tryToScrollToListFocus = false;
|
||||
return;
|
||||
@@ -387,22 +384,6 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetVerticalScrollbar(ScrollContainer scroll, [NotNullWhen(true)] out VScrollBar? vScrollBar)
|
||||
{
|
||||
vScrollBar = null;
|
||||
|
||||
foreach (var child in scroll.Children)
|
||||
{
|
||||
if (child is not VScrollBar)
|
||||
continue;
|
||||
|
||||
vScrollBar = (VScrollBar) child;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetNextScrollPosition([NotNullWhen(true)] out float? nextScrollPosition)
|
||||
{
|
||||
nextScrollPosition = 0;
|
||||
|
||||
@@ -10,7 +10,7 @@ using Robust.Client.Player;
|
||||
|
||||
namespace Content.Client.Movement.Systems;
|
||||
|
||||
public partial class EyeCursorOffsetSystem : EntitySystem
|
||||
public sealed partial class EyeCursorOffsetSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
@@ -8,5 +8,6 @@
|
||||
<tabs:KeyRebindTab Name="KeyRebindTab" />
|
||||
<tabs:AudioTab Name="AudioTab" />
|
||||
<tabs:AccessibilityTab Name="AccessibilityTab" />
|
||||
<tabs:AdminOptionsTab Name="AdminOptionsTab" />
|
||||
</TabContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Client.Options.UI.Tabs;
|
||||
using Content.Client.Administration.Managers;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -8,6 +8,8 @@ namespace Content.Client.Options.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class OptionsMenu : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IClientAdminManager _adminManager = default!;
|
||||
|
||||
public OptionsMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -18,16 +20,21 @@ namespace Content.Client.Options.UI
|
||||
Tabs.SetTabTitle(2, Loc.GetString("ui-options-tab-controls"));
|
||||
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
|
||||
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
|
||||
Tabs.SetTabTitle(5, Loc.GetString("ui-options-tab-admin"));
|
||||
|
||||
UpdateTabs();
|
||||
}
|
||||
|
||||
public void UpdateTabs()
|
||||
{
|
||||
var isAdmin = _adminManager.IsAdmin(true);
|
||||
Tabs.SetTabVisible(5, isAdmin);
|
||||
|
||||
GraphicsTab.Control.ReloadValues();
|
||||
MiscTab.Control.ReloadValues();
|
||||
AccessibilityTab.Control.ReloadValues();
|
||||
AudioTab.Control.ReloadValues();
|
||||
AdminOptionsTab.Control.ReloadValues();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,19 @@
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" Margin="8">
|
||||
<Label Text="{Loc 'ui-options-accessability-header-visuals'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="ReducedMotionCheckBox" Text="{Loc 'ui-options-reduced-motion'}" />
|
||||
<CheckBox Name="EnableColorNameCheckBox" Text="{Loc 'ui-options-enable-color-name'}" />
|
||||
<CheckBox Name="ColorblindFriendlyCheckBox" Text="{Loc 'ui-options-colorblind-friendly'}" />
|
||||
<ui:OptionSlider Name="ChatWindowOpacitySlider" Title="{Loc 'ui-options-chat-window-opacity'}" />
|
||||
<ui:OptionSlider Name="ScreenShakeIntensitySlider" Title="{Loc 'ui-options-screen-shake-intensity'}" />
|
||||
<ui:OptionSlider Name="ChatWindowOpacitySlider" Title="{Loc 'ui-options-chat-window-opacity'}" />
|
||||
<ui:OptionSlider Name="SpeechBubbleTextOpacitySlider" Title="{Loc 'ui-options-speech-bubble-text-opacity'}" />
|
||||
<ui:OptionSlider Name="SpeechBubbleSpeakerOpacitySlider" Title="{Loc 'ui-options-speech-bubble-speaker-opacity'}" />
|
||||
<ui:OptionSlider Name="SpeechBubbleBackgroundOpacitySlider" Title="{Loc 'ui-options-speech-bubble-background-opacity'}" />
|
||||
<Label Text="{Loc 'ui-options-accessability-header-content'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
<CheckBox Name="CensorNudityCheckBox" Text="{Loc 'ui-options-censor-nudity'}" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
|
||||
@@ -15,8 +15,13 @@ public sealed partial class AccessibilityTab : Control
|
||||
Control.AddOptionCheckBox(CCVars.ChatEnableColorName, EnableColorNameCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox);
|
||||
Control.AddOptionCheckBox(CCVars.ReducedMotion, ReducedMotionCheckBox);
|
||||
Control.AddOptionPercentSlider(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider);
|
||||
Control.AddOptionPercentSlider(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider);
|
||||
Control.AddOptionPercentSlider(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider);
|
||||
Control.AddOptionPercentSlider(CCVars.SpeechBubbleTextOpacity, SpeechBubbleTextOpacitySlider);
|
||||
Control.AddOptionPercentSlider(CCVars.SpeechBubbleSpeakerOpacity, SpeechBubbleSpeakerOpacitySlider);
|
||||
Control.AddOptionPercentSlider(CCVars.SpeechBubbleBackgroundOpacity, SpeechBubbleBackgroundOpacitySlider);
|
||||
|
||||
Control.AddOptionCheckBox(CCVars.AccessibilityClientCensorNudity, CensorNudityCheckBox);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
|
||||
12
Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml
Normal file
12
Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml
Normal file
@@ -0,0 +1,12 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:Content.Client.Options.UI">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Orientation="Vertical" Margin="8">
|
||||
<CheckBox Name="EnableClassicOverlayCheckBox" Text="{Loc 'ui-options-enable-classic-overlay'}" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
20
Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs
Normal file
20
Content.Client/Options/UI/Tabs/AdminOptionsTab.xaml.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Options.UI.Tabs;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class AdminOptionsTab : Control
|
||||
{
|
||||
public AdminOptionsTab()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Control.AddOptionCheckBox(CCVars.AdminOverlayClassic, EnableClassicOverlayCheckBox);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +265,51 @@ namespace Content.Client.Options.UI.Tabs
|
||||
AddButton(EngineKeyFunctions.HideUI);
|
||||
AddButton(ContentKeyFunctions.InspectEntity);
|
||||
|
||||
AddHeader("ui-options-header-text-cursor");
|
||||
AddButton(EngineKeyFunctions.TextCursorLeft);
|
||||
AddButton(EngineKeyFunctions.TextCursorRight);
|
||||
AddButton(EngineKeyFunctions.TextCursorUp);
|
||||
AddButton(EngineKeyFunctions.TextCursorDown);
|
||||
AddButton(EngineKeyFunctions.TextCursorWordLeft);
|
||||
AddButton(EngineKeyFunctions.TextCursorWordRight);
|
||||
AddButton(EngineKeyFunctions.TextCursorBegin);
|
||||
AddButton(EngineKeyFunctions.TextCursorEnd);
|
||||
|
||||
AddHeader("ui-options-header-text-cursor-select");
|
||||
AddButton(EngineKeyFunctions.TextCursorSelect);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectLeft);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectRight);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectUp);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectDown);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectWordLeft);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectWordRight);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectBegin);
|
||||
AddButton(EngineKeyFunctions.TextCursorSelectEnd);
|
||||
|
||||
AddHeader("ui-options-header-text-edit");
|
||||
AddButton(EngineKeyFunctions.TextBackspace);
|
||||
AddButton(EngineKeyFunctions.TextDelete);
|
||||
AddButton(EngineKeyFunctions.TextWordBackspace);
|
||||
AddButton(EngineKeyFunctions.TextWordDelete);
|
||||
AddButton(EngineKeyFunctions.TextNewline);
|
||||
AddButton(EngineKeyFunctions.TextSubmit);
|
||||
AddButton(EngineKeyFunctions.MultilineTextSubmit);
|
||||
AddButton(EngineKeyFunctions.TextSelectAll);
|
||||
AddButton(EngineKeyFunctions.TextCopy);
|
||||
AddButton(EngineKeyFunctions.TextCut);
|
||||
AddButton(EngineKeyFunctions.TextPaste);
|
||||
|
||||
AddHeader("ui-options-header-text-chat");
|
||||
AddButton(EngineKeyFunctions.TextHistoryPrev);
|
||||
AddButton(EngineKeyFunctions.TextHistoryNext);
|
||||
AddButton(EngineKeyFunctions.TextReleaseFocus);
|
||||
AddButton(EngineKeyFunctions.TextScrollToBottom);
|
||||
|
||||
AddHeader("ui-options-header-text-other");
|
||||
AddButton(EngineKeyFunctions.TextTabComplete);
|
||||
AddButton(EngineKeyFunctions.TextCompleteNext);
|
||||
AddButton(EngineKeyFunctions.TextCompletePrev);
|
||||
|
||||
foreach (var control in _keyControls.Values)
|
||||
{
|
||||
UpdateKeyControl(control);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -34,11 +35,12 @@ public sealed partial class StencilOverlay
|
||||
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
|
||||
var matty = Matrix3x2.Multiply(matrix, invMatrix);
|
||||
worldHandle.SetTransform(matty);
|
||||
_entManager.TryGetComponent(grid.Owner, out RoofComponent? roofComp);
|
||||
|
||||
foreach (var tile in _map.GetTilesIntersecting(grid.Owner, grid, worldAABB))
|
||||
{
|
||||
// Ignored tiles for stencil
|
||||
if (_weather.CanWeatherAffect(grid.Owner, grid, tile))
|
||||
if (_weather.CanWeatherAffect(grid.Owner, grid, tile, roofComp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,7 @@ namespace Content.Client.PDA
|
||||
|
||||
private void CreateMenu()
|
||||
{
|
||||
_menu = this.CreateWindow<PdaMenu>();
|
||||
_menu.OpenCenteredLeft();
|
||||
_menu = this.CreateWindowCenteredLeft<PdaMenu>();
|
||||
|
||||
_menu.FlashLightToggleButton.OnToggled += _ =>
|
||||
{
|
||||
|
||||
@@ -123,6 +123,12 @@ namespace Content.Client.Popups
|
||||
PopupMessage(message, type, coordinates, null, true);
|
||||
}
|
||||
|
||||
public override void PopupPredictedCoordinates(string? message, EntityCoordinates coordinates, EntityUid? recipient, PopupType type = PopupType.Small)
|
||||
{
|
||||
if (recipient != null && _timing.IsFirstTimePredicted)
|
||||
PopupCoordinates(message, coordinates, recipient.Value, type);
|
||||
}
|
||||
|
||||
private void PopupCursorInternal(string? message, PopupType type, bool recordReplay)
|
||||
{
|
||||
if (message == null)
|
||||
|
||||
8
Content.Client/Power/EntitySystems/PowerNetSystem.cs
Normal file
8
Content.Client/Power/EntitySystems/PowerNetSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Power.EntitySystems;
|
||||
|
||||
namespace Content.Client.Power.EntitySystems;
|
||||
|
||||
public sealed class PowerNetSystem : SharedPowerNetSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -27,6 +27,8 @@ public sealed class PowerReceiverSystem : SharedPowerReceiverSystem
|
||||
return;
|
||||
|
||||
component.Powered = state.Powered;
|
||||
component.NeedsPower = state.NeedsPower;
|
||||
component.PowerDisabled = state.PowerDisabled;
|
||||
}
|
||||
|
||||
public override bool ResolveApc(EntityUid entity, [NotNullWhen(true)] ref SharedApcPowerReceiverComponent? component)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user