Merge branch 'space-wizards:master' into don
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.Username, uiScale, playerInfo.Connected ? Color.Yellow : Color.White);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, playerInfo.CharacterName, uiScale, playerInfo.Connected ? Color.Aquamarine : 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
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,
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -54,6 +54,6 @@ public sealed class AfterLightTargetOverlay : Overlay
|
||||
|
||||
worldHandle.SetTransform(localMatrix);
|
||||
worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
|
||||
}, null);
|
||||
}, Color.Transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ public sealed class PlanetLightSystem : EntitySystem
|
||||
_overlayMan.AddOverlay(new RoofOverlay(EntityManager));
|
||||
_overlayMan.AddOverlay(new TileEmissionOverlay(EntityManager));
|
||||
_overlayMan.AddOverlay(new LightBlurOverlay());
|
||||
_overlayMan.AddOverlay(new SunShadowOverlay());
|
||||
_overlayMan.AddOverlay(new AfterLightTargetOverlay());
|
||||
}
|
||||
|
||||
@@ -31,6 +32,7 @@ public sealed class PlanetLightSystem : EntitySystem
|
||||
_overlayMan.RemoveOverlay<RoofOverlay>();
|
||||
_overlayMan.RemoveOverlay<TileEmissionOverlay>();
|
||||
_overlayMan.RemoveOverlay<LightBlurOverlay>();
|
||||
_overlayMan.RemoveOverlay<SunShadowOverlay>();
|
||||
_overlayMan.RemoveOverlay<AfterLightTargetOverlay>();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
|
||||
@@ -11,19 +12,29 @@ 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);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
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;
|
||||
@@ -17,9 +19,9 @@ public sealed class RoofOverlay : Overlay
|
||||
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly SharedMapSystem _mapSystem;
|
||||
private readonly SharedRoofSystem _roof = default!;
|
||||
private readonly SharedTransformSystem _xformSystem;
|
||||
|
||||
private readonly HashSet<Entity<OccluderComponent>> _occluders = new();
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.BeforeLighting;
|
||||
@@ -33,6 +35,7 @@ public sealed class RoofOverlay : Overlay
|
||||
|
||||
_lookup = _entManager.System<EntityLookupSystem>();
|
||||
_mapSystem = _entManager.System<SharedMapSystem>();
|
||||
_roof = _entManager.System<SharedRoofSystem>();
|
||||
_xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
ZIndex = ContentZIndex;
|
||||
@@ -86,33 +89,20 @@ public sealed class RoofOverlay : Overlay
|
||||
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))
|
||||
{
|
||||
if ((tileRef.Tile.Flags & (byte) TileFlag.Roof) == 0x0)
|
||||
var color = _roof.GetColor(roofEnt, tileRef.GridIndices);
|
||||
|
||||
if (color == null)
|
||||
{
|
||||
// Check if the tile is occluded in which case hide it anyway.
|
||||
// This is to avoid lit walls bleeding over to unlit tiles.
|
||||
_occluders.Clear();
|
||||
_lookup.GetLocalEntitiesIntersecting(grid.Owner, tileRef.GridIndices, _occluders);
|
||||
var found = false;
|
||||
|
||||
foreach (var occluder in _occluders)
|
||||
{
|
||||
if (!occluder.Comp.Enabled)
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!found)
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
|
||||
var local = _lookup.GetLocalBounds(tileRef, grid.Comp.TileSize);
|
||||
worldHandle.DrawRect(local, roof.Color);
|
||||
worldHandle.DrawRect(local, color.Value);
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
154
Content.Client/Light/SunShadowOverlay.cs
Normal file
154
Content.Client/Light/SunShadowOverlay.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
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 worldMatrix = _xformSys.GetWorldMatrix(xform);
|
||||
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++)
|
||||
{
|
||||
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, _target, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@ public sealed class PortableGeneratorBoundUserInterface : BoundUserInterface
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindow<GeneratorWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<GeneratorWindow>();
|
||||
_window.SetEntity(Owner);
|
||||
_window.OnState += args =>
|
||||
{
|
||||
@@ -34,8 +34,6 @@ public sealed class PortableGeneratorBoundUserInterface : BoundUserInterface
|
||||
_window.OnPower += SetTargetPower;
|
||||
_window.OnEjectFuel += EjectFuel;
|
||||
_window.OnSwitchOutput += SwitchOutput;
|
||||
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -269,27 +269,6 @@ public sealed partial class PowerMonitoringWindow
|
||||
return 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 void AutoScrollToFocus()
|
||||
{
|
||||
if (!_autoScrollActive)
|
||||
@@ -299,15 +278,12 @@ public sealed partial class PowerMonitoringWindow
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,9 +30,8 @@ public sealed class SalvageExpeditionConsoleBoundUserInterface : BoundUserInterf
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
_window = this.CreateWindow<OfferingWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<OfferingWindow>();
|
||||
_window.Title = Loc.GetString("salvage-expedition-window-title");
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -22,9 +22,8 @@ public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<OfferingWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<OfferingWindow>();
|
||||
_window.Title = Loc.GetString("salvage-magnet-window-title");
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -21,10 +21,9 @@ public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<IFFConsoleWindow>();
|
||||
_window = this.CreateWindowCenteredLeft<IFFConsoleWindow>();
|
||||
_window.ShowIFF += SendIFFMessage;
|
||||
_window.ShowVessel += SendVesselMessage;
|
||||
_window.OpenCenteredLeft();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
|
||||
@@ -20,12 +20,21 @@ public sealed class SpriteFadeSystem : EntitySystem
|
||||
|
||||
private readonly HashSet<FadingSpriteComponent> _comps = new();
|
||||
|
||||
private EntityQuery<SpriteComponent> _spriteQuery;
|
||||
private EntityQuery<SpriteFadeComponent> _fadeQuery;
|
||||
private EntityQuery<FadingSpriteComponent> _fadingQuery;
|
||||
|
||||
private const float TargetAlpha = 0.4f;
|
||||
private const float ChangeRate = 1f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
_fadeQuery = GetEntityQuery<SpriteFadeComponent>();
|
||||
_fadingQuery = GetEntityQuery<FadingSpriteComponent>();
|
||||
|
||||
SubscribeLocalEvent<FadingSpriteComponent, ComponentShutdown>(OnFadingShutdown);
|
||||
}
|
||||
|
||||
@@ -42,28 +51,26 @@ public sealed class SpriteFadeSystem : EntitySystem
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
var spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
var change = ChangeRate * frameTime;
|
||||
|
||||
if (TryComp(player, out TransformComponent? playerXform) &&
|
||||
_stateManager.CurrentState is GameplayState state &&
|
||||
spriteQuery.TryGetComponent(player, out var playerSprite))
|
||||
_spriteQuery.TryGetComponent(player, out var playerSprite))
|
||||
{
|
||||
var fadeQuery = GetEntityQuery<SpriteFadeComponent>();
|
||||
var mapPos = _transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: playerXform);
|
||||
|
||||
// Also want to handle large entities even if they may not be clickable.
|
||||
foreach (var ent in state.GetClickableEntities(mapPos))
|
||||
{
|
||||
if (ent == player ||
|
||||
!fadeQuery.HasComponent(ent) ||
|
||||
!spriteQuery.TryGetComponent(ent, out var sprite) ||
|
||||
!_fadeQuery.HasComponent(ent) ||
|
||||
!_spriteQuery.TryGetComponent(ent, out var sprite) ||
|
||||
sprite.DrawDepth < playerSprite.DrawDepth)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryComp<FadingSpriteComponent>(ent, out var fading))
|
||||
if (!_fadingQuery.TryComp(ent, out var fading))
|
||||
{
|
||||
fading = AddComp<FadingSpriteComponent>(ent);
|
||||
fading.OriginalAlpha = sprite.Color.A;
|
||||
@@ -85,7 +92,7 @@ public sealed class SpriteFadeSystem : EntitySystem
|
||||
if (_comps.Contains(comp))
|
||||
continue;
|
||||
|
||||
if (!spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
if (!_spriteQuery.TryGetComponent(uid, out var sprite))
|
||||
continue;
|
||||
|
||||
var newColor = Math.Min(sprite.Color.A + change, comp.OriginalAlpha);
|
||||
|
||||
@@ -92,12 +92,4 @@ public sealed class StorageBoundUserInterface : BoundUserInterface
|
||||
Show();
|
||||
LayoutContainer.SetPosition(_window, position);
|
||||
}
|
||||
|
||||
public void ReOpen()
|
||||
{
|
||||
_window?.Orphan();
|
||||
_window = null;
|
||||
Open();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,8 +67,11 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||
// allows a t-ray to show wires/pipes above carpets/puddles
|
||||
if (scannerRevealed)
|
||||
{
|
||||
component.OriginalDrawDepth ??= args.Sprite.DrawDepth;
|
||||
args.Sprite.DrawDepth = (int) Shared.DrawDepth.DrawDepth.FloorObjects + 1;
|
||||
if (component.OriginalDrawDepth is not null)
|
||||
return;
|
||||
component.OriginalDrawDepth = args.Sprite.DrawDepth;
|
||||
var drawDepthDifference = Shared.DrawDepth.DrawDepth.ThickPipe - Shared.DrawDepth.DrawDepth.Puddles;
|
||||
args.Sprite.DrawDepth -= drawDepthDifference - 1;
|
||||
}
|
||||
else if (component.OriginalDrawDepth.HasValue)
|
||||
{
|
||||
|
||||
29
Content.Client/SubFloor/TrayScanRevealSystem.cs
Normal file
29
Content.Client/SubFloor/TrayScanRevealSystem.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.SubFloor;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Content.Client.SubFloor;
|
||||
|
||||
public sealed class TrayScanRevealSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
public bool IsUnderRevealingEntity(EntityUid uid)
|
||||
{
|
||||
var gridUid = _transform.GetGrid(uid);
|
||||
if (gridUid is null)
|
||||
return false;
|
||||
|
||||
var gridComp = Comp<MapGridComponent>(gridUid.Value);
|
||||
var position = _transform.GetGridOrMapTilePosition(uid);
|
||||
|
||||
return HasTrayScanReveal(((EntityUid)gridUid, gridComp), position);
|
||||
}
|
||||
|
||||
private bool HasTrayScanReveal(Entity<MapGridComponent> ent, Vector2i position)
|
||||
{
|
||||
var anchoredEnum = _map.GetAnchoredEntities(ent, position);
|
||||
return anchoredEnum.Any(HasComp<TrayScanRevealComponent>);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedHandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly TrayScanRevealSystem _trayScanReveal = default!;
|
||||
|
||||
private const string TRayAnimationKey = "trays";
|
||||
private const double AnimationLength = 0.3;
|
||||
@@ -82,7 +83,7 @@ public sealed class TrayScannerSystem : SharedTrayScannerSystem
|
||||
|
||||
foreach (var (uid, comp) in inRange)
|
||||
{
|
||||
if (comp.IsUnderCover)
|
||||
if (comp.IsUnderCover || _trayScanReveal.IsUnderRevealingEntity(uid))
|
||||
EnsureComp<TrayRevealedComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using Content.Client.Clothing;
|
||||
using Content.Client.Items.Systems;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Item;
|
||||
using Content.Shared.Toggleable;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -62,7 +63,16 @@ public sealed class ToggleableLightVisualsSystem : VisualizerSystem<ToggleableLi
|
||||
|| !enabled)
|
||||
return;
|
||||
|
||||
if (!component.ClothingVisuals.TryGetValue(args.Slot, out var layers))
|
||||
if (!TryComp(args.Equipee, out InventoryComponent? inventory))
|
||||
return;
|
||||
List<PrototypeLayerData>? layers = null;
|
||||
|
||||
// attempt to get species specific data
|
||||
if (inventory.SpeciesId != null)
|
||||
component.ClothingVisuals.TryGetValue($"{args.Slot}-{inventory.SpeciesId}", out layers);
|
||||
|
||||
// No species specific data. Try to default to generic data.
|
||||
if (layers == null && !component.ClothingVisuals.TryGetValue(args.Slot, out layers))
|
||||
return;
|
||||
|
||||
var modulate = AppearanceSystem.TryGetData<Color>(uid, ToggleableLightVisuals.Color, out var color, appearance);
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class TimerTriggerVisualizerSystem : VisualizerSystem<TimerTrigger
|
||||
{
|
||||
comp.PrimingAnimation.AnimationTracks.Add(
|
||||
new AnimationTrackPlaySound() {
|
||||
KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(comp.PrimingSound), 0) }
|
||||
KeyFrames = { new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(comp.PrimingSound), 0) }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,9 +96,12 @@ public class ListContainer : Control
|
||||
{
|
||||
ListContainerButton control = new(data[0], 0);
|
||||
GenerateItem?.Invoke(data[0], control);
|
||||
// Yes this AddChild is necessary for reasons (get proper style or whatever?)
|
||||
// without it the DesiredSize may be different to the final DesiredSize.
|
||||
AddChild(control);
|
||||
control.Measure(Vector2Helpers.Infinity);
|
||||
_itemHeight = control.DesiredSize.Y;
|
||||
control.Dispose();
|
||||
control.Orphan();
|
||||
}
|
||||
|
||||
// Ensure buttons are re-generated.
|
||||
@@ -384,6 +387,7 @@ public sealed class ListContainerButton : ContainerButton, IEntityControl
|
||||
|
||||
public ListContainerButton(ListData data, int index)
|
||||
{
|
||||
AddStyleClass(StyleClassButton);
|
||||
Data = data;
|
||||
Index = index;
|
||||
// AddChild(Background = new PanelContainer
|
||||
|
||||
@@ -175,7 +175,7 @@ public sealed class AHelpUIController: UIController, IOnSystemChanged<BwoinkSyst
|
||||
UIHelper = isAdmin ? new AdminAHelpUIHandler(ownerUserId) : new UserAHelpUIHandler(ownerUserId);
|
||||
UIHelper.DiscordRelayChanged(_discordRelayActive);
|
||||
|
||||
UIHelper.SendMessageAction = (userId, textMessage, playSound) => _bwoinkSystem?.Send(userId, textMessage, playSound);
|
||||
UIHelper.SendMessageAction = (userId, textMessage, playSound, adminOnly) => _bwoinkSystem?.Send(userId, textMessage, playSound, adminOnly);
|
||||
UIHelper.InputTextChanged += (channel, text) => _bwoinkSystem?.SendInputTextUpdated(channel, text.Length > 0);
|
||||
UIHelper.OnClose += () => { SetAHelpPressed(false); };
|
||||
UIHelper.OnOpen += () => { SetAHelpPressed(true); };
|
||||
@@ -324,7 +324,7 @@ public interface IAHelpUIHandler : IDisposable
|
||||
public void PeopleTypingUpdated(BwoinkPlayerTypingUpdated args);
|
||||
public event Action OnClose;
|
||||
public event Action OnOpen;
|
||||
public Action<NetUserId, string, bool>? SendMessageAction { get; set; }
|
||||
public Action<NetUserId, string, bool, bool>? SendMessageAction { get; set; }
|
||||
public event Action<NetUserId, string>? InputTextChanged;
|
||||
}
|
||||
public sealed class AdminAHelpUIHandler : IAHelpUIHandler
|
||||
@@ -408,7 +408,7 @@ public sealed class AdminAHelpUIHandler : IAHelpUIHandler
|
||||
|
||||
public event Action? OnClose;
|
||||
public event Action? OnOpen;
|
||||
public Action<NetUserId, string, bool>? SendMessageAction { get; set; }
|
||||
public Action<NetUserId, string, bool, bool>? SendMessageAction { get; set; }
|
||||
public event Action<NetUserId, string>? InputTextChanged;
|
||||
|
||||
public void Open(NetUserId channelId, bool relayActive)
|
||||
@@ -462,7 +462,7 @@ public sealed class AdminAHelpUIHandler : IAHelpUIHandler
|
||||
if (_activePanelMap.TryGetValue(channelId, out var existingPanel))
|
||||
return existingPanel;
|
||||
|
||||
_activePanelMap[channelId] = existingPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(channelId, text, Window?.Bwoink.PlaySound.Pressed ?? true));
|
||||
_activePanelMap[channelId] = existingPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(channelId, text, Window?.Bwoink.PlaySound.Pressed ?? true, Window?.Bwoink.AdminOnly.Pressed ?? false));
|
||||
existingPanel.InputTextChanged += text => InputTextChanged?.Invoke(channelId, text);
|
||||
existingPanel.Visible = false;
|
||||
if (!Control!.BwoinkArea.Children.Contains(existingPanel))
|
||||
@@ -548,7 +548,7 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
|
||||
|
||||
public event Action? OnClose;
|
||||
public event Action? OnOpen;
|
||||
public Action<NetUserId, string, bool>? SendMessageAction { get; set; }
|
||||
public Action<NetUserId, string, bool, bool>? SendMessageAction { get; set; }
|
||||
public event Action<NetUserId, string>? InputTextChanged;
|
||||
|
||||
public void Open(NetUserId channelId, bool relayActive)
|
||||
@@ -561,7 +561,7 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
|
||||
{
|
||||
if (_window is { Disposed: false })
|
||||
return;
|
||||
_chatPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(_ownerId, text, true));
|
||||
_chatPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(_ownerId, text, true, false));
|
||||
_chatPanel.InputTextChanged += text => InputTextChanged?.Invoke(_ownerId, text);
|
||||
_chatPanel.RelayedToDiscordLabel.Visible = relayActive;
|
||||
_window = new DefaultWindow()
|
||||
|
||||
@@ -74,7 +74,7 @@ public sealed class CloseRecentWindowUIController : UIController
|
||||
/// internal recentlyInteractedWindows tracking.
|
||||
/// </summary>
|
||||
/// <param name="window"></param>
|
||||
private void SetMostRecentlyInteractedWindow(BaseWindow window)
|
||||
public void SetMostRecentlyInteractedWindow(BaseWindow window)
|
||||
{
|
||||
// Search through the list and see if already added.
|
||||
// (This search is backwards since it's fairly common that the user is clicking the same
|
||||
@@ -134,7 +134,6 @@ public sealed class CloseRecentWindowUIController : UIController
|
||||
if (window.IsOpen)
|
||||
return true;
|
||||
|
||||
recentlyInteractedWindows.RemoveAt(i);
|
||||
// continue going down the list, hoping to find a still-open window
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ public sealed class DamageOverlayUiController : UIController
|
||||
{
|
||||
_overlay.DeadLevel = 0f;
|
||||
_overlay.CritLevel = 0f;
|
||||
_overlay.BruteLevel = 0f;
|
||||
_overlay.PainLevel = 0f;
|
||||
_overlay.OxygenLevel = 0f;
|
||||
}
|
||||
|
||||
@@ -95,13 +95,22 @@ public sealed class DamageOverlayUiController : UIController
|
||||
{
|
||||
case MobState.Alive:
|
||||
{
|
||||
if (EntityManager.HasComponent<PainNumbnessComponent>(entity))
|
||||
FixedPoint2 painLevel = 0;
|
||||
_overlay.PainLevel = 0;
|
||||
|
||||
if (!EntityManager.HasComponent<PainNumbnessComponent>(entity))
|
||||
{
|
||||
_overlay.BruteLevel = 0;
|
||||
}
|
||||
else if (damageable.DamagePerGroup.TryGetValue("Brute", out var bruteDamage))
|
||||
{
|
||||
_overlay.BruteLevel = FixedPoint2.Min(1f, bruteDamage / critThreshold).Float();
|
||||
foreach (var painDamageType in damageable.PainDamageGroups)
|
||||
{
|
||||
damageable.DamagePerGroup.TryGetValue(painDamageType, out var painDamage);
|
||||
painLevel += painDamage;
|
||||
}
|
||||
_overlay.PainLevel = FixedPoint2.Min(1f, painLevel / critThreshold).Float();
|
||||
|
||||
if (_overlay.PainLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
|
||||
{
|
||||
_overlay.PainLevel = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (damageable.DamagePerGroup.TryGetValue("Airloss", out var oxyDamage))
|
||||
@@ -109,11 +118,6 @@ public sealed class DamageOverlayUiController : UIController
|
||||
_overlay.OxygenLevel = FixedPoint2.Min(1f, oxyDamage / critThreshold).Float();
|
||||
}
|
||||
|
||||
if (_overlay.BruteLevel < 0.05f) // Don't show damage overlay if they're near enough to max.
|
||||
{
|
||||
_overlay.BruteLevel = 0;
|
||||
}
|
||||
|
||||
_overlay.CritLevel = 0;
|
||||
_overlay.DeadLevel = 0;
|
||||
break;
|
||||
@@ -125,13 +129,13 @@ public sealed class DamageOverlayUiController : UIController
|
||||
return;
|
||||
_overlay.CritLevel = critLevel.Value.Float();
|
||||
|
||||
_overlay.BruteLevel = 0;
|
||||
_overlay.PainLevel = 0;
|
||||
_overlay.DeadLevel = 0;
|
||||
break;
|
||||
}
|
||||
case MobState.Dead:
|
||||
{
|
||||
_overlay.BruteLevel = 0;
|
||||
_overlay.PainLevel = 0;
|
||||
_overlay.CritLevel = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ public sealed class DamageOverlay : Overlay
|
||||
/// <summary>
|
||||
/// Handles the red pulsing overlay
|
||||
/// </summary>
|
||||
public float BruteLevel = 0f;
|
||||
public float PainLevel = 0f;
|
||||
|
||||
private float _oldBruteLevel = 0f;
|
||||
private float _oldPainLevel = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the darkening overlay.
|
||||
@@ -92,14 +92,14 @@ public sealed class DamageOverlay : Overlay
|
||||
DeadLevel = 0f;
|
||||
}
|
||||
|
||||
if (!MathHelper.CloseTo(_oldBruteLevel, BruteLevel, 0.001f))
|
||||
if (!MathHelper.CloseTo(_oldPainLevel, PainLevel, 0.001f))
|
||||
{
|
||||
var diff = BruteLevel - _oldBruteLevel;
|
||||
_oldBruteLevel += GetDiff(diff, lastFrameTime);
|
||||
var diff = PainLevel - _oldPainLevel;
|
||||
_oldPainLevel += GetDiff(diff, lastFrameTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
_oldBruteLevel = BruteLevel;
|
||||
_oldPainLevel = PainLevel;
|
||||
}
|
||||
|
||||
if (!MathHelper.CloseTo(_oldOxygenLevel, OxygenLevel, 0.001f))
|
||||
@@ -135,7 +135,7 @@ public sealed class DamageOverlay : Overlay
|
||||
|
||||
// Makes debugging easier don't @ me
|
||||
float level = 0f;
|
||||
level = _oldBruteLevel;
|
||||
level = _oldPainLevel;
|
||||
|
||||
// TODO: Lerping
|
||||
if (level > 0f && _oldCritLevel <= 0f)
|
||||
@@ -165,7 +165,7 @@ public sealed class DamageOverlay : Overlay
|
||||
}
|
||||
else
|
||||
{
|
||||
_oldBruteLevel = BruteLevel;
|
||||
_oldPainLevel = PainLevel;
|
||||
}
|
||||
|
||||
level = State != MobState.Critical ? _oldOxygenLevel : 1f;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Client.Interaction;
|
||||
using Content.Client.Storage;
|
||||
using Content.Client.Storage.Systems;
|
||||
using Content.Client.UserInterface.Systems.Hotbar.Widgets;
|
||||
using Content.Client.UserInterface.Systems.Info;
|
||||
using Content.Client.UserInterface.Systems.Storage.Controls;
|
||||
using Content.Client.Verbs.UI;
|
||||
using Content.Shared.CCVar;
|
||||
@@ -37,6 +38,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IInputManager _input = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly CloseRecentWindowUIController _closeRecentWindowUIController = default!;
|
||||
[UISystemDependency] private readonly StorageSystem _storage = default!;
|
||||
[UISystemDependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
@@ -61,39 +63,11 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
UIManager.OnScreenChanged += OnScreenChange;
|
||||
|
||||
_configuration.OnValueChanged(CCVars.StaticStorageUI, OnStaticStorageChanged, true);
|
||||
_configuration.OnValueChanged(CCVars.OpaqueStorageWindow, OnOpaqueWindowChanged, true);
|
||||
_configuration.OnValueChanged(CCVars.StorageWindowTitle, OnStorageWindowTitle, true);
|
||||
}
|
||||
|
||||
private void OnScreenChange((UIScreen? Old, UIScreen? New) obj)
|
||||
{
|
||||
// Handle reconnects with hotbargui.
|
||||
|
||||
// Essentially HotbarGui / the screen gets loaded AFTER gamestates at the moment (because clientgameticker manually changes it via event)
|
||||
// and changing this may be a massive change.
|
||||
// So instead we'll just manually reload it for now.
|
||||
if (!StaticStorageUIEnabled ||
|
||||
obj.New == null ||
|
||||
!EntityManager.TryGetComponent(_player.LocalEntity, out UserInterfaceUserComponent? userComp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// UISystemDependency not injected at this point so do it the old fashion way, I love ordering issues.
|
||||
var uiSystem = EntityManager.System<SharedUserInterfaceSystem>();
|
||||
|
||||
foreach (var bui in uiSystem.GetActorUis((_player.LocalEntity.Value, userComp)))
|
||||
{
|
||||
if (!uiSystem.TryGetOpenUi<StorageBoundUserInterface>(bui.Entity, StorageComponent.StorageUiKey.Key, out var storageBui))
|
||||
continue;
|
||||
|
||||
storageBui.ReOpen();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStorageWindowTitle(bool obj)
|
||||
{
|
||||
WindowTitle = obj;
|
||||
@@ -126,6 +100,7 @@ public sealed class StorageUIController : UIController, IOnSystemChanged<Storage
|
||||
if (StaticStorageUIEnabled)
|
||||
{
|
||||
UIManager.GetActiveUIWidgetOrNull<HotbarGui>()?.StorageContainer.AddChild(window);
|
||||
_closeRecentWindowUIController.SetMostRecentlyInteractedWindow(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -16,4 +16,9 @@ public sealed partial class VendingMachineItem : BoxContainer
|
||||
|
||||
NameLabel.Text = text;
|
||||
}
|
||||
|
||||
public void SetText(string text)
|
||||
{
|
||||
NameLabel.Text = text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Robust.Client.AutoGenerated;
|
||||
@@ -19,11 +20,16 @@ namespace Content.Client.VendingMachines.UI
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private readonly Dictionary<EntProtoId, EntityUid> _dummies = [];
|
||||
private readonly Dictionary<EntProtoId, (ListContainerButton Button, VendingMachineItem Item)> _listItems = new();
|
||||
private readonly Dictionary<EntProtoId, uint> _amounts = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the vending machine is able to be interacted with or not.
|
||||
/// </summary>
|
||||
private bool _enabled;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnItemSelected;
|
||||
|
||||
private readonly StyleBoxFlat _styleBox = new() { BackgroundColor = new Color(70, 73, 102) };
|
||||
|
||||
public VendingMachineMenu()
|
||||
{
|
||||
MinSize = SetSize = new Vector2(250, 150);
|
||||
@@ -68,18 +74,23 @@ namespace Content.Client.VendingMachines.UI
|
||||
if (data is not VendorItemsListData { ItemProtoID: var protoID, ItemText: var text })
|
||||
return;
|
||||
|
||||
button.AddChild(new VendingMachineItem(protoID, text));
|
||||
|
||||
button.ToolTip = text;
|
||||
button.StyleBoxOverride = _styleBox;
|
||||
var item = new VendingMachineItem(protoID, text);
|
||||
_listItems[protoID] = (button, item);
|
||||
button.AddChild(item);
|
||||
button.AddStyleClass("ButtonSquare");
|
||||
button.Disabled = !_enabled || _amounts[protoID] == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the list of available items on the vending machine interface
|
||||
/// and sets icons based on their prototypes
|
||||
/// </summary>
|
||||
public void Populate(List<VendingMachineInventoryEntry> inventory)
|
||||
public void Populate(List<VendingMachineInventoryEntry> inventory, bool enabled)
|
||||
{
|
||||
_enabled = enabled;
|
||||
_listItems.Clear();
|
||||
_amounts.Clear();
|
||||
|
||||
if (inventory.Count == 0 && VendingContents.Visible)
|
||||
{
|
||||
SearchBar.Visible = false;
|
||||
@@ -109,7 +120,10 @@ namespace Content.Client.VendingMachines.UI
|
||||
var entry = inventory[i];
|
||||
|
||||
if (!_prototypeManager.TryIndex(entry.ID, out var prototype))
|
||||
{
|
||||
_amounts[entry.ID] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_dummies.TryGetValue(entry.ID, out var dummy))
|
||||
{
|
||||
@@ -119,11 +133,15 @@ namespace Content.Client.VendingMachines.UI
|
||||
|
||||
var itemName = Identity.Name(dummy, _entityManager);
|
||||
var itemText = $"{itemName} [{entry.Amount}]";
|
||||
_amounts[entry.ID] = entry.Amount;
|
||||
|
||||
if (itemText.Length > longestEntry.Length)
|
||||
longestEntry = itemText;
|
||||
|
||||
listData.Add(new VendorItemsListData(prototype.ID, itemText, i));
|
||||
listData.Add(new VendorItemsListData(prototype.ID, i)
|
||||
{
|
||||
ItemText = itemText,
|
||||
});
|
||||
}
|
||||
|
||||
VendingContents.PopulateList(listData);
|
||||
@@ -131,12 +149,43 @@ namespace Content.Client.VendingMachines.UI
|
||||
SetSizeAfterUpdate(longestEntry.Length, inventory.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates text entries for vending data in place without modifying the list controls.
|
||||
/// </summary>
|
||||
public void UpdateAmounts(List<VendingMachineInventoryEntry> cachedInventory, bool enabled)
|
||||
{
|
||||
_enabled = enabled;
|
||||
|
||||
foreach (var proto in _dummies.Keys)
|
||||
{
|
||||
if (!_listItems.TryGetValue(proto, out var button))
|
||||
continue;
|
||||
|
||||
var dummy = _dummies[proto];
|
||||
var amount = cachedInventory.First(o => o.ID == proto).Amount;
|
||||
// Could be better? Problem is all inventory entries get squashed.
|
||||
var text = GetItemText(dummy, amount);
|
||||
|
||||
button.Item.SetText(text);
|
||||
button.Button.Disabled = !enabled || amount == 0;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetItemText(EntityUid dummy, uint amount)
|
||||
{
|
||||
var itemName = Identity.Name(dummy, _entityManager);
|
||||
return $"{itemName} [{amount}]";
|
||||
}
|
||||
|
||||
private void SetSizeAfterUpdate(int longestEntryLength, int contentCount)
|
||||
{
|
||||
SetSize = new Vector2(Math.Clamp((longestEntryLength + 2) * 12, 250, 400),
|
||||
Math.Clamp(contentCount * 50, 150, 350));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public record VendorItemsListData(EntProtoId ItemProtoID, string ItemText, int ItemIndex) : ListData;
|
||||
public record VendorItemsListData(EntProtoId ItemProtoID, int ItemIndex) : ListData
|
||||
{
|
||||
public string ItemText = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ namespace Content.Client.VendingMachines
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<VendingMachineMenu>();
|
||||
_menu.OpenCenteredLeft();
|
||||
_menu = this.CreateWindowCenteredLeft<VendingMachineMenu>();
|
||||
_menu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
_menu.OnItemSelected += OnItemSelected;
|
||||
Refresh();
|
||||
@@ -32,10 +31,21 @@ namespace Content.Client.VendingMachines
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting;
|
||||
|
||||
var system = EntMan.System<VendingMachineSystem>();
|
||||
_cachedInventory = system.GetAllInventory(Owner);
|
||||
|
||||
_menu?.Populate(_cachedInventory);
|
||||
_menu?.Populate(_cachedInventory, enabled);
|
||||
}
|
||||
|
||||
public void UpdateAmounts()
|
||||
{
|
||||
var enabled = EntMan.TryGetComponent(Owner, out VendingMachineComponent? bendy) && !bendy.Ejecting;
|
||||
|
||||
var system = EntMan.System<VendingMachineSystem>();
|
||||
_cachedInventory = system.GetAllInventory(Owner);
|
||||
_menu?.UpdateAmounts(_cachedInventory, enabled);
|
||||
}
|
||||
|
||||
private void OnItemSelected(GUIBoundKeyEventArgs args, ListData data)
|
||||
@@ -54,7 +64,7 @@ namespace Content.Client.VendingMachines
|
||||
if (selectedItem == null)
|
||||
return;
|
||||
|
||||
SendMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID));
|
||||
SendPredictedMessage(new VendingMachineEjectMessage(selectedItem.Type, selectedItem.ID));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.VendingMachines;
|
||||
|
||||
@@ -8,7 +10,6 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -16,14 +17,69 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<VendingMachineComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<VendingMachineComponent, AfterAutoHandleStateEvent>(OnVendingAfterState);
|
||||
SubscribeLocalEvent<VendingMachineComponent, ComponentHandleState>(OnVendingHandleState);
|
||||
}
|
||||
|
||||
private void OnVendingAfterState(EntityUid uid, VendingMachineComponent component, ref AfterAutoHandleStateEvent args)
|
||||
private void OnVendingHandleState(Entity<VendingMachineComponent> entity, ref ComponentHandleState args)
|
||||
{
|
||||
if (_uiSystem.TryGetOpenUi<VendingMachineBoundUserInterface>(uid, VendingMachineUiKey.Key, out var bui))
|
||||
if (args.Current is not VendingMachineComponentState state)
|
||||
return;
|
||||
|
||||
var uid = entity.Owner;
|
||||
var component = entity.Comp;
|
||||
|
||||
component.Contraband = state.Contraband;
|
||||
component.EjectEnd = state.EjectEnd;
|
||||
component.DenyEnd = state.DenyEnd;
|
||||
component.DispenseOnHitEnd = state.DispenseOnHitEnd;
|
||||
|
||||
// If all we did was update amounts then we can leave BUI buttons in place.
|
||||
var fullUiUpdate = !component.Inventory.Keys.SequenceEqual(state.Inventory.Keys) ||
|
||||
!component.EmaggedInventory.Keys.SequenceEqual(state.EmaggedInventory.Keys) ||
|
||||
!component.ContrabandInventory.Keys.SequenceEqual(state.ContrabandInventory.Keys);
|
||||
|
||||
component.Inventory.Clear();
|
||||
component.EmaggedInventory.Clear();
|
||||
component.ContrabandInventory.Clear();
|
||||
|
||||
foreach (var entry in state.Inventory)
|
||||
{
|
||||
bui.Refresh();
|
||||
component.Inventory.Add(entry.Key, new(entry.Value));
|
||||
}
|
||||
|
||||
foreach (var entry in state.EmaggedInventory)
|
||||
{
|
||||
component.EmaggedInventory.Add(entry.Key, new(entry.Value));
|
||||
}
|
||||
|
||||
foreach (var entry in state.ContrabandInventory)
|
||||
{
|
||||
component.ContrabandInventory.Add(entry.Key, new(entry.Value));
|
||||
}
|
||||
|
||||
if (UISystem.TryGetOpenUi<VendingMachineBoundUserInterface>(uid, VendingMachineUiKey.Key, out var bui))
|
||||
{
|
||||
if (fullUiUpdate)
|
||||
{
|
||||
bui.Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
bui.UpdateAmounts();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateUI(Entity<VendingMachineComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return;
|
||||
|
||||
if (UISystem.TryGetOpenUi<VendingMachineBoundUserInterface>(entity.Owner,
|
||||
VendingMachineUiKey.Key,
|
||||
out var bui))
|
||||
{
|
||||
bui.UpdateAmounts();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,13 +126,13 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem
|
||||
if (component.LoopDenyAnimation)
|
||||
SetLayerState(VendingMachineVisualLayers.BaseUnshaded, component.DenyState, sprite);
|
||||
else
|
||||
PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.DenyState, component.DenyDelay, sprite);
|
||||
PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.DenyState, (float)component.DenyDelay.TotalSeconds, sprite);
|
||||
|
||||
SetLayerState(VendingMachineVisualLayers.Screen, component.ScreenState, sprite);
|
||||
break;
|
||||
|
||||
case VendingMachineVisualState.Eject:
|
||||
PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.EjectState, component.EjectDelay, sprite);
|
||||
PlayAnimation(uid, VendingMachineVisualLayers.BaseUnshaded, component.EjectState, (float)component.EjectDelay.TotalSeconds, sprite);
|
||||
SetLayerState(VendingMachineVisualLayers.Screen, component.ScreenState, sprite);
|
||||
break;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Melee.Components;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -89,16 +90,6 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
|
||||
// TODO using targeted actions while combat mode is enabled should NOT trigger attacks.
|
||||
|
||||
// TODO: Need to make alt-fire melee its own component I guess?
|
||||
// Melee and guns share a lot in the middle but share virtually nothing at the start and end so
|
||||
// it's kinda tricky.
|
||||
// I think as long as we make secondaries their own component it's probably fine
|
||||
// as long as guncomp has an alt-use key then it shouldn't be too much of a PITA to deal with.
|
||||
if (TryComp<GunComponent>(weaponUid, out var gun) && gun.UseKey)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var mousePos = _eyeManager.PixelToMap(_inputManager.MouseScreenPosition);
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
@@ -116,6 +107,30 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
{
|
||||
coordinates = TransformSystem.ToCoordinates(_map.GetMap(mousePos.MapId), mousePos);
|
||||
}
|
||||
|
||||
// If the gun has AltFireComponent, it can be used to attack.
|
||||
if (TryComp<GunComponent>(weaponUid, out var gun) && gun.UseKey)
|
||||
{
|
||||
if (!TryComp<AltFireMeleeComponent>(weaponUid, out var altFireComponent) || altDown != BoundKeyState.Down)
|
||||
return;
|
||||
|
||||
switch(altFireComponent.AttackType)
|
||||
{
|
||||
case AltFireAttackType.Light:
|
||||
ClientLightAttack(entity, mousePos, coordinates, weaponUid, weapon);
|
||||
break;
|
||||
|
||||
case AltFireAttackType.Heavy:
|
||||
ClientHeavyAttack(entity, coordinates, weaponUid, weapon);
|
||||
break;
|
||||
|
||||
case AltFireAttackType.Disarm:
|
||||
ClientDisarm(entity, mousePos, coordinates);
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Heavy attack.
|
||||
if (altDown == BoundKeyState.Down)
|
||||
@@ -123,14 +138,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
// If it's an unarmed attack then do a disarm
|
||||
if (weapon.AltDisarm && weaponUid == entity)
|
||||
{
|
||||
EntityUid? target = null;
|
||||
|
||||
if (_stateManager.CurrentState is GameplayStateBase screen)
|
||||
{
|
||||
target = screen.GetClickedEntity(mousePos);
|
||||
}
|
||||
|
||||
EntityManager.RaisePredictiveEvent(new DisarmAttackEvent(GetNetEntity(target), GetNetCoordinates(coordinates)));
|
||||
ClientDisarm(entity, mousePos, coordinates);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,28 +148,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
|
||||
// Light attack
|
||||
if (useDown == BoundKeyState.Down)
|
||||
{
|
||||
var attackerPos = TransformSystem.GetMapCoordinates(entity);
|
||||
|
||||
if (mousePos.MapId != attackerPos.MapId ||
|
||||
(attackerPos.Position - mousePos.Position).Length() > weapon.Range)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EntityUid? target = null;
|
||||
|
||||
if (_stateManager.CurrentState is GameplayStateBase screen)
|
||||
{
|
||||
target = screen.GetClickedEntity(mousePos);
|
||||
}
|
||||
|
||||
// Don't light-attack if interaction will be handling this instead
|
||||
if (Interaction.CombatModeCanHandInteract(entity, target))
|
||||
return;
|
||||
|
||||
RaisePredictiveEvent(new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(coordinates)));
|
||||
}
|
||||
ClientLightAttack(entity, mousePos, coordinates, weaponUid, weapon);
|
||||
}
|
||||
|
||||
protected override bool InRange(EntityUid user, EntityUid target, float range, ICommonSession? session)
|
||||
@@ -235,6 +222,35 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
||||
var entities = GetNetEntityList(ArcRayCast(userPos, direction.ToWorldAngle(), component.Angle, distance, userXform.MapID, user).ToList());
|
||||
RaisePredictiveEvent(new HeavyAttackEvent(GetNetEntity(meleeUid), entities.GetRange(0, Math.Min(MaxTargets, entities.Count)), GetNetCoordinates(coordinates)));
|
||||
}
|
||||
|
||||
private void ClientDisarm(EntityUid attacker, MapCoordinates mousePos, EntityCoordinates coordinates)
|
||||
{
|
||||
EntityUid? target = null;
|
||||
|
||||
if (_stateManager.CurrentState is GameplayStateBase screen)
|
||||
target = screen.GetClickedEntity(mousePos);
|
||||
|
||||
RaisePredictiveEvent(new DisarmAttackEvent(GetNetEntity(target), GetNetCoordinates(coordinates)));
|
||||
}
|
||||
|
||||
private void ClientLightAttack(EntityUid attacker, MapCoordinates mousePos, EntityCoordinates coordinates, EntityUid weaponUid, MeleeWeaponComponent meleeComponent)
|
||||
{
|
||||
var attackerPos = TransformSystem.GetMapCoordinates(attacker);
|
||||
|
||||
if (mousePos.MapId != attackerPos.MapId || (attackerPos.Position - mousePos.Position).Length() > meleeComponent.Range)
|
||||
return;
|
||||
|
||||
EntityUid? target = null;
|
||||
|
||||
if (_stateManager.CurrentState is GameplayStateBase screen)
|
||||
target = screen.GetClickedEntity(mousePos);
|
||||
|
||||
// Don't light-attack if interaction will be handling this instead
|
||||
if (Interaction.CombatModeCanHandInteract(attacker, target))
|
||||
return;
|
||||
|
||||
RaisePredictiveEvent(new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(coordinates)));
|
||||
}
|
||||
|
||||
private void OnMeleeLunge(MeleeLungeEvent ev)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlay = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly MapSystem _mapSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -73,11 +74,11 @@ public sealed class TetherGunSystem : SharedTetherGunSystem
|
||||
|
||||
if (_mapManager.TryFindGridAt(mouseWorldPos, out var gridUid, out _))
|
||||
{
|
||||
coords = EntityCoordinates.FromMap(gridUid, mouseWorldPos, TransformSystem);
|
||||
coords = TransformSystem.ToCoordinates(gridUid, mouseWorldPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
coords = EntityCoordinates.FromMap(_mapManager.GetMapEntityId(mouseWorldPos.MapId), mouseWorldPos, TransformSystem);
|
||||
coords = TransformSystem.ToCoordinates(_mapSystem.GetMap(mouseWorldPos.MapId), mouseWorldPos);
|
||||
}
|
||||
|
||||
const float BufferDistance = 0.1f;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Light.Components;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -57,6 +58,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
// Work out tiles nearby to determine volume.
|
||||
if (TryComp<MapGridComponent>(entXform.GridUid, out var grid))
|
||||
{
|
||||
TryComp(entXform.GridUid, out RoofComponent? roofComp);
|
||||
var gridId = entXform.GridUid.Value;
|
||||
// FloodFill to the nearest tile and use that for audio.
|
||||
var seed = _mapSystem.GetTileRef(gridId, grid, entXform.Coordinates);
|
||||
@@ -71,7 +73,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
if (!visited.Add(node.GridIndices))
|
||||
continue;
|
||||
|
||||
if (!CanWeatherAffect(entXform.GridUid.Value, grid, node))
|
||||
if (!CanWeatherAffect(entXform.GridUid.Value, grid, node, roofComp))
|
||||
{
|
||||
// Add neighbors
|
||||
// TODO: Ideally we pick some deterministically random direction and use that
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user