Merge branch 'space-wizards:master' into don

This commit is contained in:
TytosB
2025-03-08 13:46:37 -06:00
committed by GitHub
1108 changed files with 42043 additions and 24701 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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" />

View File

@@ -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;
}

View File

@@ -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 += () =>

View File

@@ -0,0 +1,5 @@
using Content.Shared.Advertise.Systems;
namespace Content.Client.Advertise.Systems;
public sealed class SpeakOnUIClosedSystem : SharedSpeakOnUIClosedSystem;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)

View File

@@ -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)

View File

@@ -218,7 +218,7 @@ public sealed partial class ContentAudioSystem
return;
var file = _gameTicker.RestartSound;
if (string.IsNullOrEmpty(file))
if (ResolvedSoundSpecifier.IsNullOrEmpty(file))
{
return;
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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)

View File

@@ -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
{

View File

@@ -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();
}

View File

@@ -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
};

View File

@@ -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>

View File

@@ -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++)
{

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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; }
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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()

View File

@@ -0,0 +1,5 @@
using Content.Shared.Delivery;
namespace Content.Client.Delivery;
public sealed class DeliverySystem : SharedDeliverySystem;

View 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,
}

View File

@@ -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)
}
});
}

View File

@@ -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; }

View File

@@ -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()

View File

@@ -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
{

View File

@@ -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;

View 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>

View 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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,

View File

@@ -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)

View File

@@ -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;
}
}

View 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);
}
}
}

View 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>

View 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;
}
}
}
}

View File

@@ -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)));
}
}
}

View File

@@ -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)

View File

@@ -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 += _ =>
{

View File

@@ -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>

View File

@@ -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)))

View File

@@ -54,6 +54,6 @@ public sealed class AfterLightTargetOverlay : Overlay
worldHandle.SetTransform(localMatrix);
worldHandle.DrawTextureRectRegion(lightOverlay.EnlargedLightTarget.Texture, bounds, subRegion: subRegion);
}, null);
}, Color.Transparent);
}
}

View File

@@ -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>();
}
}

View 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();
}
}

View File

@@ -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);

View File

@@ -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);

View 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);
}
}
}

View File

@@ -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 =

View File

@@ -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"/>

View File

@@ -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" />

View File

@@ -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));

View File

@@ -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 =>
{

View File

@@ -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;

View File

@@ -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!;

View File

@@ -8,5 +8,6 @@
<tabs:KeyRebindTab Name="KeyRebindTab" />
<tabs:AudioTab Name="AudioTab" />
<tabs:AccessibilityTab Name="AccessibilityTab" />
<tabs:AdminOptionsTab Name="AdminOptionsTab" />
</TabContainer>
</DefaultWindow>

View File

@@ -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();
}
}
}

View File

@@ -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" />

View File

@@ -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();
}

View 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>

View 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();
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -0,0 +1,8 @@
using Content.Shared.Power.EntitySystems;
namespace Content.Client.Power.EntitySystems;
public sealed class PowerNetSystem : SharedPowerNetSystem
{
}

View File

@@ -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)

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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);

View File

@@ -92,12 +92,4 @@ public sealed class StorageBoundUserInterface : BoundUserInterface
Show();
LayoutContainer.SetPosition(_window, position);
}
public void ReOpen()
{
_window?.Orphan();
_window = null;
Open();
}
}

View File

@@ -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)
{

View 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>);
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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) }
}
);
}

View File

@@ -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

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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
{

View File

@@ -16,4 +16,9 @@ public sealed partial class VendingMachineItem : BoxContainer
NameLabel.Text = text;
}
public void SetText(string text)
{
NameLabel.Text = text;
}
}

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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