diff --git a/.editorconfig b/.editorconfig
index 872a068c7c..58d0d332bb 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -9,7 +9,7 @@ indent_style = space
tab_width = 4
# New line preferences
-#end_of_line = crlf
+end_of_line = crlf:suggestion
insert_final_newline = true
trim_trailing_whitespace = true
@@ -104,6 +104,7 @@ csharp_preferred_modifier_order = public, private, protected, internal, new, abs
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
+csharp_style_namespace_declarations = file_scoped:suggestion
#### C# Formatting Rules ####
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
index a44cac2cd1..877273d764 100644
--- a/.github/workflows/changelog.yml
+++ b/.github/workflows/changelog.yml
@@ -35,14 +35,14 @@ jobs:
- name: Install Dependencies
run: |
- cd "Tools/changelog"
+ cd "Tools/changelogs"
npm install
shell: bash
continue-on-error: true
- name: Generate Changelog
run: |
- cd "Tools/changelog"
+ cd "Tools/changelogs"
node changelog.js
shell: bash
continue-on-error: true
diff --git a/.github/workflows/prtitlecase.yml b/.github/workflows/prtitlecase.yml
new file mode 100644
index 0000000000..b3150dcc7e
--- /dev/null
+++ b/.github/workflows/prtitlecase.yml
@@ -0,0 +1,34 @@
+name: PR Title Case
+on:
+ pull_request_target:
+ types: [opened, edited, synchronize]
+
+env:
+ GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+
+jobs:
+ prtitlecase:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Master
+ uses: actions/checkout@v3
+ with:
+ token: ${{ secrets.BOT_TOKEN }}
+
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18.x
+
+ - name: Install Dependencies
+ run: |
+ cd "Tools/prtitlecase"
+ npm install
+ shell: bash
+
+ - name: Change Title
+ run: |
+ cd "Tools/prtitlecase"
+ node index.js
+ shell: bash
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 0e0d3ae890..dc6e26cbea 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,10 @@
{
"omnisharp.analyzeOpenDocumentsOnly": true,
- "dotnet.defaultSolution": "SpaceStation14.sln"
+ "dotnet.defaultSolution": "SpaceStation14.sln",
+ "json.schemas": [
+ {
+ "fileMatch": [ "**/meta.json" ],
+ "url": "https://raw.githubusercontent.com/Simple-Station/Einstein-Engines/master/.github/rsi-schema.json"
+ }
+ ]
}
diff --git a/BuildChecker/BuildChecker.csproj b/BuildChecker/BuildChecker.csproj
index 63d16fa970..d4f9a41254 100644
--- a/BuildChecker/BuildChecker.csproj
+++ b/BuildChecker/BuildChecker.csproj
@@ -14,8 +14,6 @@ https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
-->
- python3
- py -3
{C899FCA4-7037-4E49-ABC2-44DE72487110}
.NETFramework, Version=v4.7.2
false
@@ -39,7 +37,7 @@ https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild
bin\DebugOpt\
-
+
diff --git a/BuildChecker/git_helper.py b/BuildChecker/git_helper.py
deleted file mode 100644
index becd4506e8..0000000000
--- a/BuildChecker/git_helper.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/env python3
-# Installs git hooks, updates them, updates submodules, that kind of thing.
-
-import subprocess
-import sys
-import os
-import shutil
-from pathlib import Path
-from typing import List
-
-SOLUTION_PATH = Path("..") / "SpaceStation14.sln"
-# If this doesn't match the saved version we overwrite them all.
-CURRENT_HOOKS_VERSION = "2"
-QUIET = len(sys.argv) == 2 and sys.argv[1] == "--quiet"
-
-
-def run_command(command: List[str], capture: bool = False) -> subprocess.CompletedProcess:
- """
- Runs a command with pretty output.
- """
- text = ' '.join(command)
- if not QUIET:
- print("$ {}".format(text))
-
- sys.stdout.flush()
-
- completed = None
-
- if capture:
- completed = subprocess.run(command, cwd="..", stdout=subprocess.PIPE)
- else:
- completed = subprocess.run(command, cwd="..")
-
- if completed.returncode != 0:
- print("Error: command exited with code {}!".format(completed.returncode))
-
- return completed
-
-
-def update_submodules():
- """
- Updates all submodules.
- """
-
- if ('GITHUB_ACTIONS' in os.environ):
- return
-
- if os.path.isfile("DISABLE_SUBMODULE_AUTOUPDATE"):
- return
-
- if shutil.which("git") is None:
- raise FileNotFoundError("git not found in PATH")
-
- # If the status doesn't match, force VS to reload the solution.
- # status = run_command(["git", "submodule", "status"], capture=True)
- run_command(["git", "submodule", "update", "--init", "--recursive"])
- # status2 = run_command(["git", "submodule", "status"], capture=True)
-
- # Something changed.
- # if status.stdout != status2.stdout:
- # print("Git submodules changed. Reloading solution.")
- # reset_solution()
-
-
-def install_hooks():
- """
- Installs the necessary git hooks into .git/hooks.
- """
-
- # Read version file.
- if os.path.isfile("INSTALLED_HOOKS_VERSION"):
- with open("INSTALLED_HOOKS_VERSION", "r") as f:
- if f.read() == CURRENT_HOOKS_VERSION:
- if not QUIET:
- print("No hooks change detected.")
- return
-
- with open("INSTALLED_HOOKS_VERSION", "w") as f:
- f.write(CURRENT_HOOKS_VERSION)
-
- print("Hooks need updating.")
-
- hooks_target_dir = Path("..")/".git"/"hooks"
- hooks_source_dir = Path("hooks")
-
- # Clear entire tree since we need to kill deleted files too.
- for filename in os.listdir(str(hooks_target_dir)):
- os.remove(str(hooks_target_dir/filename))
-
- for filename in os.listdir(str(hooks_source_dir)):
- print("Copying hook {}".format(filename))
- shutil.copy2(str(hooks_source_dir/filename),
- str(hooks_target_dir/filename))
-
-
-def reset_solution():
- """
- Force VS to think the solution has been changed to prompt the user to reload it, thus fixing any load errors.
- """
-
- with SOLUTION_PATH.open("r") as f:
- content = f.read()
-
- with SOLUTION_PATH.open("w") as f:
- f.write(content)
-
-
-if __name__ == '__main__':
- install_hooks()
- update_submodules()
diff --git a/BuildChecker/hooks/post-checkout b/BuildChecker/hooks/post-checkout
deleted file mode 100755
index c5662445c2..0000000000
--- a/BuildChecker/hooks/post-checkout
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/bash
-
-gitroot=`git rev-parse --show-toplevel`
-
-cd "$gitroot/BuildChecker"
-
-if [[ `uname` == MINGW* || `uname` == CYGWIN* ]]; then
- # Windows
- py -3 git_helper.py --quiet
-else
- # Not Windows, so probably some other Unix thing.
- python3 git_helper.py --quiet
-fi
diff --git a/BuildChecker/hooks/post-merge b/BuildChecker/hooks/post-merge
deleted file mode 100755
index 85fe61d966..0000000000
--- a/BuildChecker/hooks/post-merge
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-
-# Just call post-checkout since it does the same thing.
-gitroot=`git rev-parse --show-toplevel`
-bash "$gitroot/.git/hooks/post-checkout"
diff --git a/Content.Client/Access/AccessOverlay.cs b/Content.Client/Access/AccessOverlay.cs
index 2be3d07e90..59c9441036 100644
--- a/Content.Client/Access/AccessOverlay.cs
+++ b/Content.Client/Access/AccessOverlay.cs
@@ -9,20 +9,20 @@ namespace Content.Client.Access;
public sealed class AccessOverlay : Overlay
{
+ private const string TextFontPath = "/Fonts/NotoSans/NotoSans-Regular.ttf";
+ private const int TextFontSize = 12;
+
private readonly IEntityManager _entityManager;
- private readonly EntityLookupSystem _lookup;
- private readonly SharedTransformSystem _xform;
+ private readonly SharedTransformSystem _transformSystem;
private readonly Font _font;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
- public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup, SharedTransformSystem xform)
+ public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, SharedTransformSystem transformSystem)
{
- _entityManager = entManager;
- _lookup = lookup;
- _xform = xform;
-
- _font = cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
+ _entityManager = entityManager;
+ _transformSystem = transformSystem;
+ _font = resourceCache.GetFont(TextFontPath, TextFontSize);
}
protected override void Draw(in OverlayDrawArgs args)
@@ -30,52 +30,65 @@ protected override void Draw(in OverlayDrawArgs args)
if (args.ViewportControl == null)
return;
- var readerQuery = _entityManager.GetEntityQuery();
- var xformQuery = _entityManager.GetEntityQuery();
-
- foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB,
- LookupFlags.Static | LookupFlags.Approximate))
+ var textBuffer = new StringBuilder();
+ var query = _entityManager.EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var accessReader, out var transform))
{
- if (!readerQuery.TryGetComponent(ent, out var reader) ||
- !xformQuery.TryGetComponent(ent, out var xform))
+ textBuffer.Clear();
+
+ var entityName = _entityManager.ToPrettyString(uid);
+ textBuffer.AppendLine(entityName.Prototype);
+ textBuffer.Append("UID: ");
+ textBuffer.Append(entityName.Uid.Id);
+ textBuffer.Append(", NUID: ");
+ textBuffer.Append(entityName.Nuid.Id);
+ textBuffer.AppendLine();
+
+ if (!accessReader.Enabled)
{
+ textBuffer.AppendLine("-Disabled");
continue;
}
- var text = new StringBuilder();
- var index = 0;
- var a = $"{_entityManager.ToPrettyString(ent)}";
- text.Append(a);
-
- foreach (var list in reader.AccessLists)
+ if (accessReader.AccessLists.Count > 0)
{
- a = $"Tag {index}";
- text.AppendLine(a);
-
- foreach (var entry in list)
+ var groupNumber = 0;
+ foreach (var accessList in accessReader.AccessLists)
{
- a = $"- {entry}";
- text.AppendLine(a);
+ groupNumber++;
+ foreach (var entry in accessList)
+ {
+ textBuffer.Append("+Set ");
+ textBuffer.Append(groupNumber);
+ textBuffer.Append(": ");
+ textBuffer.Append(entry.Id);
+ textBuffer.AppendLine();
+ }
}
-
- index++;
}
-
- string textStr;
-
- if (text.Length >= 2)
+ else
{
- textStr = text.ToString();
- textStr = textStr[..^2];
+ textBuffer.AppendLine("+Unrestricted");
}
- else
+
+ foreach (var key in accessReader.AccessKeys)
{
- textStr = "";
+ textBuffer.Append("+Key ");
+ textBuffer.Append(key.OriginStation);
+ textBuffer.Append(": ");
+ textBuffer.Append(key.Id);
+ textBuffer.AppendLine();
}
- var screenPos = args.ViewportControl.WorldToScreen(_xform.GetWorldPosition(xform));
+ foreach (var tag in accessReader.DenyTags)
+ {
+ textBuffer.Append("-Tag ");
+ textBuffer.AppendLine(tag.Id);
+ }
- args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold);
+ var accessInfoText = textBuffer.ToString();
+ var screenPos = args.ViewportControl.WorldToScreen(_transformSystem.GetWorldPosition(transform));
+ args.ScreenHandle.DrawString(_font, screenPos, accessInfoText, Color.Gold);
}
}
}
diff --git a/Content.Client/Access/Commands/ShowAccessReadersCommand.cs b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs
index 7c804dd969..cb6cb6cf6b 100644
--- a/Content.Client/Access/Commands/ShowAccessReadersCommand.cs
+++ b/Content.Client/Access/Commands/ShowAccessReadersCommand.cs
@@ -7,8 +7,16 @@ namespace Content.Client.Access.Commands;
public sealed class ShowAccessReadersCommand : IConsoleCommand
{
public string Command => "showaccessreaders";
- public string Description => "Shows all access readers in the viewport";
- public string Help => $"{Command}";
+
+ public string Description => "Toggles showing access reader permissions on the map";
+ public string Help => """
+ Overlay Info:
+ -Disabled | The access reader is disabled
+ +Unrestricted | The access reader has no restrictions
+ +Set [Index]: [Tag Name]| A tag in an access set (accessor needs all tags in the set to be allowed by the set)
+ +Key [StationUid]: [StationRecordKeyId] | A StationRecordKey that is allowed
+ -Tag [Tag Name] | A tag that is not allowed (takes priority over other allows)
+ """;
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var collection = IoCManager.Instance;
@@ -26,10 +34,9 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
var entManager = collection.Resolve();
var cache = collection.Resolve();
- var lookup = entManager.System();
var xform = entManager.System();
- overlay.AddOverlay(new AccessOverlay(entManager, cache, lookup, xform));
+ overlay.AddOverlay(new AccessOverlay(entManager, cache, xform));
shell.WriteLine($"Set access reader debug overlay to true");
}
}
diff --git a/Content.Client/Access/UI/AccessLevelControl.xaml b/Content.Client/Access/UI/AccessLevelControl.xaml
new file mode 100644
index 0000000000..56968d8983
--- /dev/null
+++ b/Content.Client/Access/UI/AccessLevelControl.xaml
@@ -0,0 +1,4 @@
+
+
diff --git a/Content.Client/Access/UI/AccessLevelControl.xaml.cs b/Content.Client/Access/UI/AccessLevelControl.xaml.cs
new file mode 100644
index 0000000000..34db80b7af
--- /dev/null
+++ b/Content.Client/Access/UI/AccessLevelControl.xaml.cs
@@ -0,0 +1,52 @@
+using System.Linq;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Content.Shared.Access;
+using Content.Shared.Access.Systems;
+
+namespace Content.Client.Access.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class AccessLevelControl : GridContainer
+{
+ public readonly Dictionary, Button> ButtonsList = new();
+
+ public AccessLevelControl()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void Populate(List> accessLevels, IPrototypeManager prototypeManager)
+ {
+ foreach (var access in accessLevels)
+ {
+ if (!prototypeManager.TryIndex(access, out var accessLevel))
+ {
+ Logger.Error($"Unable to find accesslevel for {access}");
+ continue;
+ }
+
+ var newButton = new Button
+ {
+ Text = accessLevel.GetAccessLevelName(),
+ ToggleMode = true,
+ };
+ AddChild(newButton);
+ ButtonsList.Add(accessLevel.ID, newButton);
+ }
+ }
+
+ public void UpdateState(
+ List> pressedList,
+ List>? enabledList = null)
+ {
+ foreach (var (accessName, button) in ButtonsList)
+ {
+ button.Pressed = pressedList.Contains(accessName);
+ button.Disabled = !(enabledList?.Contains(accessName) ?? true);
+ }
+ }
+}
diff --git a/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs b/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs
index 0c23542f79..c1b63dc4d0 100644
--- a/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs
+++ b/Content.Client/Access/UI/AccessOverriderBoundUserInterface.cs
@@ -64,7 +64,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.UpdateState(castState);
}
- public void SubmitData(List newAccessList)
+ public void SubmitData(List> newAccessList)
{
SendMessage(new WriteToTargetAccessReaderIdMessage(newAccessList));
}
diff --git a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs
index 2fd0057121..6025c3b551 100644
--- a/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs
+++ b/Content.Client/Access/UI/AccessOverriderWindow.xaml.cs
@@ -16,7 +16,6 @@ public sealed partial class AccessOverriderWindow : DefaultWindow
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- private readonly ISawmill _logMill = default!;
private readonly AccessOverriderBoundUserInterface _owner;
private readonly Dictionary _accessButtons = new();
@@ -25,7 +24,7 @@ public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototype
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- _logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
+ var logMill = _logManager.GetSawmill(SharedAccessOverriderSystem.Sawmill);
_owner = owner;
@@ -33,13 +32,13 @@ public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototype
{
if (!prototypeManager.TryIndex(access, out var accessLevel))
{
- _logMill.Error($"Unable to find accesslevel for {access}");
+ logMill.Error($"Unable to find accesslevel for {access}");
continue;
}
var newButton = new Button
{
- Text = GetAccessLevelName(accessLevel),
+ Text = accessLevel.GetAccessLevelName(),
ToggleMode = true,
};
@@ -49,14 +48,6 @@ public AccessOverriderWindow(AccessOverriderBoundUserInterface owner, IPrototype
}
}
- private static string GetAccessLevelName(AccessLevelPrototype prototype)
- {
- if (prototype.Name is { } name)
- return Loc.GetString(name);
-
- return prototype.ID;
- }
-
public void UpdateState(AccessOverriderBoundUserInterfaceState state)
{
PrivilegedIdLabel.Text = state.PrivilegedIdName;
@@ -105,7 +96,7 @@ private void SubmitData()
_owner.SubmitData(
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
- _accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
+ _accessButtons.Where(x => x.Value.Pressed).Select(x => new ProtoId(x.Key)).ToList());
}
}
}
diff --git a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs
index 898792aa03..5b7011c195 100644
--- a/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs
+++ b/Content.Client/Access/UI/IdCardConsoleBoundUserInterface.cs
@@ -1,5 +1,6 @@
using Content.Shared.Access;
using Content.Shared.Access.Components;
+using Content.Shared.Access;
using Content.Shared.Access.Systems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrewManifest;
@@ -28,7 +29,6 @@ protected override void Open()
if (EntMan.TryGetComponent(Owner, out var idCard))
{
accessLevels = idCard.AccessLevels;
- accessLevels.Sort();
}
else
{
@@ -65,7 +65,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window?.UpdateState(castState);
}
- public void SubmitData(string newFullName, string newJobTitle, List newAccessList, string newJobPrototype)
+ public void SubmitData(string newFullName, string newJobTitle, List> newAccessList, string newJobPrototype)
{
if (newFullName.Length > MaxFullNameLength)
newFullName = newFullName[..MaxFullNameLength];
diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml b/Content.Client/Access/UI/IdCardConsoleWindow.xaml
index c29adc8ebd..a2f5f3382b 100644
--- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml
+++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml
@@ -30,10 +30,6 @@
-
-
-
-
-
+
diff --git a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
index bf5984e809..298912e7d5 100644
--- a/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
+++ b/Content.Client/Access/UI/IdCardConsoleWindow.xaml.cs
@@ -20,7 +20,7 @@ public sealed partial class IdCardConsoleWindow : DefaultWindow
private readonly IdCardConsoleBoundUserInterface _owner;
- private readonly Dictionary _accessButtons = new();
+ private AccessLevelControl _accessButtons = new();
private readonly List _jobPrototypeIds = new();
private string? _lastFullName;
@@ -66,36 +66,18 @@ public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeMana
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
- foreach (var access in accessLevels)
- {
- if (!prototypeManager.TryIndex(access, out var accessLevel))
- {
- _logMill.Error($"Unable to find accesslevel for {access}");
- continue;
- }
+ _accessButtons.Populate(accessLevels, prototypeManager);
+ AccessLevelControlContainer.AddChild(_accessButtons);
- var newButton = new Button
- {
- Text = GetAccessLevelName(accessLevel),
- ToggleMode = true,
- };
- AccessLevelGrid.AddChild(newButton);
- _accessButtons.Add(accessLevel.ID, newButton);
- newButton.OnPressed += _ => SubmitData();
+ foreach (var (id, button) in _accessButtons.ButtonsList)
+ {
+ button.OnPressed += _ => SubmitData();
}
}
- private static string GetAccessLevelName(AccessLevelPrototype prototype)
- {
- if (prototype.Name is { } name)
- return Loc.GetString(name);
-
- return prototype.ID;
- }
-
private void ClearAllAccess()
{
- foreach (var button in _accessButtons.Values)
+ foreach (var button in _accessButtons.ButtonsList.Values)
{
if (button.Pressed)
{
@@ -119,7 +101,7 @@ private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args)
// this is a sussy way to do this
foreach (var access in job.Access)
{
- if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
+ if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
@@ -134,7 +116,7 @@ private void SelectJobPreset(OptionButton.ItemSelectedEventArgs args)
foreach (var access in groupPrototype.Tags)
{
- if (_accessButtons.TryGetValue(access, out var button) && !button.Disabled)
+ if (_accessButtons.ButtonsList.TryGetValue(access, out var button) && !button.Disabled)
{
button.Pressed = true;
}
@@ -184,15 +166,10 @@ public void UpdateState(IdCardConsoleBoundUserInterfaceState state)
JobPresetOptionButton.Disabled = !interfaceEnabled;
- foreach (var (accessName, button) in _accessButtons)
- {
- button.Disabled = !interfaceEnabled;
- if (interfaceEnabled)
- {
- button.Pressed = state.TargetIdAccessList?.Contains(accessName) ?? false;
- button.Disabled = (!state.AllowedModifyAccessList?.Contains(accessName)) ?? true;
- }
- }
+ _accessButtons.UpdateState(state.TargetIdAccessList?.ToList() ??
+ new List>(),
+ state.AllowedModifyAccessList?.ToList() ??
+ new List>());
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
if (jobIndex >= 0)
@@ -215,7 +192,7 @@ private void SubmitData()
FullNameLineEdit.Text,
JobTitleLineEdit.Text,
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
- _accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
+ _accessButtons.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty);
}
}
diff --git a/Content.Client/Administration/Systems/AdminSystem.cs b/Content.Client/Administration/Systems/AdminSystem.cs
index f7451d2304..db1b721343 100644
--- a/Content.Client/Administration/Systems/AdminSystem.cs
+++ b/Content.Client/Administration/Systems/AdminSystem.cs
@@ -10,16 +10,9 @@ public sealed partial class AdminSystem : EntitySystem
{
public event Action>? PlayerListChanged;
- private Dictionary? _playerList;
- public IReadOnlyList PlayerList
- {
- get
- {
- if (_playerList != null) return _playerList.Values.ToList();
-
- return new List();
- }
- }
+ public Dictionary PlayerInfos = new();
+ public IReadOnlyList PlayerList =>
+ PlayerInfos != null ? PlayerInfos.Values.ToList() : new List();
public override void Initialize()
{
@@ -40,15 +33,15 @@ private void OnPlayerInfoChanged(PlayerInfoChangedEvent ev)
{
if(ev.PlayerInfo == null) return;
- if (_playerList == null) _playerList = new();
+ if (PlayerInfos == null) PlayerInfos = new();
- _playerList[ev.PlayerInfo.SessionId] = ev.PlayerInfo;
- PlayerListChanged?.Invoke(_playerList.Values.ToList());
+ PlayerInfos[ev.PlayerInfo.SessionId] = ev.PlayerInfo;
+ PlayerListChanged?.Invoke(PlayerInfos.Values.ToList());
}
private void OnPlayerListChanged(FullPlayerListEvent msg)
{
- _playerList = msg.PlayersInfo.ToDictionary(x => x.SessionId, x => x);
+ PlayerInfos = msg.PlayersInfo.ToDictionary(x => x.SessionId, x => x);
PlayerListChanged?.Invoke(msg.PlayersInfo);
}
}
diff --git a/Content.Client/Administration/Systems/BwoinkSystem.cs b/Content.Client/Administration/Systems/BwoinkSystem.cs
index 5166dc8416..a3b295d6b6 100644
--- a/Content.Client/Administration/Systems/BwoinkSystem.cs
+++ b/Content.Client/Administration/Systems/BwoinkSystem.cs
@@ -1,7 +1,10 @@
#nullable enable
+using Content.Client.UserInterface.Systems.Bwoink;
using Content.Shared.Administration;
using JetBrains.Annotations;
+using Robust.Client.Audio;
using Robust.Shared.Network;
+using Robust.Shared.Player;
using Robust.Shared.Timing;
namespace Content.Client.Administration.Systems
@@ -10,6 +13,8 @@ namespace Content.Client.Administration.Systems
public sealed class BwoinkSystem : SharedBwoinkSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly AudioSystem _audio = default!;
+ [Dependency] private readonly AdminSystem _adminSystem = default!;
public event EventHandler? OnBwoinkTextMessageRecieved;
private (TimeSpan Timestamp, bool Typing) _lastTypingUpdateSent;
@@ -21,6 +26,10 @@ protected override void OnBwoinkTextMessage(BwoinkTextMessage message, EntitySes
public void Send(NetUserId channelId, string text, bool playSound)
{
+ var info = _adminSystem.PlayerInfos.GetValueOrDefault(channelId)?.Connected ?? true;
+ _audio.PlayGlobal(info ? AHelpUIController.AHelpSendSound : AHelpUIController.AHelpErrorSound,
+ Filter.Local(), false);
+
// 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));
@@ -31,9 +40,7 @@ public void SendInputTextUpdated(NetUserId channel, bool typing)
{
if (_lastTypingUpdateSent.Typing == typing &&
_lastTypingUpdateSent.Timestamp + TimeSpan.FromSeconds(1) > _timing.RealTime)
- {
return;
- }
_lastTypingUpdateSent = (_timing.RealTime, typing);
RaiseNetworkEvent(new BwoinkClientTypingUpdated(channel, typing));
diff --git a/Content.Client/Alerts/ClientAlertsSystem.cs b/Content.Client/Alerts/ClientAlertsSystem.cs
index 237f24e3ea..223bf7876a 100644
--- a/Content.Client/Alerts/ClientAlertsSystem.cs
+++ b/Content.Client/Alerts/ClientAlertsSystem.cs
@@ -49,24 +49,23 @@ public IReadOnlyDictionary? ActiveAlerts
protected override void AfterShowAlert(Entity alerts)
{
- if (_playerManager.LocalEntity != alerts.Owner)
- return;
-
- SyncAlerts?.Invoke(this, alerts.Comp.Alerts);
+ UpdateHud(alerts);
}
- protected override void AfterClearAlert(Entity alertsComponent)
+ protected override void AfterClearAlert(Entity alerts)
{
- if (_playerManager.LocalEntity != alertsComponent.Owner)
- return;
+ UpdateHud(alerts);
+ }
- SyncAlerts?.Invoke(this, alertsComponent.Comp.Alerts);
+ private void ClientAlertsHandleState(Entity alerts, ref AfterAutoHandleStateEvent args)
+ {
+ UpdateHud(alerts);
}
- private void ClientAlertsHandleState(EntityUid uid, AlertsComponent component, ref AfterAutoHandleStateEvent args)
+ private void UpdateHud(Entity entity)
{
- if (_playerManager.LocalEntity == uid)
- SyncAlerts?.Invoke(this, component.Alerts);
+ if (_playerManager.LocalEntity == entity.Owner)
+ SyncAlerts?.Invoke(this, entity.Comp.Alerts);
}
private void OnPlayerAttached(EntityUid uid, AlertsComponent component, LocalPlayerAttachedEvent args)
diff --git a/Content.Client/Alerts/UpdateAlertSpriteEvent.cs b/Content.Client/Alerts/UpdateAlertSpriteEvent.cs
new file mode 100644
index 0000000000..4f182c458c
--- /dev/null
+++ b/Content.Client/Alerts/UpdateAlertSpriteEvent.cs
@@ -0,0 +1,21 @@
+using Content.Shared.Alert;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Alerts;
+
+///
+/// Event raised on an entity with alerts in order to allow it to update visuals for the alert sprite entity.
+///
+[ByRefEvent]
+public record struct UpdateAlertSpriteEvent
+{
+ public Entity SpriteViewEnt;
+
+ public AlertPrototype Alert;
+
+ public UpdateAlertSpriteEvent(Entity spriteViewEnt, AlertPrototype alert)
+ {
+ SpriteViewEnt = spriteViewEnt;
+ Alert = alert;
+ }
+}
diff --git a/Content.Client/Announcements/Systems/AnnouncerSystem.cs b/Content.Client/Announcements/Systems/AnnouncerSystem.cs
new file mode 100644
index 0000000000..de76396f70
--- /dev/null
+++ b/Content.Client/Announcements/Systems/AnnouncerSystem.cs
@@ -0,0 +1,69 @@
+using Content.Client.Audio;
+using Content.Shared.Announcements.Events;
+using Content.Shared.Announcements.Systems;
+using Content.Shared.CCVar;
+using Robust.Client.Audio;
+using Robust.Client.Player;
+using Robust.Client.ResourceManagement;
+using Robust.Shared.Audio.Sources;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Configuration;
+
+namespace Content.Client.Announcements.Systems;
+
+public sealed class AnnouncerSystem : SharedAnnouncerSystem
+{
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IConfigurationManager _config = default!;
+ [Dependency] private readonly IResourceCache _cache = default!;
+ [Dependency] private readonly IAudioManager _audioManager = default!;
+
+ private IAudioSource? AnnouncerSource { get; set; }
+ private float AnnouncerVolume { get; set; }
+
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ AnnouncerVolume = _config.GetCVar(CCVars.AnnouncerVolume) * 100f / ContentAudioSystem.AnnouncerMultiplier;
+
+ SubscribeNetworkEvent(OnAnnouncementReceived);
+ _config.OnValueChanged(CCVars.AnnouncerVolume, OnAnnouncerVolumeChanged);
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+
+ _config.UnsubValueChanged(CCVars.AnnouncerVolume, OnAnnouncerVolumeChanged);
+ }
+
+
+ private void OnAnnouncerVolumeChanged(float value)
+ {
+ AnnouncerVolume = value;
+
+ if (AnnouncerSource != null)
+ AnnouncerSource.Gain = AnnouncerVolume;
+ }
+
+ private void OnAnnouncementReceived(AnnouncementSendEvent ev)
+ {
+ if (!ev.Recipients.Contains(_player.LocalSession!.UserId)
+ || !_cache.TryGetResource(GetAnnouncementPath(ev.AnnouncementId, ev.AnnouncerId),
+ out var resource))
+ return;
+
+ var source = _audioManager.CreateAudioSource(resource);
+ if (source != null)
+ {
+ source.Gain = AnnouncerVolume * SharedAudioSystem.VolumeToGain(ev.AudioParams.Volume);
+ source.Global = true;
+ }
+
+ AnnouncerSource?.Dispose();
+ AnnouncerSource = source;
+ AnnouncerSource?.StartPlaying();
+ }
+}
diff --git a/Content.Client/Anomaly/Ui/AnomalyScannerMenu.xaml b/Content.Client/Anomaly/Ui/AnomalyScannerMenu.xaml
index ac4adf7e0e..36a750d009 100644
--- a/Content.Client/Anomaly/Ui/AnomalyScannerMenu.xaml
+++ b/Content.Client/Anomaly/Ui/AnomalyScannerMenu.xaml
@@ -1,9 +1,9 @@
-
+ MinSize="350 400"
+ SetSize="350 400">
diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs
index 9d30cabb1e..0206017bae 100644
--- a/Content.Client/Audio/AmbientSoundSystem.cs
+++ b/Content.Client/Audio/AmbientSoundSystem.cs
@@ -50,7 +50,6 @@ protected override void QueueUpdate(EntityUid uid, AmbientSoundComponent ambienc
private static AudioParams _params = AudioParams.Default
.WithVariation(0.01f)
.WithLoop(true)
- .WithAttenuation(Attenuation.LinearDistance)
.WithMaxDistance(7f);
///
diff --git a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
index 0fdcc7a86d..92c5b7a419 100644
--- a/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
+++ b/Content.Client/Audio/ContentAudioSystem.LobbyMusic.cs
@@ -23,8 +23,8 @@ public sealed partial class ContentAudioSystem
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
- private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, "Master", 0, 0, 0, false, 0f);
- private readonly AudioParams _roundEndSoundEffectParams = new(-5f, 1, "Master", 0, 0, 0, false, 0f);
+ private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, 0, 0, 0, false, 0f);
+ private readonly AudioParams _roundEndSoundEffectParams = new(-5f, 1, 0, 0, 0, false, 0f);
///
/// EntityUid of lobby restart sound component.
diff --git a/Content.Client/Audio/ContentAudioSystem.cs b/Content.Client/Audio/ContentAudioSystem.cs
index f62b34b492..a79ff74e79 100644
--- a/Content.Client/Audio/ContentAudioSystem.cs
+++ b/Content.Client/Audio/ContentAudioSystem.cs
@@ -29,7 +29,8 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
public const float AmbientMusicMultiplier = 3f;
public const float LobbyMultiplier = 3f;
public const float InterfaceMultiplier = 2f;
-
+ public const float AnnouncerMultiplier = 3f;
+
public override void Initialize()
{
base.Initialize();
diff --git a/Content.Client/Camera/CameraRecoilSystem.cs b/Content.Client/Camera/CameraRecoilSystem.cs
index 3e04cd5bf1..2a0b6258cc 100644
--- a/Content.Client/Camera/CameraRecoilSystem.cs
+++ b/Content.Client/Camera/CameraRecoilSystem.cs
@@ -1,6 +1,7 @@
using System.Numerics;
using Content.Shared.Camera;
using Content.Shared.CCVar;
+using Content.Shared.Contests;
using Robust.Shared.Configuration;
namespace Content.Client.Camera;
@@ -8,6 +9,7 @@ namespace Content.Client.Camera;
public sealed class CameraRecoilSystem : SharedCameraRecoilSystem
{
[Dependency] private readonly IConfigurationManager _configManager = default!;
+ [Dependency] private readonly ContestsSystem _contests = default!;
private float _intensity;
@@ -37,15 +39,15 @@ public override void KickCamera(EntityUid uid, Vector2 recoil, CameraRecoilCompo
if (!Resolve(uid, ref component, false))
return;
- recoil *= _intensity;
+ var massRatio = _contests.MassContest(uid);
+ var maxRecoil = KickMagnitudeMax / massRatio;
+ recoil *= _intensity / massRatio;
- // Use really bad math to "dampen" kicks when we're already kicked.
var existing = component.CurrentKick.Length();
- var dampen = existing / KickMagnitudeMax;
- component.CurrentKick += recoil * (1 - dampen);
+ component.CurrentKick += recoil * (1 - existing);
- if (component.CurrentKick.Length() > KickMagnitudeMax)
- component.CurrentKick = component.CurrentKick.Normalized() * KickMagnitudeMax;
+ if (component.CurrentKick.Length() > maxRecoil)
+ component.CurrentKick = component.CurrentKick.Normalized() * maxRecoil;
component.LastKickTime = 0;
}
diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
index 482acb3c87..d3365702bc 100644
--- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
@@ -27,6 +27,11 @@ protected override void Open()
SendMessage(new BountyPrintLabelMessage(id));
};
+ _menu.OnSkipButtonPressed += id =>
+ {
+ SendMessage(new BountySkipMessage(id));
+ };
+
_menu.OpenCentered();
}
@@ -37,7 +42,7 @@ protected override void UpdateState(BoundUserInterfaceState message)
if (message is not CargoBountyConsoleState state)
return;
- _menu?.UpdateEntries(state.Bounties);
+ _menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
}
protected override void Dispose(bool disposing)
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml b/Content.Client/Cargo/UI/BountyEntry.xaml
index 60446327b3..7c61323bd5 100644
--- a/Content.Client/Cargo/UI/BountyEntry.xaml
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml
@@ -13,7 +13,18 @@
-
+
+
+
+
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml.cs b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
index 1fc8a4986a..027d7b3e80 100644
--- a/Content.Client/Cargo/UI/BountyEntry.xaml.cs
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
@@ -1,11 +1,13 @@
using Content.Client.Message;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Prototypes;
+using Content.Shared.Random;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
+using Serilog;
namespace Content.Client.Cargo.UI;
@@ -14,15 +16,19 @@ public sealed partial class BountyEntry : BoxContainer
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
- public Action? OnButtonPressed;
+ public Action? OnLabelButtonPressed;
+ public Action? OnSkipButtonPressed;
public TimeSpan EndTime;
+ public TimeSpan UntilNextSkip;
- public BountyEntry(CargoBountyData bounty)
+ public BountyEntry(CargoBountyData bounty, TimeSpan untilNextSkip)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
+ UntilNextSkip = untilNextSkip;
+
if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
return;
@@ -38,6 +44,27 @@ public BountyEntry(CargoBountyData bounty)
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
- PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
+ PrintButton.OnPressed += _ => OnLabelButtonPressed?.Invoke();
+ SkipButton.OnPressed += _ => OnSkipButtonPressed?.Invoke();
+ }
+
+ private void UpdateSkipButton(float deltaSeconds)
+ {
+ UntilNextSkip -= TimeSpan.FromSeconds(deltaSeconds);
+ if (UntilNextSkip > TimeSpan.Zero)
+ {
+ SkipButton.Label.Text = UntilNextSkip.ToString("mm\\:ss");
+ SkipButton.Disabled = true;
+ return;
+ }
+
+ SkipButton.Label.Text = Loc.GetString("bounty-console-skip-button-text");
+ SkipButton.Disabled = false;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+ UpdateSkipButton(args.DeltaSeconds);
}
}
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
index 2f1756dd18..3767b45e4b 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
@@ -10,19 +10,21 @@ namespace Content.Client.Cargo.UI;
public sealed partial class CargoBountyMenu : FancyWindow
{
public Action? OnLabelButtonPressed;
+ public Action? OnSkipButtonPressed;
public CargoBountyMenu()
{
RobustXamlLoader.Load(this);
}
- public void UpdateEntries(List bounties)
+ public void UpdateEntries(List bounties, TimeSpan untilNextSkip)
{
BountyEntriesContainer.Children.Clear();
foreach (var b in bounties)
{
- var entry = new BountyEntry(b);
- entry.OnButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
+ var entry = new BountyEntry(b, untilNextSkip);
+ entry.OnLabelButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
+ entry.OnSkipButtonPressed += () => OnSkipButtonPressed?.Invoke(b.Id);
BountyEntriesContainer.AddChild(entry);
}
diff --git a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs
similarity index 88%
rename from Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs
rename to Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs
index 0b5fc7ad38..9ef968e43b 100644
--- a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs
+++ b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUi.cs
@@ -1,10 +1,9 @@
-using Robust.Client.GameObjects;
-using Robust.Client.UserInterface;
+using Robust.Client.UserInterface;
using Content.Client.UserInterface.Fragments;
using Content.Shared.CartridgeLoader.Cartridges;
using Content.Shared.CartridgeLoader;
-namespace Content.Client.Nyanotrasen.CartridgeLoader.Cartridges;
+namespace Content.Client.CartridgeLoader.Cartridges;
public sealed partial class GlimmerMonitorUi : UIFragment
{
diff --git a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml
similarity index 93%
rename from Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml
rename to Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml
index 119a1831e6..e09a422ddf 100644
--- a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml
+++ b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml
@@ -1,4 +1,4 @@
-
diff --git a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs
similarity index 96%
rename from Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs
rename to Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs
index 43d9202aa4..3325c0d379 100644
--- a/Content.Client/Nyanotrasen/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs
+++ b/Content.Client/CartridgeLoader/Cartridges/GlimmerMonitorUiFragment.xaml.cs
@@ -1,12 +1,12 @@
using System.Linq;
using System.Numerics;
-using Content.Client.Nyanotrasen.UserInterface;
+using Content.Client.UserInterface;
using Robust.Client.AutoGenerated;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-namespace Content.Client.Nyanotrasen.CartridgeLoader.Cartridges;
+namespace Content.Client.CartridgeLoader.Cartridges;
[GenerateTypedNameReferences]
public sealed partial class GlimmerMonitorUiFragment : BoxContainer
diff --git a/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs b/Content.Client/Chat/PsionicChatUpdateSystem.cs
similarity index 96%
rename from Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs
rename to Content.Client/Chat/PsionicChatUpdateSystem.cs
index 84602052fe..066501acb7 100644
--- a/Content.Client/Nyanotrasen/Chat/PsionicChatUpdateSystem.cs
+++ b/Content.Client/Chat/PsionicChatUpdateSystem.cs
@@ -2,7 +2,7 @@
using Content.Client.Chat.Managers;
using Robust.Client.Player;
-namespace Content.Client.Nyanotrasen.Chat
+namespace Content.Client.Chat
{
public sealed class PsionicChatUpdateSystem : EntitySystem
{
diff --git a/Content.Client/Chemistry/Components/HyposprayComponent.cs b/Content.Client/Chemistry/Components/HyposprayComponent.cs
deleted file mode 100644
index 705b79ad84..0000000000
--- a/Content.Client/Chemistry/Components/HyposprayComponent.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using Content.Shared.Chemistry.Components;
-using Content.Shared.FixedPoint;
-
-namespace Content.Client.Chemistry.Components
-{
- [RegisterComponent]
- public sealed partial class HyposprayComponent : SharedHyposprayComponent
- {
- [ViewVariables]
- public FixedPoint2 CurrentVolume;
- [ViewVariables]
- public FixedPoint2 TotalVolume;
- [ViewVariables(VVAccess.ReadWrite)]
- public bool UiUpdateNeeded;
- }
-}
diff --git a/Content.Client/Chemistry/EntitySystems/HypospraySystem.cs b/Content.Client/Chemistry/EntitySystems/HypospraySystem.cs
new file mode 100644
index 0000000000..ee7aa3aafe
--- /dev/null
+++ b/Content.Client/Chemistry/EntitySystems/HypospraySystem.cs
@@ -0,0 +1,15 @@
+using Content.Client.Chemistry.UI;
+using Content.Client.Items;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
+
+namespace Content.Client.Chemistry.EntitySystems;
+
+public sealed class HypospraySystem : SharedHypospraySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ Subs.ItemStatus(ent => new HyposprayStatusControl(ent, _solutionContainers));
+ }
+}
diff --git a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs b/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
index 12eb7f3d14..0131a283c8 100644
--- a/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
+++ b/Content.Client/Chemistry/EntitySystems/InjectorSystem.cs
@@ -1,4 +1,3 @@
-using Content.Client.Chemistry.Components;
using Content.Client.Chemistry.UI;
using Content.Client.Items;
using Content.Shared.Chemistry.Components;
@@ -13,17 +12,5 @@ public override void Initialize()
{
base.Initialize();
Subs.ItemStatus(ent => new InjectorStatusControl(ent, SolutionContainers));
- SubscribeLocalEvent(OnHandleHyposprayState);
- Subs.ItemStatus(ent => new HyposprayStatusControl(ent));
- }
-
- private void OnHandleHyposprayState(EntityUid uid, HyposprayComponent component, ref ComponentHandleState args)
- {
- if (args.Current is not HyposprayComponentState cState)
- return;
-
- component.CurrentVolume = cState.CurVolume;
- component.TotalVolume = cState.MaxVolume;
- component.UiUpdateNeeded = true;
}
}
diff --git a/Content.Client/Chemistry/UI/HyposprayStatusControl.cs b/Content.Client/Chemistry/UI/HyposprayStatusControl.cs
index bd85cd546c..4a4d90dc4d 100644
--- a/Content.Client/Chemistry/UI/HyposprayStatusControl.cs
+++ b/Content.Client/Chemistry/UI/HyposprayStatusControl.cs
@@ -1,6 +1,8 @@
-using Content.Client.Chemistry.Components;
using Content.Client.Message;
using Content.Client.Stylesheets;
+using Content.Shared.Chemistry.Components;
+using Content.Shared.Chemistry.EntitySystems;
+using Content.Shared.FixedPoint;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
@@ -9,34 +11,48 @@ namespace Content.Client.Chemistry.UI;
public sealed class HyposprayStatusControl : Control
{
- private readonly HyposprayComponent _parent;
+ private readonly Entity _parent;
private readonly RichTextLabel _label;
+ private readonly SharedSolutionContainerSystem _solutionContainers;
- public HyposprayStatusControl(HyposprayComponent parent)
+ private FixedPoint2 PrevVolume;
+ private FixedPoint2 PrevMaxVolume;
+ private bool PrevOnlyAffectsMobs;
+
+ public HyposprayStatusControl(Entity parent, SharedSolutionContainerSystem solutionContainers)
{
_parent = parent;
- _label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}};
+ _solutionContainers = solutionContainers;
+ _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
-
- Update();
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
- if (!_parent.UiUpdateNeeded)
+
+ if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution))
return;
- Update();
- }
- public void Update()
- {
+ // only updates the UI if any of the details are different than they previously were
+ if (PrevVolume == solution.Volume
+ && PrevMaxVolume == solution.MaxVolume
+ && PrevOnlyAffectsMobs == _parent.Comp.OnlyAffectsMobs)
+ return;
+
+ PrevVolume = solution.Volume;
+ PrevMaxVolume = solution.MaxVolume;
+ PrevOnlyAffectsMobs = _parent.Comp.OnlyAffectsMobs;
- _parent.UiUpdateNeeded = false;
+ var modeStringLocalized = Loc.GetString(_parent.Comp.OnlyAffectsMobs switch
+ {
+ false => "hypospray-all-mode-text",
+ true => "hypospray-mobs-only-mode-text",
+ });
- _label.SetMarkup(Loc.GetString(
- "hypospray-volume-text",
- ("currentVolume", _parent.CurrentVolume),
- ("totalVolume", _parent.TotalVolume)));
+ _label.SetMarkup(Loc.GetString("hypospray-volume-label",
+ ("currentVolume", solution.Volume),
+ ("totalVolume", solution.MaxVolume),
+ ("modeString", modeStringLocalized)));
}
}
diff --git a/Content.Client/Chemistry/UI/InjectorStatusControl.cs b/Content.Client/Chemistry/UI/InjectorStatusControl.cs
index 9cb699330c..ba1f97cd1e 100644
--- a/Content.Client/Chemistry/UI/InjectorStatusControl.cs
+++ b/Content.Client/Chemistry/UI/InjectorStatusControl.cs
@@ -17,6 +17,7 @@ public sealed class InjectorStatusControl : Control
private FixedPoint2 PrevVolume;
private FixedPoint2 PrevMaxVolume;
+ private FixedPoint2 PrevTransferAmount;
private InjectorToggleMode PrevToggleState;
public InjectorStatusControl(Entity parent, SharedSolutionContainerSystem solutionContainers)
@@ -37,11 +38,13 @@ protected override void FrameUpdate(FrameEventArgs args)
// only updates the UI if any of the details are different than they previously were
if (PrevVolume == solution.Volume
&& PrevMaxVolume == solution.MaxVolume
+ && PrevTransferAmount == _parent.Comp.TransferAmount
&& PrevToggleState == _parent.Comp.ToggleState)
return;
PrevVolume = solution.Volume;
PrevMaxVolume = solution.MaxVolume;
+ PrevTransferAmount = _parent.Comp.TransferAmount;
PrevToggleState = _parent.Comp.ToggleState;
// Update current volume and injector state
diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs
index fbe9d5ec5b..7e78ac7d70 100644
--- a/Content.Client/Clothing/ClientClothingSystem.cs
+++ b/Content.Client/Clothing/ClientClothingSystem.cs
@@ -133,7 +133,7 @@ private bool TryGetDefaultVisuals(EntityUid uid, ClothingComponent clothing, str
else if (TryComp(uid, out SpriteComponent? sprite))
rsi = sprite.BaseRSI;
- if (rsi == null || rsi.Path == null)
+ if (rsi == null)
return false;
var correctedSlot = slot;
diff --git a/Content.Client/Clothing/Systems/WaddleClothingSystem.cs b/Content.Client/Clothing/Systems/WaddleClothingSystem.cs
new file mode 100644
index 0000000000..b8ac3c207b
--- /dev/null
+++ b/Content.Client/Clothing/Systems/WaddleClothingSystem.cs
@@ -0,0 +1,31 @@
+using Content.Shared.Clothing.Components;
+using Content.Shared.Movement.Components;
+using Content.Shared.Inventory.Events;
+
+namespace Content.Client.Clothing.Systems;
+
+public sealed class WaddleClothingSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnGotEquipped);
+ SubscribeLocalEvent(OnGotUnequipped);
+ }
+
+ private void OnGotEquipped(EntityUid entity, WaddleWhenWornComponent comp, GotEquippedEvent args)
+ {
+ var waddleAnimComp = EnsureComp(args.Equipee);
+
+ waddleAnimComp.AnimationLength = comp.AnimationLength;
+ waddleAnimComp.HopIntensity = comp.HopIntensity;
+ waddleAnimComp.RunAnimationLengthMultiplier = comp.RunAnimationLengthMultiplier;
+ waddleAnimComp.TumbleIntensity = comp.TumbleIntensity;
+ }
+
+ private void OnGotUnequipped(EntityUid entity, WaddleWhenWornComponent comp, GotUnequippedEvent args)
+ {
+ RemComp(args.Equipee);
+ }
+}
diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj
index 956f2fd035..c1958acba7 100644
--- a/Content.Client/Content.Client.csproj
+++ b/Content.Client/Content.Client.csproj
@@ -26,6 +26,11 @@
+
+
+ LobbyCharacterPanel.xaml
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/CrimeAssistUi.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/CrimeAssistUi.cs
index ea5aa3cf25..2dbe923b2a 100644
--- a/Content.Client/DeltaV/CartridgeLoader/Cartridges/CrimeAssistUi.cs
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/CrimeAssistUi.cs
@@ -18,15 +18,6 @@ public override Control GetUIFragmentRoot()
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
{
_fragment = new CrimeAssistUiFragment();
-
- _fragment.OnSync += _ => SendSyncMessage(userInterface);
- }
-
- private void SendSyncMessage(BoundUserInterface userInterface)
- {
- var syncMessage = new CrimeAssistSyncMessageEvent();
- var message = new CartridgeUiMessage(syncMessage);
- userInterface.SendMessage(message);
}
public override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/CrimeAssistUiFragment.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/CrimeAssistUiFragment.xaml.cs
index e3163975d1..fb085a8a79 100644
--- a/Content.Client/DeltaV/CartridgeLoader/Cartridges/CrimeAssistUiFragment.xaml.cs
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/CrimeAssistUiFragment.xaml.cs
@@ -1,7 +1,6 @@
using Content.Client.Message;
using Content.Shared.DeltaV.CartridgeLoader.Cartridges;
using Robust.Client.AutoGenerated;
-using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
@@ -13,9 +12,7 @@ namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
public sealed partial class CrimeAssistUiFragment : BoxContainer
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IResourceCache _resourceCache = default!;
- public event Action? OnSync;
private CrimeAssistPage _currentPage;
private List? _pages;
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml
new file mode 100644
index 0000000000..2de8a37ff7
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml.cs
new file mode 100644
index 0000000000..e8dd4eea44
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchEntryControl.xaml.cs
@@ -0,0 +1,21 @@
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class SecWatchEntryControl : BoxContainer
+{
+ public SecWatchEntryControl(SecWatchEntry entry)
+ {
+ RobustXamlLoader.Load(this);
+
+ Status.Text = Loc.GetString($"criminal-records-status-{entry.Status.ToString().ToLower()}");
+ Title.Text = Loc.GetString("sec-watch-entry", ("name", entry.Name), ("job", entry.Job));
+
+ Reason.Text = entry.Reason ?? Loc.GetString("sec-watch-no-reason");
+ }
+}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchUi.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchUi.cs
new file mode 100644
index 0000000000..da5ff825b9
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchUi.cs
@@ -0,0 +1,27 @@
+using Content.Client.UserInterface.Fragments;
+using Content.Shared.CartridgeLoader;
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+public sealed partial class SecWatchUi : UIFragment
+{
+ private SecWatchUiFragment? _fragment;
+
+ public override Control GetUIFragmentRoot()
+ {
+ return _fragment!;
+ }
+
+ public override void Setup(BoundUserInterface ui, EntityUid? owner)
+ {
+ _fragment = new SecWatchUiFragment();
+ }
+
+ public override void UpdateState(BoundUserInterfaceState state)
+ {
+ if (state is SecWatchUiState cast)
+ _fragment?.UpdateState(cast);
+ }
+}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml
new file mode 100644
index 0000000000..7fb2c42deb
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml.cs
new file mode 100644
index 0000000000..ad15284052
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/SecWatchUiFragment.xaml.cs
@@ -0,0 +1,25 @@
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class SecWatchUiFragment : BoxContainer
+{
+ public SecWatchUiFragment()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void UpdateState(SecWatchUiState state)
+ {
+ NoEntries.Visible = state.Entries.Count == 0;
+ Entries.RemoveAllChildren();
+ foreach (var entry in state.Entries)
+ {
+ Entries.AddChild(new SecWatchEntryControl(entry));
+ }
+ }
+}
diff --git a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
index 8c40c78421..b9e4a38660 100644
--- a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
+++ b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
@@ -96,24 +96,22 @@ private void OnAppearanceChange(EntityUid uid, SharedDisposalUnitComponent unit,
private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, SpriteComponent sprite, AppearanceComponent appearance)
{
if (!_appearanceSystem.TryGetData(uid, Visuals.VisualState, out var state, appearance))
- {
return;
- }
sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == VisualState.UnAnchored);
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == VisualState.Anchored);
- sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseFlush, state is VisualState.Flushing or VisualState.Charging);
+ sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFlush, state is VisualState.OverlayFlushing or VisualState.OverlayCharging);
var chargingState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseCharging, out var chargingLayer)
? sprite.LayerGetState(chargingLayer)
: new RSI.StateId(DefaultChargeState);
// This is a transient state so not too worried about replaying in range.
- if (state == VisualState.Flushing)
+ if (state == VisualState.OverlayFlushing)
{
if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
{
- var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseFlush, out var flushLayer)
+ var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.OverlayFlush, out var flushLayer)
? sprite.LayerGetState(flushLayer)
: new RSI.StateId(DefaultFlushState);
@@ -125,7 +123,7 @@ private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, Sprite
{
new AnimationTrackSpriteFlick
{
- LayerKey = DisposalUnitVisualLayers.BaseFlush,
+ LayerKey = DisposalUnitVisualLayers.OverlayFlush,
KeyFrames =
{
// Play the flush animation
@@ -154,26 +152,18 @@ private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, Sprite
_animationSystem.Play(uid, anim, AnimationKey);
}
}
- else if (state == VisualState.Charging)
- {
- sprite.LayerSetState(DisposalUnitVisualLayers.BaseFlush, chargingState);
- }
+ else if (state == VisualState.OverlayCharging)
+ sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, new RSI.StateId("disposal-charging"));
else
- {
_animationSystem.Stop(uid, AnimationKey);
- }
if (!_appearanceSystem.TryGetData(uid, Visuals.Handle, out var handleState, appearance))
- {
handleState = HandleState.Normal;
- }
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal);
if (!_appearanceSystem.TryGetData(uid, Visuals.Light, out var lightState, appearance))
- {
lightState = LightStates.Off;
- }
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayCharging,
(lightState & LightStates.Charging) != 0);
@@ -189,7 +179,7 @@ public enum DisposalUnitVisualLayers : byte
Unanchored,
Base,
BaseCharging,
- BaseFlush,
+ OverlayFlush,
OverlayCharging,
OverlayReady,
OverlayFull,
diff --git a/Content.Client/Doors/DoorSystem.cs b/Content.Client/Doors/DoorSystem.cs
index 473ae97059..bc52730b0e 100644
--- a/Content.Client/Doors/DoorSystem.cs
+++ b/Content.Client/Doors/DoorSystem.cs
@@ -4,14 +4,12 @@
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
-using Robust.Shared.Timing;
namespace Content.Client.Doors;
public sealed class DoorSystem : SharedDoorSystem
{
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
public override void Initialize()
diff --git a/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs b/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs
new file mode 100644
index 0000000000..cd7ea717ce
--- /dev/null
+++ b/Content.Client/Doors/Electronics/DoorElectronicsBoundUserInterface.cs
@@ -0,0 +1,59 @@
+using Content.Shared.Access;
+using Content.Shared.Doors.Electronics;
+using Robust.Client.GameObjects;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Doors.Electronics;
+
+public sealed class DoorElectronicsBoundUserInterface : BoundUserInterface
+{
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ private DoorElectronicsConfigurationMenu? _window;
+
+ public DoorElectronicsBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ List> accessLevels = new();
+
+ foreach (var accessLevel in _prototypeManager.EnumeratePrototypes())
+ {
+ if (accessLevel.Name != null)
+ {
+ accessLevels.Add(accessLevel.ID);
+ }
+ }
+
+ accessLevels.Sort();
+
+ _window = new DoorElectronicsConfigurationMenu(this, accessLevels, _prototypeManager);
+ _window.OnClose += Close;
+ _window.OpenCentered();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ var castState = (DoorElectronicsConfigurationState) state;
+
+ _window?.UpdateState(castState);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing) return;
+
+ _window?.Dispose();
+ }
+
+ public void UpdateConfiguration(List> newAccessList)
+ {
+ SendMessage(new DoorElectronicsUpdateConfigurationMessage(newAccessList));
+ }
+}
diff --git a/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml
new file mode 100644
index 0000000000..4cd59f38b2
--- /dev/null
+++ b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs
new file mode 100644
index 0000000000..c01f13a462
--- /dev/null
+++ b/Content.Client/Doors/Electronics/DoorElectronicsConfigurationMenu.xaml.cs
@@ -0,0 +1,41 @@
+using System.Linq;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Content.Client.Access.UI;
+using Content.Client.Doors.Electronics;
+using Content.Shared.Access;
+using Content.Shared.Doors.Electronics;
+using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
+
+namespace Content.Client.Doors.Electronics;
+
+[GenerateTypedNameReferences]
+public sealed partial class DoorElectronicsConfigurationMenu : FancyWindow
+{
+ private readonly DoorElectronicsBoundUserInterface _owner;
+ private AccessLevelControl _buttonsList = new();
+
+ public DoorElectronicsConfigurationMenu(DoorElectronicsBoundUserInterface ui, List> accessLevels, IPrototypeManager prototypeManager)
+ {
+ RobustXamlLoader.Load(this);
+
+ _owner = ui;
+
+ _buttonsList.Populate(accessLevels, prototypeManager);
+ AccessLevelControlContainer.AddChild(_buttonsList);
+
+ foreach (var (id, button) in _buttonsList.ButtonsList)
+ {
+ button.OnPressed += _ => _owner.UpdateConfiguration(
+ _buttonsList.ButtonsList.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
+ }
+ }
+
+ public void UpdateState(DoorElectronicsConfigurationState state)
+ {
+ _buttonsList.UpdateState(state.AccessList);
+ }
+}
diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs
index 10707ab93d..7f921fc1a6 100644
--- a/Content.Client/Entry/EntryPoint.cs
+++ b/Content.Client/Entry/EntryPoint.cs
@@ -111,7 +111,6 @@ public override void Init()
_prototypeManager.RegisterIgnore("gameMapPool");
_prototypeManager.RegisterIgnore("npcFaction");
_prototypeManager.RegisterIgnore("lobbyBackground");
- _prototypeManager.RegisterIgnore("advertisementsPack");
_prototypeManager.RegisterIgnore("gamePreset");
_prototypeManager.RegisterIgnore("noiseChannel");
_prototypeManager.RegisterIgnore("spaceBiome");
@@ -124,7 +123,7 @@ public override void Init()
_prototypeManager.RegisterIgnore("wireLayout");
_prototypeManager.RegisterIgnore("alertLevels");
_prototypeManager.RegisterIgnore("nukeopsRole");
- _prototypeManager.RegisterIgnore("stationGoal"); // Corvax-StationGoal
+ _prototypeManager.RegisterIgnore("stationGoal");
_componentFactory.GenerateNetIds();
_adminManager.Initialize();
diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs
index 1be472b06d..45db4efa53 100644
--- a/Content.Client/Examine/ExamineSystem.cs
+++ b/Content.Client/Examine/ExamineSystem.cs
@@ -212,14 +212,16 @@ public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCurso
var vBox = new BoxContainer
{
Name = "ExaminePopupVbox",
- Orientation = LayoutOrientation.Vertical
+ Orientation = LayoutOrientation.Vertical,
+ MaxWidth = _examineTooltipOpen.MaxWidth
};
panel.AddChild(vBox);
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
- SeparationOverride = 5
+ SeparationOverride = 5,
+ Margin = new Thickness(6, 0, 6, 0)
};
vBox.AddChild(hBox);
@@ -229,8 +231,7 @@ public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCurso
var spriteView = new SpriteView
{
OverrideDirection = Direction.South,
- SetSize = new Vector2(32, 32),
- Margin = new Thickness(2, 0, 2, 0),
+ SetSize = new Vector2(32, 32)
};
spriteView.SetEntity(target);
hBox.AddChild(spriteView);
@@ -238,19 +239,17 @@ public void OpenTooltip(EntityUid player, EntityUid target, bool centeredOnCurso
if (knowTarget)
{
- hBox.AddChild(new Label
- {
- Text = Identity.Name(target, EntityManager, player),
- HorizontalExpand = true,
- });
+ var itemName = FormattedMessage.RemoveMarkup(Identity.Name(target, EntityManager, player));
+ var labelMessage = FormattedMessage.FromMarkup($"[bold]{itemName}[/bold]");
+ var label = new RichTextLabel();
+ label.SetMessage(labelMessage);
+ hBox.AddChild(label);
}
else
{
- hBox.AddChild(new Label
- {
- Text = "???",
- HorizontalExpand = true,
- });
+ var label = new RichTextLabel();
+ label.SetMessage(FormattedMessage.FromMarkup("[bold]???[/bold]"));
+ hBox.AddChild(label);
}
panel.Measure(Vector2Helpers.Infinity);
diff --git a/Content.Client/Eye/PenLight/UI/PenLightBoundUserInterface.cs b/Content.Client/Eye/PenLight/UI/PenLightBoundUserInterface.cs
new file mode 100644
index 0000000000..c488753115
--- /dev/null
+++ b/Content.Client/Eye/PenLight/UI/PenLightBoundUserInterface.cs
@@ -0,0 +1,47 @@
+using Content.Shared.Medical;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Eye.PenLight.UI
+{
+ [UsedImplicitly]
+ public sealed class PenLightBoundUserInterface : BoundUserInterface
+ {
+ [ViewVariables]
+ private PenLightWindow? _window;
+
+ public PenLightBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) { }
+
+ protected override void Open()
+ {
+ base.Open();
+ _window = new PenLightWindow
+ {
+ Title = EntMan.GetComponent(Owner).EntityName,
+ };
+ _window.OnClose += Close;
+ _window.OpenCentered();
+ }
+
+ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+ {
+ if (_window == null
+ || message is not PenLightUserMessage cast)
+ return;
+
+ _window.Diagnose(cast);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
+
+ if (_window != null)
+ _window.OnClose -= Close;
+
+ _window?.Dispose();
+ }
+ }
+}
diff --git a/Content.Client/Eye/PenLight/UI/PenLightWindow.xaml b/Content.Client/Eye/PenLight/UI/PenLightWindow.xaml
new file mode 100644
index 0000000000..149b8a1382
--- /dev/null
+++ b/Content.Client/Eye/PenLight/UI/PenLightWindow.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Content.Client/Eye/PenLight/UI/PenLightWindow.xaml.cs b/Content.Client/Eye/PenLight/UI/PenLightWindow.xaml.cs
new file mode 100644
index 0000000000..809a569fa4
--- /dev/null
+++ b/Content.Client/Eye/PenLight/UI/PenLightWindow.xaml.cs
@@ -0,0 +1,78 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Damage;
+using Content.Shared.IdentityManagement;
+using Content.Shared.Medical;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.XAML;
+using System.Text;
+
+
+namespace Content.Client.Eye.PenLight.UI
+{
+ [GenerateTypedNameReferences]
+ public sealed partial class PenLightWindow : FancyWindow
+ {
+ private readonly IEntityManager _entityManager;
+ private const int LightHeight = 150;
+ private const int LightWidth = 900;
+
+ public PenLightWindow()
+ {
+ RobustXamlLoader.Load(this);
+
+ var dependencies = IoCManager.Instance!;
+ _entityManager = dependencies.Resolve();
+ }
+ public void Diagnose(PenLightUserMessage msg)
+ {
+ var target = _entityManager.GetEntity(msg.TargetEntity);
+
+ if (target == null || !_entityManager.TryGetComponent(target, out var damageable))
+ {
+ NoPatientDataText.Visible = true;
+ ExamDataLabel.Text = string.Empty;
+ return;
+ }
+
+ NoPatientDataText.Visible = false;
+
+
+ string entityName = Loc.GetString("pen-light-window-entity-unknown-text");
+ if (_entityManager.HasComponent(target.Value))
+ entityName = Identity.Name(target.Value, _entityManager);
+
+ var sb = new StringBuilder();
+ sb.AppendLine(Loc.GetString("pen-light-window-entity-eyes-text", ("entityName", entityName)));
+
+ // Check if Blind and return early if true
+ if (msg.Blind == true)
+ {
+ sb.AppendLine(Loc.GetString("pen-light-exam-blind-text"));
+ ExamDataLabel.Text = sb.ToString();
+ SetHeight = LightHeight;
+ SetWidth = LightWidth;
+ return;
+ }
+ // EyeDamage
+ if (msg.EyeDamage == true)
+ sb.AppendLine(Loc.GetString("pen-light-exam-eyedamage-text"));
+
+ // Drunk
+ if (msg.Drunk == true)
+ sb.AppendLine(Loc.GetString("pen-light-exam-drunk-text"));
+
+ // Hallucinating
+ if (msg.SeeingRainbows == true)
+ sb.AppendLine(Loc.GetString("pen-light-exam-hallucinating-text"));
+
+ // Healthy
+ if (msg.Healthy == true)
+ sb.AppendLine(Loc.GetString("pen-light-exam-healthy-text"));
+
+ ExamDataLabel.Text = sb.ToString();
+
+ SetHeight = LightHeight;
+ SetWidth = LightWidth;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Content.Client/Flash/FlashSystem.cs b/Content.Client/Flash/FlashSystem.cs
index ad8f8b0b82..57a91983a9 100644
--- a/Content.Client/Flash/FlashSystem.cs
+++ b/Content.Client/Flash/FlashSystem.cs
@@ -38,8 +38,10 @@ private void OnFlashableHandleState(EntityUid uid, FlashableComponent component,
// Few things here:
// 1. If a shorter duration flash is applied then don't do anything
// 2. If the client-side time is later than when the flash should've ended don't do anything
+ var calculatedStateDuration = state.Duration * state.DurationMultiplier;
+
var currentTime = _gameTiming.CurTime.TotalSeconds;
- var newEndTime = state.Time.TotalSeconds + state.Duration;
+ var newEndTime = state.Time.TotalSeconds + calculatedStateDuration;
var currentEndTime = component.LastFlash.TotalSeconds + component.Duration;
if (currentEndTime > newEndTime)
@@ -53,7 +55,7 @@ private void OnFlashableHandleState(EntityUid uid, FlashableComponent component,
}
component.LastFlash = state.Time;
- component.Duration = state.Duration;
+ component.Duration = calculatedStateDuration;
var overlay = _overlayManager.GetOverlay();
overlay.ReceiveFlash(component.Duration);
diff --git a/Content.Client/Fluids/PuddleSystem.cs b/Content.Client/Fluids/PuddleSystem.cs
index 54b1d5b86b..5dbffe0fd2 100644
--- a/Content.Client/Fluids/PuddleSystem.cs
+++ b/Content.Client/Fluids/PuddleSystem.cs
@@ -1,7 +1,9 @@
using Content.Client.IconSmoothing;
+using Content.Shared.Chemistry.Components;
using Content.Shared.Fluids;
using Content.Shared.Fluids.Components;
using Robust.Client.GameObjects;
+using Robust.Shared.Map;
namespace Content.Client.Fluids;
@@ -21,7 +23,7 @@ private void OnPuddleAppearance(EntityUid uid, PuddleComponent component, ref Ap
if (args.Sprite == null)
return;
- float volume = 1f;
+ var volume = 1f;
if (args.AppearanceData.TryGetValue(PuddleVisuals.CurrentVolume, out var volumeObj))
{
@@ -64,4 +66,38 @@ private void OnPuddleAppearance(EntityUid uid, PuddleComponent component, ref Ap
args.Sprite.Color *= baseColor;
}
}
+
+ #region Spill
+
+ // Maybe someday we'll have clientside prediction for entity spawning, but not today.
+ // Until then, these methods do nothing on the client.
+ ///
+ public override bool TrySplashSpillAt(EntityUid uid, EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true, EntityUid? user = null)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ ///
+ public override bool TrySpillAt(EntityCoordinates coordinates, Solution solution, out EntityUid puddleUid, bool sound = true)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ ///
+ public override bool TrySpillAt(EntityUid uid, Solution solution, out EntityUid puddleUid, bool sound = true, TransformComponent? transformComponent = null)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ ///
+ public override bool TrySpillAt(TileRef tileRef, Solution solution, out EntityUid puddleUid, bool sound = true, bool tileReact = true)
+ {
+ puddleUid = EntityUid.Invalid;
+ return false;
+ }
+
+ #endregion Spill
}
diff --git a/Content.Client/Forensics/ScentTrackerSystem.cs b/Content.Client/Forensics/ScentTrackerSystem.cs
new file mode 100644
index 0000000000..4e6254502a
--- /dev/null
+++ b/Content.Client/Forensics/ScentTrackerSystem.cs
@@ -0,0 +1,31 @@
+using Content.Shared.Forensics;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+using Robust.Client.Player;
+
+namespace Content.Client.Forensics
+{
+ public sealed class ScentTrackerSystem : EntitySystem
+ {
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = AllEntityQuery();
+ while (query.MoveNext(out var uid, out var comp))
+ if (TryComp(_playerManager.LocalEntity, out var scentcomp)
+ && scentcomp.Scent != string.Empty
+ && scentcomp.Scent == comp.Scent
+ && _timing.CurTime > comp.TargetTime)
+ {
+ comp.TargetTime = _timing.CurTime + TimeSpan.FromSeconds(1.0f);
+ Spawn("ScentTrackEffect", _transform.GetMapCoordinates(uid).Offset(_random.NextVector2(0.25f)));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Content.Client/Gameplay/GameplayState.cs b/Content.Client/Gameplay/GameplayState.cs
index 1efee978f3..2ea16521e8 100644
--- a/Content.Client/Gameplay/GameplayState.cs
+++ b/Content.Client/Gameplay/GameplayState.cs
@@ -93,17 +93,17 @@ private void LoadMainScreen()
var screenTypeString = _configurationManager.GetCVar(CCVars.UILayout);
if (!Enum.TryParse(screenTypeString, out ScreenType screenType))
{
- screenType = default;
+ screenType = ScreenType.Separated;
}
switch (screenType)
{
- case ScreenType.Default:
- _uiManager.LoadScreen();
- break;
case ScreenType.Separated:
_uiManager.LoadScreen();
break;
+ case ScreenType.Overlay:
+ _uiManager.LoadScreen();
+ break;
}
_loadController.LoadScreen();
diff --git a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
index 1d8d415bab..0cb3ad144d 100644
--- a/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
+++ b/Content.Client/HealthAnalyzer/UI/HealthAnalyzerWindow.xaml.cs
@@ -1,5 +1,6 @@
using System.Linq;
using System.Numerics;
+using Content.Shared.Atmos;
using Content.Client.UserInterface.Controls;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
@@ -79,7 +80,7 @@ public void Populate(HealthAnalyzerScannedUserMessage msg)
);
Temperature.Text = Loc.GetString("health-analyzer-window-entity-temperature-text",
- ("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - 273f:F1} °C ({msg.Temperature:F1} °K)")
+ ("temperature", float.IsNaN(msg.Temperature) ? "N/A" : $"{msg.Temperature - Atmospherics.T0C:F1} °C ({msg.Temperature:F1} K)")
);
BloodLevel.Text = Loc.GetString("health-analyzer-window-entity-blood-level-text",
diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
index 5bae35da5b..8087d1833e 100644
--- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
+++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs
@@ -1,3 +1,4 @@
+using System.Numerics;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
@@ -30,6 +31,15 @@ private void UpdateSprite(HumanoidAppearanceComponent component, SpriteComponent
UpdateLayers(component, sprite);
ApplyMarkingSet(component, sprite);
+ var speciesPrototype = _prototypeManager.Index(component.Species);
+
+ var height = Math.Clamp(component.Height, speciesPrototype.MinHeight, speciesPrototype.MaxHeight);
+ var width = Math.Clamp(component.Width, speciesPrototype.MinWidth, speciesPrototype.MaxWidth);
+ component.Height = height;
+ component.Width = width;
+
+ sprite.Scale = new Vector2(width, height);
+
sprite[sprite.LayerMapReserveBlank(HumanoidVisualLayers.Eyes)].Color = component.EyeColor;
}
@@ -194,6 +204,8 @@ public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile
humanoid.Species = profile.Species;
humanoid.SkinColor = profile.Appearance.SkinColor;
humanoid.EyeColor = profile.Appearance.EyeColor;
+ humanoid.Height = profile.Height;
+ humanoid.Width = profile.Width;
UpdateSprite(humanoid, Comp(uid));
}
diff --git a/Content.Client/IconSmoothing/IconSmoothSystem.cs b/Content.Client/IconSmoothing/IconSmoothSystem.cs
index 0ab33c6520..4b02560846 100644
--- a/Content.Client/IconSmoothing/IconSmoothSystem.cs
+++ b/Content.Client/IconSmoothing/IconSmoothSystem.cs
@@ -16,8 +16,6 @@ namespace Content.Client.IconSmoothing
[UsedImplicitly]
public sealed partial class IconSmoothSystem : EntitySystem
{
- [Dependency] private readonly IMapManager _mapManager = default!;
-
private readonly Queue _dirtyEntities = new();
private readonly Queue _anchorChangedEntities = new();
@@ -47,7 +45,7 @@ private void OnStartup(EntityUid uid, IconSmoothComponent component, ComponentSt
var xform = Transform(uid);
if (xform.Anchored)
{
- component.LastPosition = _mapManager.TryGetGrid(xform.GridUid, out var grid)
+ component.LastPosition = TryComp(xform.GridUid, out var grid)
? (xform.GridUid.Value, grid.TileIndicesFor(xform.Coordinates))
: (null, new Vector2i(0, 0));
@@ -134,7 +132,7 @@ public void DirtyNeighbours(EntityUid uid, IconSmoothComponent? comp = null, Tra
Vector2i pos;
- if (transform.Anchored && _mapManager.TryGetGrid(transform.GridUid, out var grid))
+ if (transform.Anchored && TryComp(transform.GridUid, out var grid))
{
pos = grid.CoordinatesToTile(transform.Coordinates);
}
@@ -144,7 +142,7 @@ public void DirtyNeighbours(EntityUid uid, IconSmoothComponent? comp = null, Tra
if (comp.LastPosition is not (EntityUid gridId, Vector2i oldPos))
return;
- if (!_mapManager.TryGetGrid(gridId, out grid))
+ if (!TryComp(gridId, out grid))
return;
pos = oldPos;
@@ -206,7 +204,7 @@ private void CalculateNewSprite(EntityUid uid,
{
var directions = DirectionFlag.None;
- if (_mapManager.TryGetGrid(xform.GridUid, out grid))
+ if (TryComp(xform.GridUid, out grid))
{
var pos = grid.TileIndicesFor(xform.Coordinates);
@@ -240,7 +238,7 @@ private void CalculateNewSprite(EntityUid uid,
if (xform.Anchored)
{
- if (!_mapManager.TryGetGrid(xform.GridUid, out grid))
+ if (!TryComp(xform.GridUid, out grid))
{
Log.Error($"Failed to calculate IconSmoothComponent sprite in {uid} because grid {xform.GridUid} was missing.");
return;
diff --git a/Content.Client/Input/ContentContexts.cs b/Content.Client/Input/ContentContexts.cs
index 03f4f3f38b..503a9ac953 100644
--- a/Content.Client/Input/ContentContexts.cs
+++ b/Content.Client/Input/ContentContexts.cs
@@ -32,6 +32,7 @@ public static void SetupContexts(IInputContextContainer contexts)
common.AddFunction(ContentKeyFunctions.ToggleFullscreen);
common.AddFunction(ContentKeyFunctions.MoveStoredItem);
common.AddFunction(ContentKeyFunctions.RotateStoredItem);
+ common.AddFunction(ContentKeyFunctions.SaveItemLocation);
common.AddFunction(ContentKeyFunctions.Point);
common.AddFunction(ContentKeyFunctions.ZoomOut);
common.AddFunction(ContentKeyFunctions.ZoomIn);
@@ -44,6 +45,9 @@ public static void SetupContexts(IInputContextContainer contexts)
// Not in engine because the engine doesn't understand what a flipped object is
common.AddFunction(ContentKeyFunctions.EditorFlipObject);
+ // Not in engine so that the RCD can rotate objects
+ common.AddFunction(EngineKeyFunctions.EditorRotateObject);
+
var human = contexts.GetContext("human");
human.AddFunction(EngineKeyFunctions.MoveUp);
human.AddFunction(EngineKeyFunctions.MoveDown);
@@ -55,6 +59,7 @@ public static void SetupContexts(IInputContextContainer contexts)
human.AddFunction(ContentKeyFunctions.UseItemInHand);
human.AddFunction(ContentKeyFunctions.AltUseItemInHand);
human.AddFunction(ContentKeyFunctions.OpenCharacterMenu);
+ human.AddFunction(ContentKeyFunctions.OpenLanguageMenu);
human.AddFunction(ContentKeyFunctions.ActivateItemInWorld);
human.AddFunction(ContentKeyFunctions.ThrowItemInHand);
human.AddFunction(ContentKeyFunctions.AltActivateItemInWorld);
@@ -67,6 +72,8 @@ public static void SetupContexts(IInputContextContainer contexts)
human.AddFunction(ContentKeyFunctions.SmartEquipBelt);
human.AddFunction(ContentKeyFunctions.OpenBackpack);
human.AddFunction(ContentKeyFunctions.OpenBelt);
+ human.AddFunction(ContentKeyFunctions.OfferItem);
+ human.AddFunction(ContentKeyFunctions.ToggleStanding);
human.AddFunction(ContentKeyFunctions.MouseMiddle);
human.AddFunction(ContentKeyFunctions.ArcadeUp);
human.AddFunction(ContentKeyFunctions.ArcadeDown);
diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs
index f8eb12df91..4bb49fecc1 100644
--- a/Content.Client/Inventory/StrippableBoundUserInterface.cs
+++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs
@@ -19,6 +19,7 @@
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
+using Robust.Client.Player;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
@@ -31,6 +32,7 @@ namespace Content.Client.Inventory
public sealed class StrippableBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IUserInterfaceManager _ui = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly ExamineSystem _examine;
private readonly InventorySystem _inv;
private readonly SharedCuffableSystem _cuffable;
@@ -198,7 +200,8 @@ private void AddInventoryButton(EntityUid invUid, string slotId, InventoryCompon
var entity = container.ContainedEntity;
// If this is a full pocket, obscure the real entity
- if (entity != null && slotDef.StripHidden)
+ if (entity != null && slotDef.StripHidden
+ && !(EntMan.TryGetComponent(_playerManager.LocalEntity, out var thiefcomponent) && thiefcomponent.IgnoreStripHidden))
entity = _virtualHiddenEntity;
var button = new SlotButton(new SlotData(slotDef, container));
diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs
index 92b10fe049..01c8f38281 100644
--- a/Content.Client/IoC/ClientContentIoC.cs
+++ b/Content.Client/IoC/ClientContentIoC.cs
@@ -23,6 +23,7 @@
using Content.Client.Guidebook;
using Content.Client.Replay;
using Content.Shared.Administration.Managers;
+using Content.Shared.Players.PlayTimeTracking;
namespace Content.Client.IoC
@@ -31,6 +32,8 @@ internal static class ClientContentIoC
{
public static void Register()
{
+ var collection = IoCManager.Instance!;
+
IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
@@ -51,6 +54,7 @@ public static void Register()
IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
+ collection.Register();
IoCManager.Register();
IoCManager.Register();
}
diff --git a/Content.Client/Items/Systems/ItemSystem.cs b/Content.Client/Items/Systems/ItemSystem.cs
index e406ba2b55..5e60d06d0c 100644
--- a/Content.Client/Items/Systems/ItemSystem.cs
+++ b/Content.Client/Items/Systems/ItemSystem.cs
@@ -93,7 +93,7 @@ private bool TryGetDefaultVisuals(EntityUid uid, ItemComponent item, string defa
else if (TryComp(uid, out SpriteComponent? sprite))
rsi = sprite.BaseRSI;
- if (rsi == null || rsi.Path == null)
+ if (rsi == null)
return false;
var state = (item.HeldPrefix == null)
diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml b/Content.Client/Kitchen/UI/GrinderMenu.xaml
index b83128d004..dacddd0df6 100644
--- a/Content.Client/Kitchen/UI/GrinderMenu.xaml
+++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml
@@ -3,10 +3,12 @@
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="{Loc grinder-menu-title}" MinSize="768 256">
-
-
-
-
+
+
+
+
+
+
diff --git a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
index 6e4b7a7618..f97d8a7330 100644
--- a/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
+++ b/Content.Client/Kitchen/UI/GrinderMenu.xaml.cs
@@ -24,6 +24,7 @@ public GrinderMenu(ReagentGrinderBoundUserInterface owner, IEntityManager entity
_entityManager = entityManager;
_prototypeManager = prototypeManager;
_owner = owner;
+ AutoModeButton.OnPressed += owner.ToggleAutoMode;
GrindButton.OnPressed += owner.StartGrinding;
JuiceButton.OnPressed += owner.StartJuicing;
ChamberContentBox.EjectButton.OnPressed += owner.EjectAll;
@@ -56,6 +57,19 @@ public void UpdateState(ReagentGrinderInterfaceState state)
GrindButton.Disabled = !state.CanGrind || !state.Powered;
JuiceButton.Disabled = !state.CanJuice || !state.Powered;
+ switch (state.AutoMode)
+ {
+ case GrinderAutoMode.Grind:
+ AutoModeButton.Text = Loc.GetString("grinder-menu-grind-button");
+ break;
+ case GrinderAutoMode.Juice:
+ AutoModeButton.Text = Loc.GetString("grinder-menu-juice-button");
+ break;
+ default:
+ AutoModeButton.Text = Loc.GetString("grinder-menu-auto-button-off");
+ break;
+ }
+
// TODO move this to a component state and ensure the net ids.
RefreshContentsDisplay(state.ReagentQuantities, _entityManager.GetEntityArray(state.ChamberContents), state.HasBeakerIn);
}
diff --git a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
index 39b85c261b..e6f108b305 100644
--- a/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
+++ b/Content.Client/Kitchen/UI/ReagentGrinderBoundUserInterface.cs
@@ -52,6 +52,11 @@ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
_menu?.HandleMessage(message);
}
+ public void ToggleAutoMode(BaseButton.ButtonEventArgs args)
+ {
+ SendMessage(new ReagentGrinderToggleAutoModeMessage());
+ }
+
public void StartGrinding(BaseButton.ButtonEventArgs? _ = null)
{
SendMessage(new ReagentGrinderStartMessage(GrinderProgram.Grind));
diff --git a/Content.Client/Language/LanguageMenuWindow.xaml b/Content.Client/Language/LanguageMenuWindow.xaml
new file mode 100644
index 0000000000..ff33a6ddf5
--- /dev/null
+++ b/Content.Client/Language/LanguageMenuWindow.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Language/LanguageMenuWindow.xaml.cs b/Content.Client/Language/LanguageMenuWindow.xaml.cs
new file mode 100644
index 0000000000..11d1c290d1
--- /dev/null
+++ b/Content.Client/Language/LanguageMenuWindow.xaml.cs
@@ -0,0 +1,131 @@
+using Content.Client.Language.Systems;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Language;
+
+[GenerateTypedNameReferences]
+public sealed partial class LanguageMenuWindow : DefaultWindow
+{
+ private readonly LanguageSystem _clientLanguageSystem;
+ private readonly List _entries = new();
+
+
+ public LanguageMenuWindow()
+ {
+ RobustXamlLoader.Load(this);
+ _clientLanguageSystem = IoCManager.Resolve().GetEntitySystem();
+ }
+
+ protected override void Opened()
+ {
+ // Refresh the window when it gets opened.
+ // This actually causes two refreshes: one immediately, and one after the server sends a state message.
+ UpdateState(_clientLanguageSystem.CurrentLanguage, _clientLanguageSystem.SpokenLanguages);
+ _clientLanguageSystem.RequestStateUpdate();
+ }
+
+
+ public void UpdateState(string currentLanguage, List spokenLanguages)
+ {
+ var langName = Loc.GetString($"language-{currentLanguage}-name");
+ CurrentLanguageLabel.Text = Loc.GetString("language-menu-current-language", ("language", langName));
+
+ OptionsList.RemoveAllChildren();
+ _entries.Clear();
+
+ foreach (var language in spokenLanguages)
+ {
+ AddLanguageEntry(language);
+ }
+
+ // Disable the button for the currently chosen language
+ foreach (var entry in _entries)
+ {
+ if (entry.button != null)
+ entry.button.Disabled = entry.language == currentLanguage;
+ }
+ }
+
+ private void AddLanguageEntry(string language)
+ {
+ var proto = _clientLanguageSystem.GetLanguagePrototype(language);
+ var state = new EntryState { language = language };
+
+ var container = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Vertical };
+
+ #region Header
+ var header = new BoxContainer
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ HorizontalExpand = true,
+ SeparationOverride = 2
+ };
+
+ var name = new Label
+ {
+ Text = proto?.Name ?? Loc.GetString("generic-error"),
+ MinWidth = 50,
+ HorizontalExpand = true
+ };
+
+ var button = new Button { Text = "Choose" };
+ button.OnPressed += _ => OnLanguageChosen(language);
+ state.button = button;
+
+ header.AddChild(name);
+ header.AddChild(button);
+
+ container.AddChild(header);
+ #endregion
+
+ #region Collapsible description
+ var body = new CollapsibleBody
+ {
+ HorizontalExpand = true,
+ Margin = new Thickness(4f, 4f)
+ };
+
+ var description = new RichTextLabel { HorizontalExpand = true };
+ description.SetMessage(proto?.Description ?? Loc.GetString("generic-error"));
+ body.AddChild(description);
+
+ var collapser = new Collapsible(Loc.GetString("language-menu-description-header"), body)
+ {
+ Orientation = BoxContainer.LayoutOrientation.Vertical,
+ HorizontalExpand = true
+ };
+
+ container.AddChild(collapser);
+ #endregion
+
+ // Before adding, wrap the new container in a PanelContainer to give it a distinct look
+ var wrapper = new PanelContainer();
+ wrapper.StyleClasses.Add("PdaBorderRect");
+
+ wrapper.AddChild(container);
+ OptionsList.AddChild(wrapper);
+
+ _entries.Add(state);
+ }
+
+
+ private void OnLanguageChosen(string id)
+ {
+ var proto = _clientLanguageSystem.GetLanguagePrototype(id);
+ if (proto == null)
+ return;
+
+ _clientLanguageSystem.RequestSetLanguage(proto);
+ UpdateState(id, _clientLanguageSystem.SpokenLanguages);
+ }
+
+
+ private struct EntryState
+ {
+ public string language;
+ public Button? button;
+ }
+}
diff --git a/Content.Client/Language/Systems/LanguageSystem.cs b/Content.Client/Language/Systems/LanguageSystem.cs
new file mode 100644
index 0000000000..5dc2fc1f4e
--- /dev/null
+++ b/Content.Client/Language/Systems/LanguageSystem.cs
@@ -0,0 +1,75 @@
+using Content.Shared.Language;
+using Content.Shared.Language.Events;
+using Content.Shared.Language.Systems;
+using Robust.Client;
+
+namespace Content.Client.Language.Systems;
+
+///
+/// Client-side language system.
+///
+///
+/// Unlike the server, the client is not aware of other entities' languages; it's only notified about the entity that it posesses.
+/// Due to that, this system stores such information in a static manner.
+///
+public sealed class LanguageSystem : SharedLanguageSystem
+{
+ [Dependency] private readonly IBaseClient _client = default!;
+
+ ///
+ /// The current language of the entity currently possessed by the player.
+ ///
+ public string CurrentLanguage { get; private set; } = default!;
+ ///
+ /// The list of languages the currently possessed entity can speak.
+ ///
+ public List SpokenLanguages { get; private set; } = new();
+ ///
+ /// The list of languages the currently possessed entity can understand.
+ ///
+ public List UnderstoodLanguages { get; private set; } = new();
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeNetworkEvent(OnLanguagesUpdated);
+ _client.RunLevelChanged += OnRunLevelChanged;
+ }
+
+ private void OnLanguagesUpdated(LanguagesUpdatedMessage message)
+ {
+ CurrentLanguage = message.CurrentLanguage;
+ SpokenLanguages = message.Spoken;
+ UnderstoodLanguages = message.Understood;
+ }
+
+ private void OnRunLevelChanged(object? sender, RunLevelChangedEventArgs args)
+ {
+ // Request an update when entering a game
+ if (args.NewLevel == ClientRunLevel.InGame)
+ RequestStateUpdate();
+ }
+
+ ///
+ /// Sends a network request to the server to update this system's state.
+ /// The server may ignore the said request if the player is not possessing an entity.
+ ///
+ public void RequestStateUpdate()
+ {
+ RaiseNetworkEvent(new RequestLanguagesMessage());
+ }
+
+ public void RequestSetLanguage(LanguagePrototype language)
+ {
+ if (language.ID == CurrentLanguage)
+ return;
+
+ RaiseNetworkEvent(new LanguagesSetMessage(language.ID));
+
+ // May cause some minor desync...
+ // So to reduce the probability of desync, we replicate the change locally too
+ if (SpokenLanguages.Contains(language.ID))
+ CurrentLanguage = language.ID;
+ }
+}
diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs
index b99d30015e..92e1ee2aaf 100644
--- a/Content.Client/LateJoin/LateJoinGui.cs
+++ b/Content.Client/LateJoin/LateJoinGui.cs
@@ -4,9 +4,13 @@
using Content.Client.GameTicking.Managers;
using Content.Client.UserInterface.Controls;
using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Preferences;
using Content.Shared.CCVar;
+using Content.Shared.Customization.Systems;
+using Content.Shared.Preferences;
using Content.Shared.Roles;
using Content.Shared.StatusIcon;
+using Microsoft.Win32.SafeHandles;
using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
@@ -26,14 +30,17 @@ public sealed class LateJoinGui : DefaultWindow
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
[Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IClientPreferencesManager _prefs = default!;
public event Action<(NetEntity, string)> SelectedId;
private readonly ClientGameTicker _gameTicker;
private readonly SpriteSystem _sprites;
private readonly CrewManifestSystem _crewManifest;
+ private readonly CharacterRequirementsSystem _characterRequirements;
- private readonly Dictionary> _jobButtons = new();
+ private readonly Dictionary>> _jobButtons = new();
private readonly Dictionary> _jobCategories = new();
private readonly List _jobLists = new();
@@ -46,6 +53,7 @@ public LateJoinGui()
_sprites = _entitySystem.GetEntitySystem();
_crewManifest = _entitySystem.GetEntitySystem();
_gameTicker = _entitySystem.GetEntitySystem();
+ _characterRequirements = _entitySystem.GetEntitySystem();
Title = Loc.GetString("late-join-gui-title");
@@ -139,7 +147,7 @@ private void RebuildUI()
var jobListScroll = new ScrollContainer()
{
VerticalExpand = true,
- Children = {jobList},
+ Children = { jobList },
Visible = false,
};
@@ -163,11 +171,12 @@ private void RebuildUI()
var departments = _prototypeManager.EnumeratePrototypes().ToArray();
Array.Sort(departments, DepartmentUIComparer.Instance);
+ _jobButtons[id] = new Dictionary>();
+
foreach (var department in departments)
{
var departmentName = Loc.GetString($"department-{department.ID}");
_jobCategories[id] = new Dictionary();
- _jobButtons[id] = new Dictionary();
var stationAvailable = _gameTicker.JobsAvailable[id];
var jobsAvailable = new List();
@@ -223,7 +232,13 @@ private void RebuildUI()
foreach (var prototype in jobsAvailable)
{
var value = stationAvailable[prototype.ID];
- var jobButton = new JobButton(prototype.ID, value);
+
+ var jobLabel = new Label
+ {
+ Margin = new Thickness(5f, 0, 0, 0)
+ };
+
+ var jobButton = new JobButton(jobLabel, prototype.ID, prototype.LocalizedName, value);
var jobSelector = new BoxContainer
{
@@ -241,28 +256,30 @@ private void RebuildUI()
icon.Texture = _sprites.Frame0(jobIcon.Icon);
jobSelector.AddChild(icon);
- var jobLabel = new Label
- {
- Margin = new Thickness(5f, 0, 0, 0),
- Text = value != null ?
- Loc.GetString("late-join-gui-job-slot-capped", ("jobName", prototype.LocalizedName), ("amount", value)) :
- Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", prototype.LocalizedName)),
- };
-
jobSelector.AddChild(jobLabel);
jobButton.AddChild(jobSelector);
category.AddChild(jobButton);
jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId));
- if (!_jobRequirements.IsAllowed(prototype, out var reason))
+ if (!_characterRequirements.CheckRequirementsValid(
+ prototype.Requirements ?? new(),
+ prototype,
+ (HumanoidCharacterProfile) (_prefs.Preferences?.SelectedCharacter
+ ?? HumanoidCharacterProfile.DefaultWithSpecies()),
+ _jobRequirements.GetRawPlayTimeTrackers(),
+ _jobRequirements.IsWhitelisted(),
+ _entityManager,
+ _prototypeManager,
+ _configManager,
+ out var reasons))
{
jobButton.Disabled = true;
- if (!reason.IsEmpty)
+ if (reasons.Count > 0)
{
var tooltip = new Tooltip();
- tooltip.SetMessage(reason);
+ tooltip.SetMessage(_characterRequirements.GetRequirementsText(reasons));
jobButton.TooltipSupplier = _ => tooltip;
}
@@ -280,15 +297,43 @@ private void RebuildUI()
jobButton.Disabled = true;
}
- _jobButtons[id][prototype.ID] = jobButton;
+ if (!_jobButtons[id].ContainsKey(prototype.ID))
+ {
+ _jobButtons[id][prototype.ID] = new List();
+ }
+
+ _jobButtons[id][prototype.ID].Add(jobButton);
}
}
}
}
- private void JobsAvailableUpdated(IReadOnlyDictionary> _)
+ private void JobsAvailableUpdated(IReadOnlyDictionary> updatedJobs)
{
- RebuildUI();
+ foreach (var stationEntries in updatedJobs)
+ {
+ if (_jobButtons.ContainsKey(stationEntries.Key))
+ {
+ var jobsAvailable = stationEntries.Value;
+
+ var existingJobEntries = _jobButtons[stationEntries.Key];
+ foreach (var existingJobEntry in existingJobEntries)
+ {
+ if (jobsAvailable.ContainsKey(existingJobEntry.Key))
+ {
+ var updatedJobValue = jobsAvailable[existingJobEntry.Key];
+ foreach (var matchingJobButton in existingJobEntry.Value)
+ {
+ if (matchingJobButton.Amount != updatedJobValue)
+ {
+ matchingJobButton.RefreshLabel(updatedJobValue);
+ matchingJobButton.Disabled |= matchingJobButton.Amount == 0;
+ }
+ }
+ }
+ }
+ }
+ }
}
protected override void Dispose(bool disposing)
@@ -307,14 +352,33 @@ protected override void Dispose(bool disposing)
sealed class JobButton : ContainerButton
{
+ public Label JobLabel { get; }
public string JobId { get; }
- public uint? Amount { get; }
+ public string JobLocalisedName { get; }
+ public uint? Amount { get; private set; }
+ private bool _initialised = false;
- public JobButton(string jobId, uint? amount)
+ public JobButton(Label jobLabel, string jobId, string jobLocalisedName, uint? amount)
{
+ JobLabel = jobLabel;
JobId = jobId;
- Amount = amount;
+ JobLocalisedName = jobLocalisedName;
+ RefreshLabel(amount);
AddStyleClass(StyleClassButton);
+ _initialised = true;
+ }
+
+ public void RefreshLabel(uint? amount)
+ {
+ if (Amount == amount && _initialised)
+ {
+ return;
+ }
+ Amount = amount;
+
+ JobLabel.Text = Amount != null ?
+ Loc.GetString("late-join-gui-job-slot-capped", ("jobName", JobLocalisedName), ("amount", Amount)) :
+ Loc.GetString("late-join-gui-job-slot-uncapped", ("jobName", JobLocalisedName));
}
}
}
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml
index 2b97166f05..6f484d8c7b 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml
@@ -124,9 +124,7 @@
+ Orientation="Vertical">
(id, out var proto))
- continue;
-
- if (first)
- first = false;
- else
- sb.Append('\n');
-
- var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, component.MaterialUseMultiplier);
- var sheetVolume = _materialStorage.GetSheetVolume(proto);
-
- var unit = Loc.GetString(proto.Unit);
- // rounded in locale not here
- var sheets = adjustedAmount / (float) sheetVolume;
- var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
- var name = Loc.GetString(proto.Name);
- sb.Append(Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText)));
- }
-
- if (!string.IsNullOrWhiteSpace(prototype.Description))
- {
- sb.Append('\n');
- sb.Append(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
- }
-
var icon = prototype.Icon == null
? _spriteSystem.GetPrototypeIcon(prototype.Result).Default
: _spriteSystem.Frame0(prototype.Icon);
var canProduce = _lathe.CanProduce(_owner, prototype, quantity);
- var control = new RecipeControl(prototype, sb.ToString(), canProduce, icon);
+ var control = new RecipeControl(prototype, () => GenerateTooltipText(prototype), canProduce, icon);
control.OnButtonPressed += s =>
{
if (!int.TryParse(AmountLineEdit.Text, out var amount) || amount <= 0)
@@ -149,6 +120,51 @@ public void PopulateRecipes()
}
}
+ private string GenerateTooltipText(LatheRecipePrototype prototype)
+ {
+ StringBuilder sb = new();
+
+ foreach (var (id, amount) in prototype.RequiredMaterials)
+ {
+ if (!_prototypeManager.TryIndex(id, out var proto))
+ continue;
+
+ var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, _entityManager.GetComponent(_owner).MaterialUseMultiplier);
+ var sheetVolume = _materialStorage.GetSheetVolume(proto);
+
+ var unit = Loc.GetString(proto.Unit);
+ var sheets = adjustedAmount / (float) sheetVolume;
+
+ var availableAmount = _materialStorage.GetMaterialAmount(_owner, id);
+ var missingAmount = Math.Max(0, adjustedAmount - availableAmount);
+ var missingSheets = missingAmount / (float) sheetVolume;
+
+ var name = Loc.GetString(proto.Name);
+
+ string tooltipText;
+ if (missingSheets > 0)
+ {
+ tooltipText = Loc.GetString("lathe-menu-material-amount-missing", ("amount", sheets), ("missingAmount", missingSheets), ("unit", unit), ("material", name));
+ }
+ else
+ {
+ var amountText = Loc.GetString("lathe-menu-material-amount", ("amount", sheets), ("unit", unit));
+ tooltipText = Loc.GetString("lathe-menu-tooltip-display", ("material", name), ("amount", amountText));
+ }
+
+ sb.AppendLine(tooltipText);
+ }
+
+ if (!string.IsNullOrWhiteSpace(prototype.Description))
+ sb.AppendLine(Loc.GetString("lathe-menu-description-display", ("description", prototype.Description)));
+
+ // Remove last newline
+ if (sb.Length > 0)
+ sb.Remove(sb.Length - 1, 1);
+
+ return sb.ToString();
+ }
+
public void UpdateCategories()
{
var currentCategories = new List>();
diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml.cs b/Content.Client/Lathe/UI/RecipeControl.xaml.cs
index 451a988765..bf85ff7d93 100644
--- a/Content.Client/Lathe/UI/RecipeControl.xaml.cs
+++ b/Content.Client/Lathe/UI/RecipeControl.xaml.cs
@@ -11,17 +11,16 @@ namespace Content.Client.Lathe.UI;
public sealed partial class RecipeControl : Control
{
public Action? OnButtonPressed;
+ public Func TooltipTextSupplier;
- public string TooltipText;
-
- public RecipeControl(LatheRecipePrototype recipe, string tooltip, bool canProduce, Texture? texture = null)
+ public RecipeControl(LatheRecipePrototype recipe, Func tooltipTextSupplier, bool canProduce, Texture? texture = null)
{
RobustXamlLoader.Load(this);
RecipeName.Text = recipe.Name;
RecipeTexture.Texture = texture;
Button.Disabled = !canProduce;
- TooltipText = tooltip;
+ TooltipTextSupplier = tooltipTextSupplier;
Button.TooltipSupplier = SupplyTooltip;
Button.OnPressed += (_) =>
@@ -32,6 +31,6 @@ public RecipeControl(LatheRecipePrototype recipe, string tooltip, bool canProduc
private Control? SupplyTooltip(Control sender)
{
- return new RecipeTooltip(TooltipText);
+ return new RecipeTooltip(TooltipTextSupplier());
}
}
diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs
index fe31dce062..bed52217a9 100644
--- a/Content.Client/Lobby/LobbyState.cs
+++ b/Content.Client/Lobby/LobbyState.cs
@@ -43,9 +43,7 @@ public sealed class LobbyState : Robust.Client.State.State
protected override void Startup()
{
if (_userInterfaceManager.ActiveScreen == null)
- {
return;
- }
_lobby = (LobbyGui) _userInterfaceManager.ActiveScreen;
@@ -70,7 +68,7 @@ protected override void Startup()
_characterSetup.SaveButton.OnPressed += _ =>
{
_characterSetup.Save();
- _lobby.CharacterPreview.UpdateUI();
+ _userInterfaceManager.GetUIController().UpdateCharacterUI();
};
LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide);
@@ -84,10 +82,6 @@ protected override void Startup()
_gameTicker.InfoBlobUpdated += UpdateLobbyUi;
_gameTicker.LobbyStatusUpdated += LobbyStatusUpdated;
_gameTicker.LobbyLateJoinStatusUpdated += LobbyLateJoinStatusUpdated;
-
- _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
-
- _lobby.CharacterPreview.UpdateUI();
}
protected override void Shutdown()
@@ -109,13 +103,6 @@ protected override void Shutdown()
_characterSetup?.Dispose();
_characterSetup = null;
-
- _preferencesManager.OnServerDataLoaded -= PreferencesDataLoaded;
- }
-
- private void PreferencesDataLoaded()
- {
- _lobby?.CharacterPreview.UpdateUI();
}
private void OnSetupPressed(BaseButton.ButtonEventArgs args)
@@ -127,9 +114,7 @@ private void OnSetupPressed(BaseButton.ButtonEventArgs args)
private void OnReadyPressed(BaseButton.ButtonEventArgs args)
{
if (!_gameTicker.IsGameStarted)
- {
return;
- }
new LateJoinGui().OpenCentered();
}
@@ -153,9 +138,7 @@ public override void FrameUpdate(FrameEventArgs e)
string text;
if (_gameTicker.Paused)
- {
text = Loc.GetString("lobby-state-paused");
- }
else if (_gameTicker.StartTime < _gameTiming.CurTime)
{
_lobby!.StartTime.Text = Loc.GetString("lobby-state-soon");
@@ -166,13 +149,11 @@ public override void FrameUpdate(FrameEventArgs e)
var difference = _gameTicker.StartTime - _gameTiming.CurTime;
var seconds = difference.TotalSeconds;
if (seconds < 0)
- {
- text = Loc.GetString(seconds < -5 ? "lobby-state-right-now-question" : "lobby-state-right-now-confirmation");
- }
+ text = Loc.GetString(seconds < -5
+ ? "lobby-state-right-now-question"
+ : "lobby-state-right-now-confirmation");
else
- {
text = $"{difference.Minutes}:{difference.Seconds:D2}";
- }
}
_lobby!.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text));
@@ -209,21 +190,15 @@ private void UpdateLobbyUi()
}
if (_gameTicker.ServerInfoBlob != null)
- {
_lobby!.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob);
- }
}
private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev)
{
if (ev.SoundtrackFilename == null)
- {
_lobby!.LobbySong.SetMarkup(Loc.GetString("lobby-state-song-no-song-text"));
- }
- else if (
- ev.SoundtrackFilename != null
- && _resourceCache.TryGetResource(ev.SoundtrackFilename, out var lobbySongResource)
- )
+ else if (ev.SoundtrackFilename != null
+ && _resourceCache.TryGetResource(ev.SoundtrackFilename, out var lobbySongResource))
{
var lobbyStream = lobbySongResource.AudioStream;
@@ -246,22 +221,16 @@ private void UpdateLobbySoundtrackInfo(LobbySoundtrackChangedEvent ev)
private void UpdateLobbyBackground()
{
if (_gameTicker.LobbyBackground != null)
- {
- _lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground );
- }
+ _lobby!.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground);
else
- {
_lobby!.Background.Texture = null;
- }
}
private void SetReady(bool newReady)
{
if (_gameTicker.IsGameStarted)
- {
return;
- }
_consoleHost.ExecuteCommand($"toggleready {newReady}");
}
diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs
new file mode 100644
index 0000000000..47ab651c10
--- /dev/null
+++ b/Content.Client/Lobby/LobbyUIController.cs
@@ -0,0 +1,209 @@
+using System.Linq;
+using Content.Client.Humanoid;
+using Content.Client.Inventory;
+using Content.Client.Lobby.UI;
+using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Preferences;
+using Content.Client.Preferences.UI;
+using Content.Shared.Clothing.Loadouts.Systems;
+using Content.Shared.GameTicking;
+using Content.Shared.Humanoid;
+using Content.Shared.Humanoid.Prototypes;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Robust.Client.State;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controllers;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Lobby;
+
+public sealed class LobbyUIController : UIController, IOnStateEntered, IOnStateExited
+{
+ [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
+ [Dependency] private readonly IStateManager _stateManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
+ [UISystemDependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
+ [UISystemDependency] private readonly ClientInventorySystem _inventory = default!;
+ [UISystemDependency] private readonly LoadoutSystem _loadouts = default!;
+
+ private LobbyCharacterPanel? _previewPanel;
+ private HumanoidProfileEditor? _profileEditor;
+
+ /*
+ * Each character profile has its own dummy. There is also a dummy for the lobby screen + character editor
+ * that is shared too.
+ */
+
+ ///
+ /// Preview dummy for role gear.
+ ///
+ private EntityUid? _previewDummy;
+
+ [Access(typeof(HumanoidProfileEditor))]
+ public bool UpdateClothes = true;
+ [Access(typeof(HumanoidProfileEditor))]
+ public bool ShowClothes = true;
+ [Access(typeof(HumanoidProfileEditor))]
+ public bool ShowLoadouts = true;
+
+ // TODO: Load the species directly and don't update entity ever.
+ public event Action? PreviewDummyUpdated;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _preferencesManager.OnServerDataLoaded += PreferencesDataLoaded;
+ }
+
+ private void PreferencesDataLoaded()
+ {
+ if (_previewDummy != null)
+ EntityManager.DeleteEntity(_previewDummy);
+
+ UpdateCharacterUI();
+ }
+
+ public void OnStateEntered(LobbyState state)
+ {
+ }
+
+ public void OnStateExited(LobbyState state)
+ {
+ EntityManager.DeleteEntity(_previewDummy);
+ _previewDummy = null;
+ }
+
+ public void SetPreviewPanel(LobbyCharacterPanel? panel)
+ {
+ _previewPanel = panel;
+ UpdateCharacterUI();
+ }
+
+ public void SetProfileEditor(HumanoidProfileEditor? editor)
+ {
+ _profileEditor = editor;
+ UpdateCharacterUI();
+ }
+
+ public void UpdateCharacterUI()
+ {
+ // Test moment
+ if (_stateManager.CurrentState is not LobbyState)
+ return;
+
+ if (!_preferencesManager.ServerDataLoaded)
+ {
+ _previewPanel?.SetLoaded(false);
+ return;
+ }
+
+ var maybeProfile = _profileEditor?.Profile ?? (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
+
+ if (_previewDummy == null
+ || maybeProfile.Species != EntityManager.GetComponent(_previewDummy.Value).Species)
+ {
+ RespawnDummy(maybeProfile);
+ _previewPanel?.SetSprite(_previewDummy!.Value);
+ }
+
+ _previewPanel?.SetLoaded(true);
+
+ if (_previewDummy == null)
+ return;
+
+ _previewPanel?.SetSummaryText(maybeProfile.Summary);
+ _humanoid.LoadProfile(_previewDummy.Value, maybeProfile);
+
+
+ if (UpdateClothes)
+ {
+ RemoveDummyClothes(_previewDummy.Value);
+ if (ShowClothes)
+ GiveDummyJobClothes(_previewDummy.Value, GetPreferredJob(maybeProfile), maybeProfile);
+ if (ShowLoadouts)
+ _loadouts.ApplyCharacterLoadout(_previewDummy.Value, GetPreferredJob(maybeProfile), maybeProfile,
+ _jobRequirements.GetRawPlayTimeTrackers(), _jobRequirements.IsWhitelisted());
+ UpdateClothes = false;
+ }
+
+ PreviewDummyUpdated?.Invoke(_previewDummy.Value);
+ }
+
+
+ public void RespawnDummy(HumanoidCharacterProfile profile)
+ {
+ if (_previewDummy != null)
+ RemoveDummyClothes(_previewDummy.Value);
+
+ EntityManager.DeleteEntity(_previewDummy);
+ _previewDummy = EntityManager.SpawnEntity(
+ _prototypeManager.Index(profile.Species).DollPrototype, MapCoordinates.Nullspace);
+
+ UpdateClothes = true;
+ }
+
+ ///
+ /// Gets the highest priority job for the profile.
+ ///
+ public JobPrototype GetPreferredJob(HumanoidCharacterProfile profile)
+ {
+ var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
+ // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is ReSharper smoking?)
+ return _prototypeManager.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
+ }
+
+ public void RemoveDummyClothes(EntityUid dummy)
+ {
+ if (!_inventory.TryGetSlots(dummy, out var slots))
+ return;
+
+ foreach (var slot in slots)
+ if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
+ EntityManager.DeleteEntity(unequippedItem.Value);
+ }
+
+ ///
+ /// Applies the highest priority job's clothes and loadouts to the dummy.
+ ///
+ public void GiveDummyJobClothesLoadout(EntityUid dummy, HumanoidCharacterProfile profile)
+ {
+ var job = GetPreferredJob(profile);
+ GiveDummyJobClothes(dummy, job, profile);
+ _loadouts.ApplyCharacterLoadout(dummy, job, profile, _jobRequirements.GetRawPlayTimeTrackers(), _jobRequirements.IsWhitelisted());
+ }
+
+ ///
+ /// Applies the specified job's clothes to the dummy.
+ ///
+ public void GiveDummyJobClothes(EntityUid dummy, JobPrototype job, HumanoidCharacterProfile profile)
+ {
+ if (!_inventory.TryGetSlots(dummy, out var slots)
+ || job.StartingGear == null)
+ return;
+
+ var gear = _prototypeManager.Index(job.StartingGear);
+
+ foreach (var slot in slots)
+ {
+ var itemType = gear.GetGear(slot.Name, profile);
+
+ if (_inventory.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
+ EntityManager.DeleteEntity(unequippedItem.Value);
+
+ if (itemType == string.Empty)
+ continue;
+
+ var item = EntityManager.SpawnEntity(itemType, MapCoordinates.Nullspace);
+ _inventory.TryEquip(dummy, item, slot.Name, true, true);
+ }
+ }
+
+ public EntityUid? GetPreviewDummy()
+ {
+ return _previewDummy;
+ }
+}
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPanel.xaml b/Content.Client/Lobby/UI/LobbyCharacterPanel.xaml
new file mode 100644
index 0000000000..997507414c
--- /dev/null
+++ b/Content.Client/Lobby/UI/LobbyCharacterPanel.xaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPanel.xaml.cs b/Content.Client/Lobby/UI/LobbyCharacterPanel.xaml.cs
new file mode 100644
index 0000000000..160f1d7201
--- /dev/null
+++ b/Content.Client/Lobby/UI/LobbyCharacterPanel.xaml.cs
@@ -0,0 +1,45 @@
+using System.Numerics;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Lobby.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class LobbyCharacterPanel : Control
+{
+ public Button CharacterSetupButton => CharacterSetup;
+
+ public LobbyCharacterPanel()
+ {
+ RobustXamlLoader.Load(this);
+ UserInterfaceManager.GetUIController().SetPreviewPanel(this);
+ }
+
+ public void SetLoaded(bool value)
+ {
+ Loaded.Visible = value;
+ Unloaded.Visible = !value;
+ }
+
+ public void SetSummaryText(string value)
+ {
+ Summary.Text = value;
+ }
+
+ public void SetSprite(EntityUid uid)
+ {
+ ViewBox.DisposeAllChildren();
+ var spriteView = new SpriteView
+ {
+ OverrideDirection = Direction.South,
+ Scale = new Vector2(4f, 4f),
+ MaxSize = new Vector2(112, 112),
+ Stretch = SpriteView.StretchMode.Fill,
+ };
+ spriteView.SetEntity(uid);
+ ViewBox.AddChild(spriteView);
+ }
+}
diff --git a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs b/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs
deleted file mode 100644
index 59a518f269..0000000000
--- a/Content.Client/Lobby/UI/LobbyCharacterPreviewPanel.cs
+++ /dev/null
@@ -1,173 +0,0 @@
-using System.Linq;
-using System.Numerics;
-using Content.Client.Humanoid;
-using Content.Client.Inventory;
-using Content.Client.Preferences;
-using Content.Client.UserInterface.Controls;
-using Content.Shared.Clothing.Loadouts.Systems;
-using Content.Shared.GameTicking;
-using Content.Shared.Humanoid.Prototypes;
-using Content.Shared.Preferences;
-using Content.Shared.Roles;
-using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
-using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
-
-namespace Content.Client.Lobby.UI
-{
- public sealed class LobbyCharacterPreviewPanel : Control
- {
- [Dependency] private readonly IEntityManager _entityManager = default!;
- [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-
-
- private EntityUid? _previewDummy;
- private readonly Label _summaryLabel;
- private readonly BoxContainer _loaded;
- private readonly BoxContainer _viewBox;
- private readonly Label _unloaded;
-
- public LobbyCharacterPreviewPanel()
- {
- IoCManager.InjectDependencies(this);
- var header = new NanoHeading
- {
- Text = Loc.GetString("lobby-character-preview-panel-header")
- };
-
- CharacterSetupButton = new Button
- {
- Text = Loc.GetString("lobby-character-preview-panel-character-setup-button"),
- HorizontalAlignment = HAlignment.Center,
- Margin = new Thickness(0, 5, 0, 0),
- };
-
- _summaryLabel = new Label
- {
- HorizontalAlignment = HAlignment.Center,
- Margin = new Thickness(3, 3),
- };
-
- var vBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical
- };
- _unloaded = new Label { Text = Loc.GetString("lobby-character-preview-panel-unloaded-preferences-label") };
-
- _loaded = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Visible = false
- };
- _viewBox = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- HorizontalAlignment = HAlignment.Center,
- };
- var _vSpacer = new VSpacer();
-
- _loaded.AddChild(_summaryLabel);
- _loaded.AddChild(_viewBox);
- _loaded.AddChild(_vSpacer);
- _loaded.AddChild(CharacterSetupButton);
-
- vBox.AddChild(header);
- vBox.AddChild(_loaded);
- vBox.AddChild(_unloaded);
- AddChild(vBox);
-
- UpdateUI();
- }
-
- public Button CharacterSetupButton { get; }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
-
- if (!disposing) return;
- if (_previewDummy != null) _entityManager.DeleteEntity(_previewDummy.Value);
- _previewDummy = default;
- }
-
- public void UpdateUI()
- {
- if (!_preferencesManager.ServerDataLoaded)
- {
- _loaded.Visible = false;
- _unloaded.Visible = true;
- }
- else
- {
- _loaded.Visible = true;
- _unloaded.Visible = false;
- if (_preferencesManager.Preferences?.SelectedCharacter is not HumanoidCharacterProfile selectedCharacter)
- {
- _summaryLabel.Text = string.Empty;
- }
- else
- {
- _previewDummy = _entityManager.SpawnEntity(_prototypeManager.Index(selectedCharacter.Species).DollPrototype, MapCoordinates.Nullspace);
- _viewBox.DisposeAllChildren();
- var spriteView = new SpriteView
- {
- OverrideDirection = Direction.South,
- Scale = new Vector2(4f, 4f),
- MaxSize = new Vector2(112, 112),
- Stretch = SpriteView.StretchMode.Fill,
- };
- spriteView.SetEntity(_previewDummy.Value);
- _viewBox.AddChild(spriteView);
- _summaryLabel.Text = selectedCharacter.Summary;
- _entityManager.System().LoadProfile(_previewDummy.Value, selectedCharacter);
- GiveDummyJobClothes(_previewDummy.Value, selectedCharacter);
- GiveDummyLoadoutItems(_previewDummy.Value, selectedCharacter);
- }
- }
- }
-
- public static void GiveDummyJobClothes(EntityUid dummy, HumanoidCharacterProfile profile)
- {
- var protoMan = IoCManager.Resolve();
- var entMan = IoCManager.Resolve();
- var invSystem = EntitySystem.Get();
-
- var highPriorityJob = profile.JobPriorities.FirstOrDefault(p => p.Value == JobPriority.High).Key;
-
- // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract (what is resharper smoking?)
- var job = protoMan.Index(highPriorityJob ?? SharedGameTicker.FallbackOverflowJob);
-
- if (job.StartingGear != null && invSystem.TryGetSlots(dummy, out var slots))
- {
- var gear = protoMan.Index(job.StartingGear);
-
- foreach (var slot in slots)
- {
- var itemType = gear.GetGear(slot.Name, profile);
-
- if (invSystem.TryUnequip(dummy, slot.Name, out var unequippedItem, silent: true, force: true, reparent: false))
- {
- entMan.DeleteEntity(unequippedItem.Value);
- }
-
- if (itemType != string.Empty)
- {
- var item = entMan.SpawnEntity(itemType, MapCoordinates.Nullspace);
- invSystem.TryEquip(dummy, item, slot.Name, true, true);
- }
- }
- }
- }
-
- public static void GiveDummyLoadoutItems(EntityUid dummy, HumanoidCharacterProfile profile)
- {
- var highPriorityJobId = profile.JobPriorities.FirstOrDefault(j => j.Value == JobPriority.High).Key;
- var highPriorityJob = IoCManager.Resolve().Index(highPriorityJobId ?? SharedGameTicker.FallbackOverflowJob);
-
- EntitySystem.Get().ApplyCharacterLoadout(dummy, highPriorityJob, profile);
- }
- }
-}
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml b/Content.Client/Lobby/UI/LobbyGui.xaml
index c3bd0da642..ea9c63d6cd 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml
@@ -94,7 +94,7 @@
-
+
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
index 69867ea90c..5a0b580262 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
@@ -1,23 +1,9 @@
-using Content.Client.Chat.UI;
-using Content.Client.Info;
using Content.Client.Message;
-using Content.Client.Preferences;
-using Content.Client.Preferences.UI;
-using Content.Client.UserInterface.Screens;
-using Content.Client.UserInterface.Systems.Chat.Widgets;
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
-using Robust.Client.Graphics;
-using Robust.Client.State;
using Robust.Client.UserInterface;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Prototypes;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Content.Client.Lobby.UI
{
diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml b/Content.Client/Materials/UI/MaterialStorageControl.xaml
index e504434649..2be0f40aa5 100644
--- a/Content.Client/Materials/UI/MaterialStorageControl.xaml
+++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml
@@ -1,7 +1,8 @@
-
-
-
+
+
+
+
diff --git a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
index 3ef247d529..31d99624a8 100644
--- a/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
+++ b/Content.Client/Materials/UI/MaterialStorageControl.xaml.cs
@@ -11,7 +11,7 @@ namespace Content.Client.Materials.UI;
/// This widget is one row in the lathe eject menu.
///
[GenerateTypedNameReferences]
-public sealed partial class MaterialStorageControl : BoxContainer
+public sealed partial class MaterialStorageControl : ScrollContainer
{
[Dependency] private readonly IEntityManager _entityManager = default!;
@@ -63,7 +63,7 @@ protected override void FrameUpdate(FrameEventArgs args)
}
var children = new List();
- children.AddRange(Children.OfType());
+ children.AddRange(MaterialList.Children.OfType());
foreach (var display in children)
{
@@ -71,7 +71,7 @@ protected override void FrameUpdate(FrameEventArgs args)
if (extra.Contains(mat))
{
- RemoveChild(display);
+ MaterialList.RemoveChild(display);
continue;
}
@@ -83,7 +83,7 @@ protected override void FrameUpdate(FrameEventArgs args)
foreach (var mat in missing)
{
var volume = mats[mat];
- AddChild(new MaterialDisplay(_owner.Value, mat, volume, canEject));
+ MaterialList.AddChild(new MaterialDisplay(_owner.Value, mat, volume, canEject));
}
_currentMaterials = mats;
diff --git a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
index 39326c8a99..863412e553 100644
--- a/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
+++ b/Content.Client/Medical/CrewMonitoring/CrewMonitoringWindow.xaml.cs
@@ -210,9 +210,9 @@ private void PopulateDepartmentList(IEnumerable departmentSens
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "dead");
}
- else if (sensor.TotalDamage != null)
+ else if (sensor.DamagePercentage != null)
{
- var index = MathF.Round(4f * (sensor.TotalDamage.Value / 100f));
+ var index = MathF.Round(4f * sensor.DamagePercentage.Value);
if (index >= 5)
specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Alerts/human_crew_monitoring.rsi"), "critical");
diff --git a/Content.Client/Movement/Systems/JetpackSystem.cs b/Content.Client/Movement/Systems/JetpackSystem.cs
index f0836ee9b6..b7f5e48821 100644
--- a/Content.Client/Movement/Systems/JetpackSystem.cs
+++ b/Content.Client/Movement/Systems/JetpackSystem.cs
@@ -4,6 +4,7 @@
using Content.Shared.Movement.Systems;
using Robust.Client.GameObjects;
using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
@@ -12,7 +13,6 @@ namespace Content.Client.Movement.Systems;
public sealed class JetpackSystem : SharedJetpackSystem
{
[Dependency] private readonly IGameTiming _timing = default!;
- [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ClothingSystem _clothing = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
@@ -75,7 +75,7 @@ private void CreateParticles(EntityUid uid)
var coordinates = uidXform.Coordinates;
var gridUid = coordinates.GetGridUid(EntityManager);
- if (_mapManager.TryGetGrid(gridUid, out var grid))
+ if (TryComp(gridUid, out var grid))
{
coordinates = new EntityCoordinates(gridUid.Value, grid.WorldToLocal(coordinates.ToMapPos(EntityManager, _transform)));
}
diff --git a/Content.Client/Movement/Systems/WaddleAnimationSystem.cs b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs
new file mode 100644
index 0000000000..9555c1f6b9
--- /dev/null
+++ b/Content.Client/Movement/Systems/WaddleAnimationSystem.cs
@@ -0,0 +1,173 @@
+using System.Numerics;
+using Content.Client.Buckle;
+using Content.Client.Gravity;
+using Content.Shared.ActionBlocker;
+using Content.Shared.Buckle.Components;
+using Content.Shared.Mobs.Systems;
+using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Events;
+using Content.Shared.StatusEffect;
+using Content.Shared.Stunnable;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
+using Robust.Shared.Animations;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Movement.Systems;
+
+public sealed class WaddleAnimationSystem : EntitySystem
+{
+ [Dependency] private readonly AnimationPlayerSystem _animation = default!;
+ [Dependency] private readonly GravitySystem _gravity = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
+ [Dependency] private readonly BuckleSystem _buckle = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnMovementInput);
+ SubscribeLocalEvent(OnStartedWalking);
+ SubscribeLocalEvent(OnStoppedWalking);
+ SubscribeLocalEvent(OnAnimationCompleted);
+ SubscribeLocalEvent(OnStunned);
+ SubscribeLocalEvent(OnKnockedDown);
+ SubscribeLocalEvent(OnBuckleChange);
+ }
+
+ private void OnMovementInput(EntityUid entity, WaddleAnimationComponent component, MoveInputEvent args)
+ {
+ // Prediction mitigation. Prediction means that MoveInputEvents are spammed repeatedly, even though you'd assume
+ // they're once-only for the user actually doing something. As such do nothing if we're just repeating this FoR.
+ if (!_timing.IsFirstTimePredicted)
+ {
+ return;
+ }
+
+ if (!args.HasDirectionalMovement && component.IsCurrentlyWaddling)
+ {
+ var stopped = new StoppedWaddlingEvent(entity);
+
+ RaiseLocalEvent(entity, ref stopped);
+
+ return;
+ }
+
+ // Only start waddling if we're not currently AND we're actually moving.
+ if (component.IsCurrentlyWaddling || !args.HasDirectionalMovement)
+ return;
+
+ var started = new StartedWaddlingEvent(entity);
+
+ RaiseLocalEvent(entity, ref started);
+ }
+
+ private void OnStartedWalking(EntityUid uid, WaddleAnimationComponent component, StartedWaddlingEvent args)
+ {
+ if (_animation.HasRunningAnimation(uid, component.KeyName))
+ return;
+
+ if (!TryComp(uid, out var mover))
+ return;
+
+ if (_gravity.IsWeightless(uid))
+ return;
+
+
+ if (!_actionBlocker.CanMove(uid, mover))
+ return;
+
+ // Do nothing if buckled in
+ if (_buckle.IsBuckled(uid))
+ return;
+
+ // Do nothing if crit or dead (for obvious reasons)
+ if (_mobState.IsIncapacitated(uid))
+ return;
+
+ var tumbleIntensity = component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
+ var len = mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
+
+ component.LastStep = !component.LastStep;
+ component.IsCurrentlyWaddling = true;
+
+ var anim = new Animation()
+ {
+ Length = TimeSpan.FromSeconds(len),
+ AnimationTracks =
+ {
+ new AnimationTrackComponentProperty()
+ {
+ ComponentType = typeof(SpriteComponent),
+ Property = nameof(SpriteComponent.Rotation),
+ InterpolationMode = AnimationInterpolationMode.Linear,
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
+ new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
+ }
+ },
+ new AnimationTrackComponentProperty()
+ {
+ ComponentType = typeof(SpriteComponent),
+ Property = nameof(SpriteComponent.Offset),
+ InterpolationMode = AnimationInterpolationMode.Linear,
+ KeyFrames =
+ {
+ new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
+ new AnimationTrackProperty.KeyFrame(component.HopIntensity, len/2),
+ new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
+ }
+ }
+ }
+ };
+
+ _animation.Play(uid, anim, component.KeyName);
+ }
+
+ private void OnStoppedWalking(EntityUid uid, WaddleAnimationComponent component, StoppedWaddlingEvent args)
+ {
+ StopWaddling(uid, component);
+ }
+
+ private void OnAnimationCompleted(EntityUid uid, WaddleAnimationComponent component, AnimationCompletedEvent args)
+ {
+ var started = new StartedWaddlingEvent(uid);
+
+ RaiseLocalEvent(uid, ref started);
+ }
+
+ private void OnStunned(EntityUid uid, WaddleAnimationComponent component, StunnedEvent args)
+ {
+ StopWaddling(uid, component);
+ }
+
+ private void OnKnockedDown(EntityUid uid, WaddleAnimationComponent component, KnockedDownEvent args)
+ {
+ StopWaddling(uid, component);
+ }
+
+ private void OnBuckleChange(EntityUid uid, WaddleAnimationComponent component, BuckleChangeEvent args)
+ {
+ StopWaddling(uid, component);
+ }
+
+ private void StopWaddling(EntityUid uid, WaddleAnimationComponent component)
+ {
+ if (!component.IsCurrentlyWaddling)
+ return;
+
+ _animation.Stop(uid, component.KeyName);
+
+ if (!TryComp(uid, out var sprite))
+ {
+ return;
+ }
+
+ sprite.Offset = new Vector2();
+ sprite.Rotation = Angle.FromDegrees(0);
+
+ component.IsCurrentlyWaddling = false;
+ }
+}
diff --git a/Content.Client/NPC/PathfindingSystem.cs b/Content.Client/NPC/PathfindingSystem.cs
index 7bf3df1f0b..709601a57b 100644
--- a/Content.Client/NPC/PathfindingSystem.cs
+++ b/Content.Client/NPC/PathfindingSystem.cs
@@ -289,7 +289,6 @@ private void DrawScreen(OverlayDrawArgs args, DrawingHandleScreen screenHandle)
var invGridMatrix = gridXform.InvWorldMatrix;
DebugPathPoly? nearest = null;
- var nearestDistance = float.MaxValue;
foreach (var poly in tile)
{
diff --git a/Content.Client/NodeContainer/NodeVisualizationOverlay.cs b/Content.Client/NodeContainer/NodeVisualizationOverlay.cs
index f10eb9ed8b..691bcb41db 100644
--- a/Content.Client/NodeContainer/NodeVisualizationOverlay.cs
+++ b/Content.Client/NodeContainer/NodeVisualizationOverlay.cs
@@ -80,7 +80,7 @@ private void DrawScreen(in OverlayDrawArgs args)
var xform = _entityManager.GetComponent(_entityManager.GetEntity(node.Entity));
- if (!_mapManager.TryGetGrid(xform.GridUid, out var grid))
+ if (!_entityManager.TryGetComponent(xform.GridUid, out var grid))
return;
var gridTile = grid.TileIndicesFor(xform.Coordinates);
@@ -145,7 +145,7 @@ private void DrawWorld(in OverlayDrawArgs overlayDrawArgs)
foreach (var (gridId, gridDict) in _gridIndex)
{
- var grid = _mapManager.GetGrid(gridId);
+ var grid = _entityManager.GetComponent(gridId);
var (_, _, worldMatrix, invMatrix) = _entityManager.GetComponent(gridId).GetWorldPositionRotationMatrixWithInv();
var lCursorBox = invMatrix.TransformBox(cursorBox);
diff --git a/Content.Client/Nutrition/EntitySystems/OpenableSystem.cs b/Content.Client/Nutrition/EntitySystems/OpenableSystem.cs
deleted file mode 100644
index f8c3f7c447..0000000000
--- a/Content.Client/Nutrition/EntitySystems/OpenableSystem.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-using Content.Shared.Nutrition.EntitySystems;
-
-namespace Content.Client.Nutrition.EntitySystems;
-
-public sealed class OpenableSystem : SharedOpenableSystem
-{
-}
diff --git a/Content.Client/OfferItem/OfferItemIndicatorsOverlay.cs b/Content.Client/OfferItem/OfferItemIndicatorsOverlay.cs
new file mode 100644
index 0000000000..16a314a2cf
--- /dev/null
+++ b/Content.Client/OfferItem/OfferItemIndicatorsOverlay.cs
@@ -0,0 +1,72 @@
+using System.Numerics;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Client.UserInterface;
+using Robust.Shared.Enums;
+using Robust.Shared.Utility;
+
+namespace Content.Client.OfferItem;
+
+public sealed class OfferItemIndicatorsOverlay : Overlay
+{
+ private readonly IInputManager _inputManager;
+ private readonly IEntityManager _entMan;
+ private readonly IEyeManager _eye;
+ private readonly OfferItemSystem _offer;
+
+ private readonly Texture _sight;
+
+ public override OverlaySpace Space => OverlaySpace.ScreenSpace;
+
+ private readonly Color _mainColor = Color.White.WithAlpha(0.3f);
+ private readonly Color _strokeColor = Color.Black.WithAlpha(0.5f);
+ private readonly float _scale = 0.6f; // 1 is a little big
+
+ public OfferItemIndicatorsOverlay(IInputManager input, IEntityManager entMan,
+ IEyeManager eye, OfferItemSystem offerSys)
+ {
+ _inputManager = input;
+ _entMan = entMan;
+ _eye = eye;
+ _offer = offerSys;
+
+ var spriteSys = _entMan.EntitySysManager.GetEntitySystem();
+ _sight = spriteSys.Frame0(new SpriteSpecifier.Rsi(new ResPath("/Textures/Interface/Misc/give_item.rsi"),
+ "give_item"));
+ }
+
+ protected override bool BeforeDraw(in OverlayDrawArgs args)
+ {
+ if (!_offer.IsInOfferMode())
+ return false;
+
+ return base.BeforeDraw(in args);
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ var mouseScreenPosition = _inputManager.MouseScreenPosition;
+ var mousePosMap = _eye.PixelToMap(mouseScreenPosition);
+ if (mousePosMap.MapId != args.MapId)
+ return;
+
+
+ var mousePos = mouseScreenPosition.Position;
+ var uiScale = (args.ViewportControl as Control)?.UIScale ?? 1f;
+ var limitedScale = uiScale > 1.25f ? 1.25f : uiScale;
+
+ DrawSight(_sight, args.ScreenHandle, mousePos, limitedScale * _scale);
+ }
+
+ private void DrawSight(Texture sight, DrawingHandleScreen screen, Vector2 centerPos, float scale)
+ {
+ var sightSize = sight.Size * scale;
+ var expandedSize = sightSize + new Vector2(7f, 7f);
+
+ screen.DrawTextureRect(sight,
+ UIBox2.FromDimensions(centerPos - sightSize * 0.5f, sightSize), _strokeColor);
+ screen.DrawTextureRect(sight,
+ UIBox2.FromDimensions(centerPos - expandedSize * 0.5f, expandedSize), _mainColor);
+ }
+}
diff --git a/Content.Client/OfferItem/OfferItemSystem.cs b/Content.Client/OfferItem/OfferItemSystem.cs
new file mode 100644
index 0000000000..51b8dcbc0b
--- /dev/null
+++ b/Content.Client/OfferItem/OfferItemSystem.cs
@@ -0,0 +1,51 @@
+using Content.Shared.CCVar;
+using Content.Shared.OfferItem;
+using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Client.Player;
+using Robust.Shared.Configuration;
+
+namespace Content.Client.OfferItem;
+
+public sealed class OfferItemSystem : SharedOfferItemSystem
+{
+ [Dependency] private readonly IOverlayManager _overlayManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly IInputManager _inputManager = default!;
+ [Dependency] private readonly IEyeManager _eye = default!;
+
+ public override void Initialize()
+ {
+ Subs.CVar(_cfg, CCVars.OfferModeIndicatorsPointShow, OnShowOfferIndicatorsChanged, true);
+ }
+ public override void Shutdown()
+ {
+ _overlayManager.RemoveOverlay();
+
+ base.Shutdown();
+ }
+
+ public bool IsInOfferMode()
+ {
+ var entity = _playerManager.LocalEntity;
+
+ if (entity == null)
+ return false;
+
+ return IsInOfferMode(entity.Value);
+ }
+ private void OnShowOfferIndicatorsChanged(bool isShow)
+ {
+ if (isShow)
+ {
+ _overlayManager.AddOverlay(new OfferItemIndicatorsOverlay(
+ _inputManager,
+ EntityManager,
+ _eye,
+ this));
+ }
+ else
+ _overlayManager.RemoveOverlay();
+ }
+}
diff --git a/Content.Client/Options/UI/Tabs/AudioTab.xaml b/Content.Client/Options/UI/Tabs/AudioTab.xaml
index e54b0dc34e..8dd723d446 100644
--- a/Content.Client/Options/UI/Tabs/AudioTab.xaml
+++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml
@@ -100,6 +100,19 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs b/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
index 470ca7d799..0207ed5c47 100644
--- a/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/AudioTab.xaml.cs
@@ -37,6 +37,7 @@ public AudioTab()
AmbienceSoundsSlider.OnValueChanged += OnAmbienceSoundsSliderChanged;
LobbyVolumeSlider.OnValueChanged += OnLobbyVolumeSliderChanged;
InterfaceVolumeSlider.OnValueChanged += OnInterfaceVolumeSliderChanged;
+ AnnouncerVolumeSlider.OnValueChanged += OnAnnouncerVolumeSliderChanged;
LobbyMusicCheckBox.OnToggled += OnLobbyMusicCheckToggled;
RestartSoundsCheckBox.OnToggled += OnRestartSoundsCheckToggled;
EventMusicCheckBox.OnToggled += OnEventMusicCheckToggled;
@@ -58,6 +59,7 @@ protected override void Dispose(bool disposing)
AmbienceVolumeSlider.OnValueChanged -= OnAmbienceVolumeSliderChanged;
LobbyVolumeSlider.OnValueChanged -= OnLobbyVolumeSliderChanged;
InterfaceVolumeSlider.OnValueChanged -= OnInterfaceVolumeSliderChanged;
+ AnnouncerVolumeSlider.OnValueChanged -= OnAnnouncerVolumeSliderChanged;
base.Dispose(disposing);
}
@@ -97,6 +99,11 @@ private void OnMidiVolumeSliderChanged(Range range)
UpdateChanges();
}
+ private void OnAnnouncerVolumeSliderChanged(Range range)
+ {
+ UpdateChanges();
+ }
+
private void OnLobbyMusicCheckToggled(BaseButton.ButtonEventArgs args)
{
UpdateChanges();
@@ -125,6 +132,7 @@ private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
_cfg.SetCVar(CCVars.AmbientMusicVolume, AmbientMusicVolumeSlider.Value / 100f * ContentAudioSystem.AmbientMusicMultiplier);
_cfg.SetCVar(CCVars.LobbyMusicVolume, LobbyVolumeSlider.Value / 100f * ContentAudioSystem.LobbyMultiplier);
_cfg.SetCVar(CCVars.InterfaceVolume, InterfaceVolumeSlider.Value / 100f * ContentAudioSystem.InterfaceMultiplier);
+ _cfg.SetCVar(CCVars.AnnouncerVolume, AnnouncerVolumeSlider.Value / 100f * ContentAudioSystem.AnnouncerMultiplier);
_cfg.SetCVar(CCVars.MaxAmbientSources, (int)AmbienceSoundsSlider.Value);
@@ -149,6 +157,7 @@ private void Reset()
AmbientMusicVolumeSlider.Value = _cfg.GetCVar(CCVars.AmbientMusicVolume) * 100f / ContentAudioSystem.AmbientMusicMultiplier;
LobbyVolumeSlider.Value = _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier;
InterfaceVolumeSlider.Value = _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier;
+ AnnouncerVolumeSlider.Value = _cfg.GetCVar(CCVars.AnnouncerVolume) * 100f / ContentAudioSystem.AnnouncerMultiplier;
AmbienceSoundsSlider.Value = _cfg.GetCVar(CCVars.MaxAmbientSources);
@@ -174,14 +183,17 @@ private void UpdateChanges()
Math.Abs(LobbyVolumeSlider.Value - _cfg.GetCVar(CCVars.LobbyMusicVolume) * 100f / ContentAudioSystem.LobbyMultiplier) < 0.01f;
var isInterfaceVolumeSame =
Math.Abs(InterfaceVolumeSlider.Value - _cfg.GetCVar(CCVars.InterfaceVolume) * 100f / ContentAudioSystem.InterfaceMultiplier) < 0.01f;
+ var isAnnouncerVolumeSame =
+ Math.Abs(AnnouncerVolumeSlider.Value - _cfg.GetCVar(CCVars.AnnouncerVolume) * 100f / ContentAudioSystem.AnnouncerMultiplier) < 0.01f;
var isAmbientSoundsSame = (int)AmbienceSoundsSlider.Value == _cfg.GetCVar(CCVars.MaxAmbientSources);
var isLobbySame = LobbyMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.LobbyMusicEnabled);
var isRestartSoundsSame = RestartSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.RestartSoundsEnabled);
var isEventSame = EventMusicCheckBox.Pressed == _cfg.GetCVar(CCVars.EventMusicEnabled);
var isAdminSoundsSame = AdminSoundsCheckBox.Pressed == _cfg.GetCVar(CCVars.AdminSoundsEnabled);
- var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame && isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
- && isAdminSoundsSame && isLobbyVolumeSame && isInterfaceVolumeSame;
+ var isEverythingSame = isMasterVolumeSame && isMidiVolumeSame && isAmbientVolumeSame
+ && isAmbientMusicVolumeSame && isAmbientSoundsSame && isLobbySame && isRestartSoundsSame && isEventSame
+ && isAdminSoundsSame && isLobbyVolumeSame && isInterfaceVolumeSame && isAnnouncerVolumeSame;
ApplyButton.Disabled = isEverythingSame;
ResetButton.Disabled = isEverythingSame;
MasterVolumeLabel.Text =
@@ -196,6 +208,8 @@ private void UpdateChanges()
Loc.GetString("ui-options-volume-percent", ("volume", LobbyVolumeSlider.Value / 100));
InterfaceVolumeLabel.Text =
Loc.GetString("ui-options-volume-percent", ("volume", InterfaceVolumeSlider.Value / 100));
+ AnnouncerVolumeLabel.Text =
+ Loc.GetString("ui-options-volume-percent", ("volume", AnnouncerVolumeSlider.Value / 100));
AmbienceSoundsLabel.Text = ((int)AmbienceSoundsSlider.Value).ToString();
}
}
diff --git a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
index f0537079b9..13e456985a 100644
--- a/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/KeyRebindTab.xaml.cs
@@ -183,6 +183,9 @@ void AddCheckBox(string checkBoxName, bool currentState, Action
+
+
+
+
+
+
@@ -49,6 +58,7 @@
StyleClasses="LabelKeyText"/>
+
@@ -65,6 +75,3 @@
-
-
-
diff --git a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
index 3b9c41efdf..70c4f48932 100644
--- a/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
+++ b/Content.Client/Options/UI/Tabs/MiscTab.xaml.cs
@@ -3,11 +3,14 @@
using Content.Shared.CCVar;
using Content.Shared.HUD;
using Robust.Client.AutoGenerated;
+using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared;
using Robust.Shared.Configuration;
+using Robust.Shared.Network;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Range = Robust.Client.UserInterface.Controls.Range;
@@ -16,6 +19,7 @@ namespace Content.Client.Options.UI.Tabs
[GenerateTypedNameReferences]
public sealed partial class MiscTab : Control
{
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -55,32 +59,42 @@ public MiscTab()
UpdateApplyButton();
};
+ // Channel can be null in replays so.
+ // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
+ ShowOocPatronColor.Visible = _playerManager.LocalSession?.Channel?.UserData.PatronTier is { };
+
HudThemeOption.OnItemSelected += OnHudThemeChanged;
DiscordRich.OnToggled += OnCheckBoxToggled;
+ ShowOocPatronColor.OnToggled += OnCheckBoxToggled;
ShowLoocAboveHeadCheckBox.OnToggled += OnCheckBoxToggled;
ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled;
ShowCombatModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
+ ShowOfferModeIndicatorsCheckBox.OnToggled += OnCheckBoxToggled;
OpaqueStorageWindowCheckBox.OnToggled += OnCheckBoxToggled;
FancySpeechBubblesCheckBox.OnToggled += OnCheckBoxToggled;
FancyNameBackgroundsCheckBox.OnToggled += OnCheckBoxToggled;
EnableColorNameCheckBox.OnToggled += OnCheckBoxToggled;
ColorblindFriendlyCheckBox.OnToggled += OnCheckBoxToggled;
ReducedMotionCheckBox.OnToggled += OnCheckBoxToggled;
+ ChatWindowOpacitySlider.OnValueChanged += OnChatWindowOpacitySliderChanged;
ScreenShakeIntensitySlider.OnValueChanged += OnScreenShakeIntensitySliderChanged;
// ToggleWalk.OnToggled += OnCheckBoxToggled;
StaticStorageUI.OnToggled += OnCheckBoxToggled;
HudThemeOption.SelectId(_hudThemeIdToIndex.GetValueOrDefault(_cfg.GetCVar(CVars.InterfaceTheme), 0));
DiscordRich.Pressed = _cfg.GetCVar(CVars.DiscordEnabled);
+ ShowOocPatronColor.Pressed = _cfg.GetCVar(CCVars.ShowOocPatronColor);
ShowLoocAboveHeadCheckBox.Pressed = _cfg.GetCVar(CCVars.LoocAboveHeadShow);
ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow);
ShowCombatModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
+ ShowOfferModeIndicatorsCheckBox.Pressed = _cfg.GetCVar(CCVars.OfferModeIndicatorsPointShow);
OpaqueStorageWindowCheckBox.Pressed = _cfg.GetCVar(CCVars.OpaqueStorageWindow);
FancySpeechBubblesCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
FancyNameBackgroundsCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatFancyNameBackground);
EnableColorNameCheckBox.Pressed = _cfg.GetCVar(CCVars.ChatEnableColorName);
ColorblindFriendlyCheckBox.Pressed = _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
ReducedMotionCheckBox.Pressed = _cfg.GetCVar(CCVars.ReducedMotion);
+ ChatWindowOpacitySlider.Value = _cfg.GetCVar(CCVars.ChatWindowOpacity);
ScreenShakeIntensitySlider.Value = _cfg.GetCVar(CCVars.ScreenShakeIntensity) * 100f;
// ToggleWalk.Pressed = _cfg.GetCVar(CCVars.ToggleWalk);
StaticStorageUI.Pressed = _cfg.GetCVar(CCVars.StaticStorageUI);
@@ -101,6 +115,13 @@ private void OnHudThemeChanged(OptionButton.ItemSelectedEventArgs args)
UpdateApplyButton();
}
+ private void OnChatWindowOpacitySliderChanged(Range range)
+ {
+ ChatWindowOpacityLabel.Text = Loc.GetString("ui-options-chat-window-opacity-percent",
+ ("opacity", range.Value));
+ UpdateApplyButton();
+ }
+
private void OnScreenShakeIntensitySliderChanged(Range obj)
{
ScreenShakeIntensityLabel.Text = Loc.GetString("ui-options-screen-shake-percent", ("intensity", ScreenShakeIntensitySlider.Value / 100f));
@@ -120,13 +141,16 @@ private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
_cfg.SetCVar(CVars.DiscordEnabled, DiscordRich.Pressed);
_cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed);
_cfg.SetCVar(CCVars.CombatModeIndicatorsPointShow, ShowCombatModeIndicatorsCheckBox.Pressed);
+ _cfg.SetCVar(CCVars.OfferModeIndicatorsPointShow, ShowOfferModeIndicatorsCheckBox.Pressed);
_cfg.SetCVar(CCVars.OpaqueStorageWindow, OpaqueStorageWindowCheckBox.Pressed);
+ _cfg.SetCVar(CCVars.ShowOocPatronColor, ShowOocPatronColor.Pressed);
_cfg.SetCVar(CCVars.LoocAboveHeadShow, ShowLoocAboveHeadCheckBox.Pressed);
_cfg.SetCVar(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox.Pressed);
_cfg.SetCVar(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox.Pressed);
_cfg.SetCVar(CCVars.ChatEnableColorName, EnableColorNameCheckBox.Pressed);
_cfg.SetCVar(CCVars.AccessibilityColorblindFriendly, ColorblindFriendlyCheckBox.Pressed);
_cfg.SetCVar(CCVars.ReducedMotion, ReducedMotionCheckBox.Pressed);
+ _cfg.SetCVar(CCVars.ChatWindowOpacity, ChatWindowOpacitySlider.Value);
_cfg.SetCVar(CCVars.ScreenShakeIntensity, ScreenShakeIntensitySlider.Value / 100f);
// _cfg.SetCVar(CCVars.ToggleWalk, ToggleWalk.Pressed);
_cfg.SetCVar(CCVars.StaticStorageUI, StaticStorageUI.Pressed);
@@ -147,13 +171,16 @@ private void UpdateApplyButton()
var isDiscordSame = DiscordRich.Pressed == _cfg.GetCVar(CVars.DiscordEnabled);
var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow);
var isCombatModeIndicatorsSame = ShowCombatModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.CombatModeIndicatorsPointShow);
+ var isOfferModeIndicatorsSame = ShowOfferModeIndicatorsCheckBox.Pressed == _cfg.GetCVar(CCVars.OfferModeIndicatorsPointShow);
var isOpaqueStorageWindow = OpaqueStorageWindowCheckBox.Pressed == _cfg.GetCVar(CCVars.OpaqueStorageWindow);
+ var isOocPatronColorShowSame = ShowOocPatronColor.Pressed == _cfg.GetCVar(CCVars.ShowOocPatronColor);
var isLoocShowSame = ShowLoocAboveHeadCheckBox.Pressed == _cfg.GetCVar(CCVars.LoocAboveHeadShow);
var isFancyChatSame = FancySpeechBubblesCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableFancyBubbles);
var isFancyBackgroundSame = FancyNameBackgroundsCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatFancyNameBackground);
var isEnableColorNameSame = EnableColorNameCheckBox.Pressed == _cfg.GetCVar(CCVars.ChatEnableColorName);
var isColorblindFriendly = ColorblindFriendlyCheckBox.Pressed == _cfg.GetCVar(CCVars.AccessibilityColorblindFriendly);
var isReducedMotionSame = ReducedMotionCheckBox.Pressed == _cfg.GetCVar(CCVars.ReducedMotion);
+ var isChatWindowOpacitySame = Math.Abs(ChatWindowOpacitySlider.Value - _cfg.GetCVar(CCVars.ChatWindowOpacity)) < 0.01f;
var isScreenShakeIntensitySame = Math.Abs(ScreenShakeIntensitySlider.Value / 100f - _cfg.GetCVar(CCVars.ScreenShakeIntensity)) < 0.01f;
// var isToggleWalkSame = ToggleWalk.Pressed == _cfg.GetCVar(CCVars.ToggleWalk);
var isStaticStorageUISame = StaticStorageUI.Pressed == _cfg.GetCVar(CCVars.StaticStorageUI);
@@ -163,13 +190,16 @@ private void UpdateApplyButton()
isDiscordSame &&
isShowHeldItemSame &&
isCombatModeIndicatorsSame &&
+ isOfferModeIndicatorsSame &&
isOpaqueStorageWindow &&
+ isOocPatronColorShowSame &&
isLoocShowSame &&
isFancyChatSame &&
isFancyBackgroundSame &&
isEnableColorNameSame &&
isColorblindFriendly &&
isReducedMotionSame &&
+ isChatWindowOpacitySame &&
isScreenShakeIntensitySame &&
// isToggleWalkSame &&
isStaticStorageUISame;
diff --git a/Content.Client/Overlays/EntityHealthBarOverlay.cs b/Content.Client/Overlays/EntityHealthBarOverlay.cs
index 9e562b5dd3..c1c0ae93ec 100644
--- a/Content.Client/Overlays/EntityHealthBarOverlay.cs
+++ b/Content.Client/Overlays/EntityHealthBarOverlay.cs
@@ -19,7 +19,6 @@ namespace Content.Client.Overlays;
///
public sealed class EntityHealthBarOverlay : Overlay
{
- [Dependency] private readonly IPrototypeManager _prototype = default!;
private readonly IEntityManager _entManager;
private readonly SharedTransformSystem _transform;
private readonly MobStateSystem _mobStateSystem;
@@ -27,17 +26,14 @@ public sealed class EntityHealthBarOverlay : Overlay
private readonly ProgressColorSystem _progressColor;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
public HashSet DamageContainers = new();
- private readonly ShaderInstance _shader;
public EntityHealthBarOverlay(IEntityManager entManager)
{
- IoCManager.InjectDependencies(this);
_entManager = entManager;
_transform = _entManager.System();
_mobStateSystem = _entManager.System();
_mobThresholdSystem = _entManager.System();
_progressColor = _entManager.System();
- _shader = _prototype.Index("unshaded").Instance();
}
protected override void Draw(in OverlayDrawArgs args)
@@ -50,8 +46,6 @@ protected override void Draw(in OverlayDrawArgs args)
var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale));
var rotationMatrix = Matrix3.CreateRotation(-rotation);
- handle.UseShader(_shader);
-
var query = _entManager.AllEntityQueryEnumerator();
while (query.MoveNext(out var uid,
out var mobThresholdsComponent,
@@ -122,7 +116,6 @@ protected override void Draw(in OverlayDrawArgs args)
handle.DrawRect(pixelDarken, Black.WithAlpha(128));
}
- handle.UseShader(null);
handle.SetTransform(Matrix3.Identity);
}
diff --git a/Content.Client/Paint/PaintVisualizerSystem.cs b/Content.Client/Paint/PaintVisualizerSystem.cs
deleted file mode 100644
index 6c99b2d35f..0000000000
--- a/Content.Client/Paint/PaintVisualizerSystem.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-using System.Linq;
-using Robust.Client.GameObjects;
-using static Robust.Client.GameObjects.SpriteComponent;
-using Content.Shared.Clothing;
-using Content.Shared.Hands;
-using Content.Shared.Paint;
-using Robust.Client.Graphics;
-using Robust.Shared.Prototypes;
-
-namespace Content.Client.Paint
-{
- public sealed class PaintedVisualizerSystem : VisualizerSystem
- {
- ///
- /// Visualizer for Paint which applies a shader and colors the entity.
- ///
-
- [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
- [Dependency] private readonly IPrototypeManager _protoMan = default!;
-
- public ShaderInstance? Shader; // in Robust.Client.Graphics so cannot move to shared component.
-
- public override void Initialize()
- {
- base.Initialize();
-
- SubscribeLocalEvent(OnHeldVisualsUpdated);
- SubscribeLocalEvent(OnShutdown);
- SubscribeLocalEvent(OnEquipmentVisualsUpdated);
- }
-
- protected override void OnAppearanceChange(EntityUid uid, PaintedComponent component, ref AppearanceChangeEvent args)
- {
- // ShaderPrototype sadly in Robust.Client, cannot move to shared component.
- Shader = _protoMan.Index(component.ShaderName).Instance();
-
- if (args.Sprite == null)
- return;
-
- if (!_appearance.TryGetData(uid, PaintVisuals.Painted, out bool isPainted))
- return;
-
- var sprite = args.Sprite;
-
-
- foreach (var spriteLayer in sprite.AllLayers)
- {
- if (spriteLayer is not Layer layer)
- continue;
-
- if (layer.Shader == null) // If shader isn't null we dont want to replace the original shader.
- {
- layer.Shader = Shader;
- layer.Color = component.Color;
- }
- }
- }
-
- private void OnHeldVisualsUpdated(EntityUid uid, PaintedComponent component, HeldVisualsUpdatedEvent args)
- {
- if (args.RevealedLayers.Count == 0)
- return;
-
- if (!TryComp(args.User, out SpriteComponent? sprite))
- return;
-
- foreach (var revealed in args.RevealedLayers)
- {
- if (!sprite.LayerMapTryGet(revealed, out var layer) || sprite[layer] is not Layer notlayer)
- continue;
-
- sprite.LayerSetShader(layer, component.ShaderName);
- sprite.LayerSetColor(layer, component.Color);
- }
- }
-
- private void OnEquipmentVisualsUpdated(EntityUid uid, PaintedComponent component, EquipmentVisualsUpdatedEvent args)
- {
- if (args.RevealedLayers.Count == 0)
- return;
-
- if (!TryComp(args.Equipee, out SpriteComponent? sprite))
- return;
-
- foreach (var revealed in args.RevealedLayers)
- {
- if (!sprite.LayerMapTryGet(revealed, out var layer) || sprite[layer] is not Layer notlayer)
- continue;
-
- sprite.LayerSetShader(layer, component.ShaderName);
- sprite.LayerSetColor(layer, component.Color);
- }
- }
-
- private void OnShutdown(EntityUid uid, PaintedComponent component, ref ComponentShutdown args)
- {
- if (!TryComp(uid, out SpriteComponent? sprite))
- return;
-
- component.BeforeColor = sprite.Color;
- Shader = _protoMan.Index(component.ShaderName).Instance();
-
- if (!Terminating(uid))
- {
- foreach (var spriteLayer in sprite.AllLayers)
- {
- if (spriteLayer is not Layer layer)
- continue;
-
- if (layer.Shader == Shader) // If shader isn't same as one in component we need to ignore it.
- {
- layer.Shader = null;
- if (layer.Color == component.Color) // If color isn't the same as one in component we don't want to change it.
- layer.Color = component.BeforeColor;
- }
- }
- }
- }
- }
-}
diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs
index 7a8a6e39cf..31042854d4 100644
--- a/Content.Client/Physics/Controllers/MoverController.cs
+++ b/Content.Client/Physics/Controllers/MoverController.cs
@@ -1,6 +1,7 @@
using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Systems;
-using Content.Shared.Pulling.Components;
+using Robust.Client.GameObjects;
using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Shared.Physics.Components;
@@ -24,7 +25,7 @@ public override void Initialize()
SubscribeLocalEvent(OnUpdatePredicted);
SubscribeLocalEvent(OnUpdateRelayTargetPredicted);
- SubscribeLocalEvent(OnUpdatePullablePredicted);
+ SubscribeLocalEvent(OnUpdatePullablePredicted);
}
private void OnUpdatePredicted(EntityUid uid, InputMoverComponent component, ref UpdateIsPredictedEvent args)
@@ -40,7 +41,7 @@ private void OnUpdateRelayTargetPredicted(EntityUid uid, MovementRelayTargetComp
args.IsPredicted = true;
}
- private void OnUpdatePullablePredicted(EntityUid uid, SharedPullableComponent component, ref UpdateIsPredictedEvent args)
+ private void OnUpdatePullablePredicted(EntityUid uid, PullableComponent component, ref UpdateIsPredictedEvent args)
{
// Enable prediction if an entity is being pulled by the player.
// Disable prediction if an entity is being pulled by some non-player entity.
diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs
index a8ec7b37a0..677092e191 100644
--- a/Content.Client/Pinpointer/UI/NavMapControl.cs
+++ b/Content.Client/Pinpointer/UI/NavMapControl.cs
@@ -114,9 +114,16 @@ public NavMapControl() : base(MinDisplayedRange, MaxDisplayedRange, DefaultDispl
VerticalExpand = false,
Children =
{
- _zoom,
- _beacons,
- _recenter,
+ new BoxContainer()
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ Children =
+ {
+ _zoom,
+ _beacons,
+ _recenter
+ }
+ }
}
};
diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
index 7688a3b3aa..a2f8061d05 100644
--- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
+++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
@@ -1,5 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.CCVar;
+using Content.Shared.Customization.Systems;
using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Roles;
@@ -7,21 +8,19 @@
using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client.Players.PlayTimeTracking;
-public sealed partial class JobRequirementsManager
+public sealed partial class JobRequirementsManager : ISharedPlaytimeManager
{
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClientNetManager _net = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
- [Dependency] private readonly IEntityManager _entManager = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
- public readonly Dictionary PlayTimes = new();
+ private readonly Dictionary _roles = new();
private readonly List _roleBans = new();
private ISawmill _sawmill = default!;
@@ -45,7 +44,7 @@ private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e)
if (e.NewLevel == ClientRunLevel.Initialize)
{
// Reset on disconnect, just in case.
- PlayTimes.Clear();
+ _roles.Clear();
}
}
@@ -63,12 +62,12 @@ private void RxRoleBans(MsgRoleBans message)
private void RxPlayTime(MsgPlayTime message)
{
- PlayTimes.Clear();
+ _roles.Clear();
// NOTE: do not assign _roles = message.Trackers due to implicit data sharing in integration tests.
foreach (var (tracker, time) in message.Trackers)
{
- PlayTimes[tracker] = time;
+ _roles[tracker] = time;
}
/*var sawmill = Logger.GetSawmill("play_time");
@@ -79,60 +78,33 @@ private void RxPlayTime(MsgPlayTime message)
Updated?.Invoke();
}
- public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out FormattedMessage? reason)
+ public TimeSpan FetchOverallPlaytime()
{
- reason = null;
-
- if (_roleBans.Contains($"Job:{job.ID}"))
- {
- reason = FormattedMessage.FromUnformatted(Loc.GetString("role-ban"));
- return false;
- }
-
- var player = _playerManager.LocalSession;
- if (player == null)
- return true;
-
- return CheckRoleTime(job.Requirements, out reason);
+ return _roles.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
}
- public bool CheckRoleTime(HashSet? requirements, [NotNullWhen(false)] out FormattedMessage? reason, string? localePrefix = null)
+ public Dictionary FetchPlaytimeByRoles()
{
- reason = null;
-
- if (requirements == null || !_cfg.GetCVar(CCVars.GameRoleTimers))
- return true;
-
- var reasons = new List();
- foreach (var requirement in requirements)
- {
- if (JobRequirements.TryRequirementMet(requirement, PlayTimes, out var jobReason, _entManager, _prototypes, _whitelisted, localePrefix))
- continue;
+ var jobsToMap = _prototypes.EnumeratePrototypes();
+ var ret = new Dictionary();
- reasons.Add(jobReason.ToMarkup());
- }
+ foreach (var job in jobsToMap)
+ if (_roles.TryGetValue(job.PlayTimeTracker, out var locJobName))
+ ret.Add(job.Name, locJobName);
- reason = reasons.Count == 0 ? null : FormattedMessage.FromMarkup(string.Join('\n', reasons));
- return reason == null;
+ return ret;
}
- public TimeSpan FetchOverallPlaytime()
+
+ public Dictionary GetPlayTimes()
{
- return PlayTimes.TryGetValue("Overall", out var overallPlaytime) ? overallPlaytime : TimeSpan.Zero;
+ var dict = FetchPlaytimeByRoles();
+ dict.Add(PlayTimeTrackingShared.TrackerOverall, FetchOverallPlaytime());
+ return dict;
}
- public IEnumerable> FetchPlaytimeByRoles()
+ public Dictionary GetRawPlayTimeTrackers()
{
- var jobsToMap = _prototypes.EnumeratePrototypes();
-
- foreach (var job in jobsToMap)
- {
- if (PlayTimes.TryGetValue(job.PlayTimeTracker, out var locJobName))
- {
- yield return new KeyValuePair(job.Name, locJobName);
- }
- }
+ return _roles;
}
-
-
}
diff --git a/Content.Client/Popups/PopupSystem.cs b/Content.Client/Popups/PopupSystem.cs
index 479fb02906..3faa392e58 100644
--- a/Content.Client/Popups/PopupSystem.cs
+++ b/Content.Client/Popups/PopupSystem.cs
@@ -163,10 +163,13 @@ public override void PopupEntity(string? message, EntityUid uid, Filter filter,
PopupEntity(message, uid, type);
}
- public override void PopupClient(string? message, EntityUid uid, EntityUid recipient, PopupType type = PopupType.Small)
+ public override void PopupClient(string? message, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
{
+ if (recipient == null)
+ return;
+
if (_timing.IsFirstTimePredicted)
- PopupEntity(message, uid, recipient, type);
+ PopupEntity(message, uid, recipient.Value, type);
}
public override void PopupEntity(string? message, EntityUid uid, PopupType type = PopupType.Small)
@@ -181,6 +184,12 @@ public override void PopupPredicted(string? message, EntityUid uid, EntityUid? r
PopupEntity(message, uid, recipient.Value, type);
}
+ public override void PopupPredicted(string? recipientMessage, string? othersMessage, EntityUid uid, EntityUid? recipient, PopupType type = PopupType.Small)
+ {
+ if (recipient != null && _timing.IsFirstTimePredicted)
+ PopupEntity(recipientMessage, uid, recipient.Value, type);
+ }
+
#endregion
#region Network Event Handlers
diff --git a/Content.Client/Preferences/ClientPreferencesManager.cs b/Content.Client/Preferences/ClientPreferencesManager.cs
index b518493c9d..aca7159504 100644
--- a/Content.Client/Preferences/ClientPreferencesManager.cs
+++ b/Content.Client/Preferences/ClientPreferencesManager.cs
@@ -3,6 +3,7 @@
using System.Linq;
using Content.Shared.Preferences;
using Robust.Client;
+using Robust.Client.Player;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Network;
@@ -22,6 +23,7 @@ public sealed class ClientPreferencesManager : IClientPreferencesManager
[Dependency] private readonly IBaseClient _baseClient = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
public event Action? OnServerDataLoaded;
@@ -64,7 +66,8 @@ public void SelectCharacter(int slot)
public void UpdateCharacter(ICharacterProfile profile, int slot)
{
- profile.EnsureValid(_cfg, _prototypes);
+ var collection = IoCManager.Instance!;
+ profile.EnsureValid(_playerManager.LocalSession!, collection);
var characters = new Dictionary(Preferences.Characters) {[slot] = profile};
Preferences = new PlayerPreferences(characters, Preferences.SelectedCharacterIndex, Preferences.AdminOOCColor);
var msg = new MsgUpdateCharacter
diff --git a/Content.Client/Preferences/UI/AntagPreferenceSelector.cs b/Content.Client/Preferences/UI/AntagPreferenceSelector.cs
new file mode 100644
index 0000000000..4a339d3f65
--- /dev/null
+++ b/Content.Client/Preferences/UI/AntagPreferenceSelector.cs
@@ -0,0 +1,56 @@
+using Content.Client.Players.PlayTimeTracking;
+using Content.Shared.Customization.Systems;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Robust.Shared.Configuration;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Preferences.UI;
+
+public sealed class AntagPreferenceSelector : RequirementsSelector
+{
+ // 0 is yes and 1 is no
+ public bool Preference
+ {
+ get => Options.SelectedValue == 0;
+ set => Options.Select(value && !Disabled ? 0 : 1);
+ }
+
+ public event Action? PreferenceChanged;
+
+ public AntagPreferenceSelector(AntagPrototype proto, JobPrototype highJob) : base(proto, highJob)
+ {
+ Options.OnItemSelected += _ => PreferenceChanged?.Invoke(Preference);
+
+ var items = new[]
+ {
+ ("humanoid-profile-editor-antag-preference-yes-button", 0),
+ ("humanoid-profile-editor-antag-preference-no-button", 1),
+ };
+ var title = Loc.GetString(proto.Name);
+ var description = Loc.GetString(proto.Objective);
+ Setup(items, title, 250, description);
+
+ // Immediately lock requirements if they aren't met.
+ // Another function checks Disabled after creating the selector so this has to be done now
+ var requirements = IoCManager.Resolve();
+ var prefs = IoCManager.Resolve();
+ var entMan = IoCManager.Resolve();
+ var characterReqs = entMan.System();
+ var protoMan = IoCManager.Resolve();
+ var configMan = IoCManager.Resolve();
+
+ if (proto.Requirements != null
+ && !characterReqs.CheckRequirementsValid(
+ proto.Requirements,
+ highJob,
+ (HumanoidCharacterProfile) (prefs.Preferences?.SelectedCharacter ?? HumanoidCharacterProfile.DefaultWithSpecies()),
+ requirements.GetRawPlayTimeTrackers(),
+ requirements.IsWhitelisted(),
+ entMan,
+ protoMan,
+ configMan,
+ out var reasons))
+ LockRequirements(characterReqs.GetRequirementsText(reasons));
+ }
+}
diff --git a/Content.Client/Preferences/UI/CharacterSetupGui.xaml b/Content.Client/Preferences/UI/CharacterSetupGui.xaml
index 9a76029ce0..35067eebfd 100644
--- a/Content.Client/Preferences/UI/CharacterSetupGui.xaml
+++ b/Content.Client/Preferences/UI/CharacterSetupGui.xaml
@@ -40,7 +40,7 @@
-
+
diff --git a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs
index dba96d74b6..5165db5479 100644
--- a/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs
+++ b/Content.Client/Preferences/UI/CharacterSetupGui.xaml.cs
@@ -3,27 +3,23 @@
using Content.Client.Humanoid;
using Content.Client.Info;
using Content.Client.Info.PlaytimeStats;
+using Content.Client.Lobby;
using Content.Client.Lobby.UI;
using Content.Client.Resources;
using Content.Client.Stylesheets;
+using Content.Shared.Clothing.Loadouts.Systems;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
using Robust.Shared.Map;
-using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using Direction = Robust.Shared.Maths.Direction;
@@ -36,7 +32,6 @@ public sealed partial class CharacterSetupGui : Control
private readonly IClientPreferencesManager _preferencesManager;
private readonly IEntityManager _entityManager;
private readonly IPrototypeManager _prototypeManager;
- private readonly IConfigurationManager _configurationManager;
private readonly Button _createNewCharacterButton;
private readonly HumanoidProfileEditor _humanoidProfileEditor;
@@ -51,7 +46,6 @@ public CharacterSetupGui(
_entityManager = entityManager;
_prototypeManager = prototypeManager;
_preferencesManager = preferencesManager;
- _configurationManager = configurationManager;
var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
@@ -74,7 +68,7 @@ public CharacterSetupGui(
args.Event.Handle();
};
- _humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, entityManager, configurationManager);
+ _humanoidProfileEditor = new HumanoidProfileEditor(preferencesManager, prototypeManager, configurationManager);
_humanoidProfileEditor.OnProfileChanged += ProfileChanged;
CharEditor.AddChild(_humanoidProfileEditor);
@@ -105,14 +99,13 @@ private void ProfileChanged(ICharacterProfile profile, int profileSlot)
private void UpdateUI()
{
+ UserInterfaceManager.GetUIController().UpdateCharacterUI();
var numberOfFullSlots = 0;
var characterButtonsGroup = new ButtonGroup();
Characters.RemoveAllChildren();
if (!_preferencesManager.ServerDataLoaded)
- {
return;
- }
_createNewCharacterButton.ToolTip =
Loc.GetString("character-setup-gui-create-new-character-button-tooltip",
@@ -120,11 +113,6 @@ private void UpdateUI()
foreach (var (slot, character) in _preferencesManager.Preferences!.Characters)
{
- if (character is null)
- {
- continue;
- }
-
numberOfFullSlots++;
var characterPickerButton = new CharacterPickerButton(_entityManager,
_preferencesManager,
@@ -148,8 +136,12 @@ private void UpdateUI()
_createNewCharacterButton.Disabled =
numberOfFullSlots >= _preferencesManager.Settings.MaxCharacterSlots;
Characters.AddChild(_createNewCharacterButton);
+ // TODO: Move this shit to the Lobby UI controller
}
+ ///
+ /// Shows individual characters on the side of the character GUI.
+ ///
private sealed class CharacterPickerButton : ContainerButton
{
private EntityUid _previewDummy;
@@ -180,8 +172,8 @@ public CharacterPickerButton(
if (humanoid != null)
{
- LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy, humanoid);
- LobbyCharacterPreviewPanel.GiveDummyLoadoutItems(_previewDummy, humanoid);
+ var controller = UserInterfaceManager.GetUIController();
+ controller.GiveDummyJobClothesLoadout(_previewDummy, humanoid);
}
var isSelectedCharacter = profile == preferencesManager.Preferences?.SelectedCharacter;
diff --git a/Content.Client/Preferences/UI/HighlightedContainer.xaml b/Content.Client/Preferences/UI/HighlightedContainer.xaml
new file mode 100644
index 0000000000..8cf6e2da05
--- /dev/null
+++ b/Content.Client/Preferences/UI/HighlightedContainer.xaml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs b/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs
new file mode 100644
index 0000000000..68294d0f05
--- /dev/null
+++ b/Content.Client/Preferences/UI/HighlightedContainer.xaml.cs
@@ -0,0 +1,14 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Preferences.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class HighlightedContainer : PanelContainer
+{
+ public HighlightedContainer()
+ {
+ RobustXamlLoader.Load(this);
+ }
+}
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
index c9e184dfc2..e12da12d0a 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.Random.cs
@@ -1,25 +1,22 @@
using Content.Shared.Preferences;
-using Robust.Shared.Prototypes;
-namespace Content.Client.Preferences.UI
+namespace Content.Client.Preferences.UI;
+
+public sealed partial class HumanoidProfileEditor
{
- public sealed partial class HumanoidProfileEditor
+ private void RandomizeEverything()
{
- private readonly IPrototypeManager _prototypeManager;
-
- private void RandomizeEverything()
- {
- Profile = HumanoidCharacterProfile.Random();
- UpdateControls();
- IsDirty = true;
- }
+ Profile = HumanoidCharacterProfile.Random();
+ UpdateControls();
+ IsDirty = true;
+ }
- private void RandomizeName()
- {
- if (Profile == null) return;
- var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender);
- SetName(name);
- UpdateNameEdit();
- }
+ private void RandomizeName()
+ {
+ if (Profile == null)
+ return;
+ var name = HumanoidCharacterProfile.GetName(Profile.Species, Profile.Gender);
+ SetName(name);
+ UpdateNameEdit();
}
}
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
index 9a70d67831..ebf794954e 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
@@ -1,178 +1,203 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
index 8b88fc1e9b..954a705fce 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
@@ -1,71 +1,56 @@
using System.Linq;
using System.Numerics;
-using System.Text;
using Content.Client.Guidebook;
using Content.Client.Humanoid;
-using Content.Client.Lobby.UI;
+using Content.Client.Lobby;
using Content.Client.Message;
using Content.Client.Players.PlayTimeTracking;
-using Content.Client.Stylesheets;
-using Content.Client.UserInterface.Controls;
+using Content.Client.Roles;
using Content.Client.UserInterface.Systems.Guidebook;
using Content.Shared.CCVar;
using Content.Shared.Clothing.Loadouts.Prototypes;
using Content.Shared.Clothing.Loadouts.Systems;
+using Content.Shared.Customization.Systems;
using Content.Shared.GameTicking;
using Content.Shared.Humanoid;
using Content.Shared.Humanoid.Markings;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Preferences;
using Content.Shared.Roles;
-using Content.Shared.StatusIcon;
+using Content.Shared.Roles.Jobs;
using Content.Shared.Traits;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
+using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
-using Robust.Shared.Map;
+using Robust.Shared.Physics;
+using Robust.Shared.Player;
using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
using Robust.Shared.Utility;
-using static Robust.Client.UserInterface.Controls.BoxContainer;
using Direction = Robust.Shared.Maths.Direction;
namespace Content.Client.Preferences.UI
{
- public sealed class HighlightedContainer : PanelContainer
- {
- public HighlightedContainer()
- {
- PanelOverride = new StyleBoxFlat()
- {
- BackgroundColor = new Color(47, 47, 53),
- ContentMarginTopOverride = 10,
- ContentMarginBottomOverride = 10,
- ContentMarginLeftOverride = 10,
- ContentMarginRightOverride = 10
- };
- }
- }
-
[GenerateTypedNameReferences]
- public sealed partial class HumanoidProfileEditor : Control
+ public sealed partial class HumanoidProfileEditor : BoxContainer
{
+ private readonly IEntityManager _entityManager;
+ private readonly IPrototypeManager _prototypeManager;
private readonly IClientPreferencesManager _preferencesManager;
- private readonly IEntityManager _entMan;
private readonly IConfigurationManager _configurationManager;
private readonly MarkingManager _markingManager;
private readonly JobRequirementsManager _requirements;
- private readonly LoadoutSystem _loadoutSystem;
+ private readonly CharacterRequirementsSystem _characterRequirementsSystem;
+ private readonly LobbyUIController _controller;
private LineEdit _ageEdit => CAgeEdit;
private LineEdit _nameEdit => CNameEdit;
- private TextEdit _flavorTextEdit = null!;
+ private TextEdit? _flavorTextEdit;
private Button _nameRandomButton => CNameRandomize;
private Button _randomizeEverythingButton => CRandomizeEverything;
private RichTextLabel _warningLabel => CWarningLabel;
@@ -79,22 +64,28 @@ public sealed partial class HumanoidProfileEditor : Control
private SingleMarkingPicker _hairPicker => CHairStylePicker;
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
private EyeColorPicker _eyesPicker => CEyeColorPicker;
+ private Slider _heightSlider => CHeightSlider;
+ private Slider _widthSlider => CWidthSlider;
private TabContainer _tabContainer => CTabContainer;
private BoxContainer _jobList => CJobList;
private BoxContainer _antagList => CAntagList;
- private BoxContainer _traitsList => CTraitsList;
+ private Label _traitPointsLabel => TraitPointsLabel;
+ private int _traitCount;
+ private ProgressBar _traitPointsBar => TraitPointsBar;
+ private Button _traitsShowUnusableButton => TraitsShowUnusableButton;
+ private BoxContainer _traitsTab => CTraitsTab;
+ private TabContainer _traitsTabs => CTraitsTabs;
private Label _loadoutPointsLabel => LoadoutPointsLabel;
private ProgressBar _loadoutPointsBar => LoadoutPointsBar;
- private Button _loadoutsShowUnusableButton => CHideShowUnusableButton;
+ private Button _loadoutsShowUnusableButton => LoadoutsShowUnusableButton;
private BoxContainer _loadoutsTab => CLoadoutsTab;
private TabContainer _loadoutsTabs => CLoadoutsTabs;
private readonly List _jobPriorities;
private OptionButton _preferenceUnavailableButton => CPreferenceUnavailableButton;
private readonly Dictionary _jobCategories;
- // Mildly hacky, as I don't trust prototype order to stay consistent and don't want the UI to break should a new one get added mid-edit. --moony
private readonly List _speciesList;
- private readonly List _antagPreferences;
+ private readonly List _antagPreferences = new();
private readonly List _traitPreferences;
private readonly List _loadoutPreferences;
@@ -102,38 +93,37 @@ public sealed partial class HumanoidProfileEditor : Control
private Button _previewRotateLeftButton => CSpriteRotateLeft;
private Button _previewRotateRightButton => CSpriteRotateRight;
private Direction _previewRotation = Direction.North;
- private EntityUid? _previewDummy;
private BoxContainer _rgbSkinColorContainer => CRgbSkinColorContainer;
private ColorSelectorSliders _rgbSkinColorSelector;
private bool _isDirty;
- private bool _needUpdatePreview;
public int CharacterSlot;
public HumanoidCharacterProfile? Profile;
- private MarkingSet _markingSet = new(); // storing this here feels iffy but a few things need it this high up
public event Action? OnProfileChanged;
+ [ValidatePrototypeId]
+ private const string DefaultSpeciesGuidebook = "Species";
+
public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IPrototypeManager prototypeManager,
- IEntityManager entityManager, IConfigurationManager configurationManager)
+ IConfigurationManager configurationManager)
{
RobustXamlLoader.Load(this);
+ _entityManager = IoCManager.Resolve();
_prototypeManager = prototypeManager;
- _entMan = entityManager;
_preferencesManager = preferencesManager;
_configurationManager = configurationManager;
_markingManager = IoCManager.Resolve();
- _loadoutSystem = EntitySystem.Get();
+ _characterRequirementsSystem = _entityManager.System();
+ _controller = UserInterfaceManager.GetUIController();
- SpeciesInfoButton.ToolTip = Loc.GetString("humanoid-profile-editor-guidebook-button-tooltip");
+ _controller.SetProfileEditor(this);
+ _controller.PreviewDummyUpdated += OnDummyUpdate;
+ _previewSpriteView.SetEntity(_controller.GetPreviewDummy());
#region Left
- #region Randomize
-
- #endregion Randomize
-
#region Name
_nameEdit.OnTextChanged += args => { SetName(args.Text); };
@@ -148,7 +138,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_tabContainer.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-appearance-tab"));
ShowClothes.OnPressed += ToggleClothes;
- ShowLoadouts.OnPressed += ToggleClothes;
+ ShowLoadouts.OnPressed += ToggleLoadouts;
#region Sex
@@ -205,6 +195,82 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
#endregion Species
+ #region Height
+
+ var prototype = _speciesList.Find(x => x.ID == Profile?.Species) ?? _speciesList.First();
+
+ _heightSlider.MinValue = prototype.MinHeight;
+ _heightSlider.MaxValue = prototype.MaxHeight;
+ _heightSlider.Value = Profile?.Height ?? prototype.DefaultHeight;
+ var height = MathF.Round(prototype.AverageHeight * _heightSlider.Value);
+ CHeightLabel.Text = Loc.GetString("humanoid-profile-editor-height-label", ("height", (int) height));
+
+ _heightSlider.OnValueChanged += args =>
+ {
+ if (Profile is null)
+ return;
+
+ prototype = _speciesList.Find(x => x.ID == Profile.Species) ?? _speciesList.First(); // Just in case
+
+ var value = Math.Clamp(args.Value, prototype.MinHeight, prototype.MaxHeight);
+ var height = MathF.Round(prototype.AverageHeight * value);
+ CHeightLabel.Text = Loc.GetString("humanoid-profile-editor-height-label", ("height", (int) height));
+ SetProfileHeight(value);
+ UpdateWeight();
+ };
+
+ CHeightReset.OnPressed += _ =>
+ {
+ _heightSlider.Value = prototype.DefaultHeight;
+ SetProfileHeight(prototype.DefaultHeight);
+ UpdateWeight();
+ };
+
+
+ _widthSlider.MinValue = prototype.MinWidth;
+ _widthSlider.MaxValue = prototype.MaxWidth;
+ _widthSlider.Value = Profile?.Width ?? prototype.DefaultWidth;
+ var width = MathF.Round(prototype.AverageWidth * _widthSlider.Value);
+ CWidthLabel.Text = Loc.GetString("humanoid-profile-editor-width-label", ("width", width));
+
+ _widthSlider.OnValueChanged += args =>
+ {
+ if (Profile is null)
+ return;
+
+ prototype = _speciesList.Find(x => x.ID == Profile.Species) ?? _speciesList.First(); // Just in case
+
+ var value = Math.Clamp(args.Value, prototype.MinWidth, prototype.MaxWidth);
+ var width = MathF.Round(prototype.AverageWidth * value);
+ CWidthLabel.Text = Loc.GetString("humanoid-profile-editor-width-label", ("width", width));
+ SetProfileWidth(value);
+ UpdateWeight();
+ };
+
+ CWidthReset.OnPressed += _ =>
+ {
+ _widthSlider.Value = prototype.DefaultWidth;
+ SetProfileWidth(prototype.DefaultWidth);
+ UpdateWeight();
+ };
+
+ prototypeManager.Index(prototype.Prototype).TryGetComponent(out var fixture);
+ if (fixture != null)
+ {
+ var radius = fixture.Fixtures["fix1"].Shape.Radius;
+ var density = fixture.Fixtures["fix1"].Density;
+ var avg = (_widthSlider.Value + _heightSlider.Value) / 2;
+ var weight = MathF.Round(MathF.PI * MathF.Pow(radius * avg, 2) * density);
+ CWeightLabel.Text = Loc.GetString("humanoid-profile-editor-weight-label", ("weight", (int) weight));
+ }
+ else
+ {
+ // Whelp, the fixture doesn't exist, guesstimate it instead
+ CWeightLabel.Text = Loc.GetString("humanoid-profile-editor-weight-label", ("weight", (int) 71));
+ }
+
+ #endregion Height
+
#region Skin
@@ -230,6 +296,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithHairStyleName(newStyle.id));
IsDirty = true;
+ UpdatePreview();
};
_hairPicker.OnColorChanged += newColor =>
@@ -240,6 +307,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
Profile.Appearance.WithHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsHair();
IsDirty = true;
+ UpdatePreview();
};
_facialHairPicker.OnMarkingSelect += newStyle =>
@@ -249,6 +317,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
Profile = Profile.WithCharacterAppearance(
Profile.Appearance.WithFacialHairStyleName(newStyle.id));
IsDirty = true;
+ UpdatePreview();
};
_facialHairPicker.OnColorChanged += newColor =>
@@ -259,6 +328,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
Profile.Appearance.WithFacialHairColor(newColor.marking.MarkingColors[0]));
UpdateCMarkingsFacialHair();
IsDirty = true;
+ UpdatePreview();
};
_hairPicker.OnSlotRemove += _ =>
@@ -271,6 +341,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
UpdateHairPickers();
UpdateCMarkingsHair();
IsDirty = true;
+ UpdatePreview();
};
_facialHairPicker.OnSlotRemove += _ =>
@@ -283,6 +354,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
UpdateHairPickers();
UpdateCMarkingsFacialHair();
IsDirty = true;
+ UpdatePreview();
};
_hairPicker.OnSlotAdd += delegate()
@@ -303,6 +375,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
UpdateHairPickers();
UpdateCMarkingsHair();
IsDirty = true;
+ UpdatePreview();
};
_facialHairPicker.OnSlotAdd += delegate()
@@ -323,6 +396,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
UpdateHairPickers();
UpdateCMarkingsFacialHair();
IsDirty = true;
+ UpdatePreview();
};
#endregion Hair
@@ -379,6 +453,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
Profile.Appearance.WithEyeColor(newColor));
CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
IsDirty = true;
+ UpdatePreview();
};
#endregion Eyes
@@ -408,7 +483,9 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_jobPriorities = new List();
_jobCategories = new Dictionary();
_requirements = IoCManager.Resolve();
+ _requirements.Updated += UpdateAntagRequirements;
_requirements.Updated += UpdateRoleRequirements;
+ UpdateAntagRequirements();
UpdateRoleRequirements();
#endregion Jobs
@@ -417,60 +494,23 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_tabContainer.SetTabTitle(2, Loc.GetString("humanoid-profile-editor-antags-tab"));
- _antagPreferences = new List();
-
- foreach (var antag in prototypeManager.EnumeratePrototypes().OrderBy(a => Loc.GetString(a.Name)))
- {
- if (!antag.SetPreference)
- continue;
-
- var selector = new AntagPreferenceSelector(antag);
- _antagList.AddChild(selector);
- _antagPreferences.Add(selector);
- if (selector.Disabled)
- {
- Profile = Profile?.WithAntagPreference(antag.ID, false);
- IsDirty = true;
- }
-
- selector.PreferenceChanged += preference =>
- {
- Profile = Profile?.WithAntagPreference(antag.ID, preference);
- IsDirty = true;
- };
- }
-
#endregion Antags
#region Traits
- var traits = prototypeManager.EnumeratePrototypes().OrderBy(t => Loc.GetString(t.Name)).ToList();
- _traitPreferences = new List();
+ // Set up the traits tab
_tabContainer.SetTabTitle(3, Loc.GetString("humanoid-profile-editor-traits-tab"));
+ _traitPreferences = new List();
- if (traits.Count > 0)
- {
- foreach (var trait in traits)
- {
- var selector = new TraitPreferenceSelector(trait);
- _traitsList.AddChild(selector);
- _traitPreferences.Add(selector);
+ // Show/Hide the traits tab if they ever get enabled/disabled
+ var traitsEnabled = configurationManager.GetCVar(CCVars.GameTraitsEnabled);
+ _tabContainer.SetTabVisible(3, traitsEnabled);
+ configurationManager.OnValueChanged(CCVars.GameTraitsEnabled,
+ enabled => _tabContainer.SetTabVisible(3, enabled));
- selector.PreferenceChanged += preference =>
- {
- Profile = Profile?.WithTraitPreference(trait.ID, preference);
- IsDirty = true;
- };
- }
- }
- else
- {
- _traitsList.AddChild(new Label
- {
- Text = "No traits available :(",
- FontColorOverride = Color.Gray,
- });
- }
+ _traitsShowUnusableButton.OnToggled += args => UpdateTraits(args.Pressed);
+
+ UpdateTraits(false);
#endregion
@@ -480,14 +520,17 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_tabContainer.SetTabTitle(4, Loc.GetString("humanoid-profile-editor-loadouts-tab"));
_loadoutPreferences = new List();
- // Show/Hide loadouts tab if they ever get enabled/disabled
- var loadoutsEnabled = _configurationManager.GetCVar(CCVars.GameLoadoutsEnabled);
+ // Show/Hide the loadouts tab if they ever get enabled/disabled
+ var loadoutsEnabled = configurationManager.GetCVar(CCVars.GameLoadoutsEnabled);
_tabContainer.SetTabVisible(4, loadoutsEnabled);
ShowLoadouts.Visible = loadoutsEnabled;
- _configurationManager.OnValueChanged(CCVars.GameLoadoutsEnabled, enabled => LoadoutsChanged(enabled));
+ configurationManager.OnValueChanged(CCVars.GameLoadoutsEnabled,
+ enabled => LoadoutsChanged(enabled));
_loadoutsShowUnusableButton.OnToggled += args => UpdateLoadouts(args.Pressed);
+ UpdateLoadouts(false);
+
#endregion
#region Save
@@ -508,7 +551,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
#region FlavorText
- if (_configurationManager.GetCVar(CCVars.FlavorText))
+ if (configurationManager.GetCVar(CCVars.FlavorText))
{
var flavorText = new FlavorText.FlavorText();
_tabContainer.AddChild(flavorText);
@@ -525,33 +568,20 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_previewRotateLeftButton.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCw();
- _needUpdatePreview = true;
+ SetPreviewRotation(_previewRotation);
};
_previewRotateRightButton.OnPressed += _ =>
{
_previewRotation = _previewRotation.TurnCcw();
- _needUpdatePreview = true;
+ SetPreviewRotation(_previewRotation);
};
- var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var dollProto = _prototypeManager.Index(species).DollPrototype;
-
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy!.Value);
-
- _previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
- _previewSpriteView.SetEntity(_previewDummy);
-
- UpdateLoadouts(false); // Initial UpdateLoadouts call has to have a dummy to get information from
-
#endregion Dummy
#endregion Left
if (preferencesManager.ServerDataLoaded)
- {
LoadServerData();
- }
preferencesManager.OnServerDataLoaded += LoadServerData;
@@ -562,6 +592,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
IsDirty = false;
}
+
private void LoadoutsChanged(bool enabled)
{
_tabContainer.SetTabVisible(4, enabled);
@@ -572,22 +603,63 @@ private void OnSpeciesInfoButtonPressed(BaseButton.ButtonEventArgs args)
{
var guidebookController = UserInterfaceManager.GetUIController();
var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var page = "Species";
+ var page = DefaultSpeciesGuidebook;
if (_prototypeManager.HasIndex(species))
page = species;
- if (_prototypeManager.TryIndex("Species", out var guideRoot))
+ if (_prototypeManager.TryIndex(DefaultSpeciesGuidebook, out var guideRoot))
{
- var dict = new Dictionary();
- dict.Add("Species", guideRoot);
+ var dict = new Dictionary { { DefaultSpeciesGuidebook, guideRoot } };
//TODO: Don't close the guidebook if its already open, just go to the correct page
guidebookController.ToggleGuidebook(dict, includeChildren:true, selected: page);
}
}
- private void ToggleClothes(BaseButton.ButtonEventArgs obj)
+ private void ToggleClothes(BaseButton.ButtonEventArgs _)
+ {
+ _controller.ShowClothes = ShowClothes.Pressed;
+ _controller.UpdateCharacterUI();
+ }
+
+ private void ToggleLoadouts(BaseButton.ButtonEventArgs _)
+ {
+ _controller.ShowLoadouts = ShowLoadouts.Pressed;
+ _controller.UpdateCharacterUI();
+ }
+
+ private void OnDummyUpdate(EntityUid value)
{
- RebuildSpriteView();
+ _previewSpriteView.SetEntity(value);
+ }
+
+ private void UpdateAntagRequirements()
+ {
+ _antagList.DisposeAllChildren();
+ _antagPreferences.Clear();
+
+ foreach (var antag in _prototypeManager.EnumeratePrototypes().OrderBy(a => Loc.GetString(a.Name)))
+ {
+ if (!antag.SetPreference)
+ continue;
+
+ var selector = new AntagPreferenceSelector(antag,
+ _jobPriorities.FirstOrDefault(j => j.Priority == JobPriority.High)?.HighJob
+ ?? new())
+ { Margin = new Thickness(3f, 3f, 3f, 0f) };
+ _antagList.AddChild(selector);
+ _antagPreferences.Add(selector);
+ if (selector.Disabled)
+ {
+ Profile = Profile?.WithAntagPreference(antag.ID, false);
+ IsDirty = true;
+ }
+
+ selector.PreferenceChanged += preference =>
+ {
+ Profile = Profile?.WithAntagPreference(antag.ID, preference);
+ IsDirty = true;
+ };
+ }
}
private void UpdateRoleRequirements()
@@ -653,10 +725,17 @@ private void UpdateRoleRequirements()
{
var selector = new JobPrioritySelector(job, _prototypeManager);
- if (!_requirements.IsAllowed(job, out var reason))
- {
- selector.LockRequirements(reason);
- }
+ if (!_characterRequirementsSystem.CheckRequirementsValid(
+ job.Requirements ?? new(),
+ job,
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
+ _requirements.GetRawPlayTimeTrackers(),
+ _requirements.IsWhitelisted(),
+ _entityManager,
+ _prototypeManager,
+ _configurationManager,
+ out var reasons))
+ selector.LockRequirements(_characterRequirementsSystem.GetRequirementsText(reasons));
category.AddChild(selector);
_jobPriorities.Add(selector);
@@ -693,24 +772,34 @@ private void UpdateRoleRequirements()
}
///
- /// DeltaV - Make sure that no invalid job priorities get through.
+ /// DeltaV - Make sure that no invalid job priorities get through.
///
private void EnsureJobRequirementsValid()
{
var changed = false;
foreach (var selector in _jobPriorities)
{
- if (_requirements.IsAllowed(selector.Proto, out var _) || selector.Priority == JobPriority.Never)
+ if (selector.Priority == JobPriority.Never
+ || _characterRequirementsSystem.CheckRequirementsValid(
+ selector.Proto.Requirements ?? new(),
+ selector.Proto,
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
+ _requirements.GetRawPlayTimeTrackers(),
+ _requirements.IsWhitelisted(),
+ _entityManager,
+ _prototypeManager,
+ _configurationManager,
+ out _))
continue;
selector.Priority = JobPriority.Never;
Profile = Profile?.WithJobPriority(selector.Proto.ID, JobPriority.Never);
changed = true;
}
+
if (!changed)
return;
- _needUpdatePreview = true;
Save();
}
@@ -729,26 +818,16 @@ private void OnMarkingChange(MarkingSet markings)
return;
Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings.GetForwardEnumerator().ToList()));
- _needUpdatePreview = true;
IsDirty = true;
+ UpdatePreview();
}
- private void OnMarkingColorChange(List markings)
+ private void OnSkinColorOnValueChanged()
{
if (Profile is null)
return;
- Profile = Profile.WithCharacterAppearance(Profile.Appearance.WithMarkings(markings));
- IsDirty = true;
- }
-
-
- private void OnSkinColorOnValueChanged()
- {
- if (Profile is null) return;
-
var skin = _prototypeManager.Index(Profile.Species).SkinColoration;
-
var skinColor = _prototypeManager.Index(Profile.Species).DefaultSkinTone;
switch (skin)
@@ -802,6 +881,7 @@ private void OnSkinColorOnValueChanged()
}
IsDirty = true;
+ UpdatePreview();
}
protected override void Dispose(bool disposing)
@@ -810,26 +890,12 @@ protected override void Dispose(bool disposing)
if (!disposing)
return;
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy.Value);
-
+ _controller.PreviewDummyUpdated -= OnDummyUpdate;
+ _requirements.Updated -= UpdateAntagRequirements;
_requirements.Updated -= UpdateRoleRequirements;
_preferencesManager.OnServerDataLoaded -= LoadServerData;
- _configurationManager.UnsubValueChanged(CCVars.GameLoadoutsEnabled, enabled => LoadoutsChanged(enabled));
- }
-
- private void RebuildSpriteView()
- {
- var species = Profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies;
- var dollProto = _prototypeManager.Index(species).DollPrototype;
-
- if (_previewDummy != null)
- _entMan.DeleteEntity(_previewDummy!.Value);
-
- _previewDummy = _entMan.SpawnEntity(dollProto, MapCoordinates.Nullspace);
- _previewSpriteView.SetEntity(_previewDummy);
- _needUpdatePreview = true;
+ _configurationManager.UnsubValueChanged(CCVars.GameLoadoutsEnabled, LoadoutsChanged);
}
private void LoadServerData()
@@ -837,9 +903,10 @@ private void LoadServerData()
Profile = (HumanoidCharacterProfile) _preferencesManager.Preferences!.SelectedCharacter;
CharacterSlot = _preferencesManager.Preferences.SelectedCharacterIndex;
+ UpdateAntagRequirements();
+ UpdateRoleRequirements();
UpdateControls();
EnsureJobRequirementsValid(); // DeltaV
- _needUpdatePreview = true;
}
private void SetAge(int newAge)
@@ -867,6 +934,7 @@ private void SetSex(Sex newSex)
UpdateGenderControls();
CMarkings.SetSex(newSex);
IsDirty = true;
+ UpdatePreview();
}
private void SetGender(Gender newGender)
@@ -880,11 +948,14 @@ private void SetSpecies(string newSpecies)
Profile = Profile?.WithSpecies(newSpecies);
OnSkinColorOnValueChanged(); // Species may have special color prefs, make sure to update it.
CMarkings.SetSpecies(newSpecies); // Repopulate the markings tab as well.
- UpdateSexControls(); // update sex for new species
- RebuildSpriteView(); // they might have different inv so we need a new dummy
+ UpdateSexControls(); // Update sex for new species
+ // Changing species provides inaccurate sliders without these
+ UpdateHeightControls();
+ UpdateWidthControls();
+ UpdateWeight();
UpdateSpeciesGuidebookIcon();
IsDirty = true;
- _needUpdatePreview = true;
+ UpdatePreview();
}
private void SetName(string newName)
@@ -897,12 +968,16 @@ private void SetClothing(ClothingPreference newClothing)
{
Profile = Profile?.WithClothingPreference(newClothing);
IsDirty = true;
+ _controller.UpdateClothes = true;
+ UpdatePreview();
}
private void SetBackpack(BackpackPreference newBackpack)
{
Profile = Profile?.WithBackpackPreference(newBackpack);
IsDirty = true;
+ _controller.UpdateClothes = true;
+ UpdatePreview();
}
private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
@@ -911,16 +986,29 @@ private void SetSpawnPriority(SpawnPriorityPreference newSpawnPriority)
IsDirty = true;
}
+ private void SetProfileHeight(float height)
+ {
+ Profile = Profile?.WithHeight(height);
+ IsDirty = true;
+ UpdatePreview();
+ }
+
+ private void SetProfileWidth(float width)
+ {
+ Profile = Profile?.WithWidth(width);
+ IsDirty = true;
+ UpdatePreview();
+ }
+
public void Save()
{
IsDirty = false;
- if (Profile != null)
- {
- _preferencesManager.UpdateCharacter(Profile, CharacterSlot);
- OnProfileChanged?.Invoke(Profile, CharacterSlot);
- _needUpdatePreview = true;
- }
+ if (Profile == null)
+ return;
+
+ _preferencesManager.UpdateCharacter(Profile, CharacterSlot);
+ OnProfileChanged?.Invoke(Profile, CharacterSlot);
}
private bool IsDirty
@@ -929,7 +1017,6 @@ private bool IsDirty
set
{
_isDirty = value;
- _needUpdatePreview = true;
UpdateSaveButton();
}
}
@@ -942,9 +1029,7 @@ private void UpdateNameEdit()
private void UpdateFlavorTextEdit()
{
if(_flavorTextEdit != null)
- {
_flavorTextEdit.TextRope = new Rope.Leaf(Profile?.FlavorText ?? "");
- }
}
private void UpdateAgeEdit()
@@ -961,23 +1046,16 @@ private void UpdateSexControls()
var sexes = new List();
- // add species sex options, default to just none if we are in bizzaro world and have no species
+ // Add species sex options, default to just none if we are in bizzaro world and have no species
if (_prototypeManager.TryIndex(Profile.Species, out var speciesProto))
- {
foreach (var sex in speciesProto.Sexes)
- {
sexes.Add(sex);
- }
- } else
- {
+ else
sexes.Add(Sex.Unsexed);
- }
- // add button for each sex
+ // Add button for each sex
foreach (var sex in sexes)
- {
_sexButton.AddItem(Loc.GetString($"humanoid-profile-editor-sex-{sex.ToString().ToLower()}-text"), (int) sex);
- }
if (sexes.Contains(Profile.Sex))
_sexButton.SelectId((int) Profile.Sex);
@@ -1003,7 +1081,6 @@ private void UpdateSkinColor()
}
_skinColor.Value = SkinColor.HumanSkinToneFromColor(Profile.Appearance.SkinColor);
-
break;
}
case HumanoidSkinColor.Hues:
@@ -1014,7 +1091,7 @@ private void UpdateSkinColor()
_rgbSkinColorContainer.Visible = true;
}
- // set the RGB values to the direct values otherwise
+ // Set the RGB values to the direct values otherwise
_rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
break;
}
@@ -1026,12 +1103,11 @@ private void UpdateSkinColor()
_rgbSkinColorContainer.Visible = true;
}
- // set the RGB values to the direct values otherwise
+ // Set the RGB values to the direct values otherwise
_rgbSkinColorSelector.Color = Profile.Appearance.SkinColor;
break;
}
}
-
}
public void UpdateSpeciesGuidebookIcon()
@@ -1039,38 +1115,28 @@ public void UpdateSpeciesGuidebookIcon()
SpeciesInfoButton.StyleClasses.Clear();
var species = Profile?.Species;
- if (species is null)
- return;
-
- if (!_prototypeManager.TryIndex(species, out var speciesProto))
- return;
-
- // Don't display the info button if no guide entry is found
- if (!_prototypeManager.HasIndex(species))
+ if (species is null
+ || !_prototypeManager.TryIndex(species, out var speciesProto)
+ || !_prototypeManager.HasIndex(species))
return;
- var style = speciesProto.GuideBookIcon;
+ const string style = "SpeciesInfoDefault";
SpeciesInfoButton.StyleClasses.Add(style);
}
private void UpdateMarkings()
{
if (Profile == null)
- {
return;
- }
- CMarkings.SetData(Profile.Appearance.Markings, Profile.Species,
- Profile.Sex, Profile.Appearance.SkinColor, Profile.Appearance.EyeColor
- );
+ CMarkings.SetData(Profile.Appearance.Markings, Profile.Species, Profile.Sex, Profile.Appearance.SkinColor,
+ Profile.Appearance.EyeColor);
}
private void UpdateSpecies()
{
if (Profile == null)
- {
return;
- }
CSpeciesButton.Select(_speciesList.FindIndex(x => x.ID == Profile.Species));
}
@@ -1078,9 +1144,7 @@ private void UpdateSpecies()
private void UpdateGenderControls()
{
if (Profile == null)
- {
return;
- }
_genderButton.SelectId((int) Profile.Gender);
}
@@ -1088,9 +1152,7 @@ private void UpdateGenderControls()
private void UpdateClothingControls()
{
if (Profile == null)
- {
return;
- }
_clothingButton.SelectId((int) Profile.Clothing);
}
@@ -1098,9 +1160,7 @@ private void UpdateClothingControls()
private void UpdateBackpackControls()
{
if (Profile == null)
- {
return;
- }
_backpackButton.SelectId((int) Profile.Backpack);
}
@@ -1108,19 +1168,66 @@ private void UpdateBackpackControls()
private void UpdateSpawnPriorityControls()
{
if (Profile == null)
- {
return;
- }
_spawnPriorityButton.SelectId((int) Profile.SpawnPriority);
}
- private void UpdateHairPickers()
+ private void UpdateHeightControls()
+ {
+ if (Profile == null)
+ return;
+
+ var species = _speciesList.Find(x => x.ID == Profile.Species) ?? _speciesList.First();
+
+ _heightSlider.MinValue = species.MinHeight;
+ _heightSlider.Value = Profile.Height;
+ _heightSlider.MaxValue = species.MaxHeight;
+
+ var height = MathF.Round(species.AverageHeight * _heightSlider.Value);
+ CHeightLabel.Text = Loc.GetString("humanoid-profile-editor-height-label", ("height", (int) height));
+ }
+
+ private void UpdateWidthControls()
+ {
+ if (Profile == null)
+ return;
+
+ var species = _speciesList.Find(x => x.ID == Profile.Species) ?? _speciesList.First();
+
+ _widthSlider.MinValue = species.MinWidth;
+ _widthSlider.Value = Profile.Width;
+ _widthSlider.MaxValue = species.MaxWidth;
+
+ var width = MathF.Round(species.AverageWidth * _widthSlider.Value);
+ CWidthLabel.Text = Loc.GetString("humanoid-profile-editor-width-label", ("width", (int) width));
+ }
+
+ private void UpdateWeight()
{
if (Profile == null)
- {
return;
+
+ var species = _speciesList.Find(x => x.ID == Profile.Species) ?? _speciesList.First();
+ _prototypeManager.Index(species.Prototype).TryGetComponent(out var fixture);
+
+ if (fixture != null)
+ {
+ var radius = fixture.Fixtures["fix1"].Shape.Radius;
+ var density = fixture.Fixtures["fix1"].Density;
+ var avg = (Profile.Width + Profile.Height) / 2;
+ var weight = MathF.Round(MathF.PI * MathF.Pow(radius * avg, 2) * density);
+ CWeightLabel.Text = Loc.GetString("humanoid-profile-editor-weight-label", ("weight", (int) weight));
}
+
+ _previewSpriteView.InvalidateMeasure();
+ }
+
+ private void UpdateHairPickers()
+ {
+ if (Profile == null)
+ return;
+
var hairMarking = Profile.Appearance.HairStyleId switch
{
HairStyles.DefaultHairStyle => new List(),
@@ -1146,79 +1253,47 @@ private void UpdateHairPickers()
private void UpdateCMarkingsHair()
{
if (Profile == null)
- {
return;
- }
// hair color
Color? hairColor = null;
if ( Profile.Appearance.HairStyleId != HairStyles.DefaultHairStyle &&
- _markingManager.Markings.TryGetValue(Profile.Appearance.HairStyleId, out var hairProto)
- )
- {
+ _markingManager.Markings.TryGetValue(Profile.Appearance.HairStyleId, out var hairProto))
if (_markingManager.CanBeApplied(Profile.Species, Profile.Sex, hairProto, _prototypeManager))
- {
- if (_markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, out var _, _prototypeManager))
- {
- hairColor = Profile.Appearance.SkinColor;
- }
- else
- {
- hairColor = Profile.Appearance.HairColor;
- }
- }
- }
+ hairColor = _markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, out _, _prototypeManager)
+ ? Profile.Appearance.SkinColor
+ : Profile.Appearance.HairColor;
+
if (hairColor != null)
- {
- CMarkings.HairMarking = new (Profile.Appearance.HairStyleId, new List() { hairColor.Value });
- }
+ CMarkings.HairMarking = new(Profile.Appearance.HairStyleId, new List { hairColor.Value });
else
- {
CMarkings.HairMarking = null;
- }
}
private void UpdateCMarkingsFacialHair()
{
if (Profile == null)
- {
return;
- }
// facial hair color
Color? facialHairColor = null;
if ( Profile.Appearance.FacialHairStyleId != HairStyles.DefaultFacialHairStyle &&
- _markingManager.Markings.TryGetValue(Profile.Appearance.FacialHairStyleId, out var facialHairProto)
- )
- {
+ _markingManager.Markings.TryGetValue(Profile.Appearance.FacialHairStyleId, out var facialHairProto))
if (_markingManager.CanBeApplied(Profile.Species, Profile.Sex, facialHairProto, _prototypeManager))
- {
- if (_markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, out var _, _prototypeManager))
- {
- facialHairColor = Profile.Appearance.SkinColor;
- }
- else
- {
- facialHairColor = Profile.Appearance.FacialHairColor;
- }
- }
- }
+ facialHairColor = _markingManager.MustMatchSkin(Profile.Species, HumanoidVisualLayers.Hair, out _, _prototypeManager)
+ ? Profile.Appearance.SkinColor
+ : Profile.Appearance.FacialHairColor;
+
if (facialHairColor != null)
- {
- CMarkings.FacialHairMarking = new (Profile.Appearance.FacialHairStyleId, new List() { facialHairColor.Value });
- }
+ CMarkings.FacialHairMarking = new(Profile.Appearance.FacialHairStyleId, new List { facialHairColor.Value });
else
- {
CMarkings.FacialHairMarking = null;
- }
}
private void UpdateEyePickers()
{
if (Profile == null)
- {
return;
- }
CMarkings.CurrentEyeColor = Profile.Appearance.EyeColor;
_eyesPicker.SetData(Profile.Appearance.EyeColor);
@@ -1234,20 +1309,20 @@ private void UpdatePreview()
if (Profile is null)
return;
- var humanoid = _entMan.System();
- humanoid.LoadProfile(_previewDummy!.Value, Profile);
-
- if (ShowClothes.Pressed)
- LobbyCharacterPreviewPanel.GiveDummyJobClothes(_previewDummy!.Value, Profile);
- if (ShowLoadouts.Pressed)
- LobbyCharacterPreviewPanel.GiveDummyLoadoutItems(_previewDummy!.Value, Profile);
+ SetPreviewRotation(_previewRotation);
+ _controller.UpdateCharacterUI();
+ }
- _previewSpriteView.OverrideDirection = (Direction) ((int) _previewRotation % 4 * 2);
+ private void SetPreviewRotation(Direction direction)
+ {
+ _previewSpriteView.OverrideDirection = (Direction) ((int) direction % 4 * 2);
}
public void UpdateControls()
{
- if (Profile is null) return;
+ if (Profile is null)
+ return;
+
UpdateNameEdit();
UpdateFlavorTextEdit();
UpdateSexControls();
@@ -1266,195 +1341,303 @@ public void UpdateControls()
UpdateLoadouts(_loadoutsShowUnusableButton.Pressed);
UpdateLoadoutPreferences();
UpdateMarkings();
- RebuildSpriteView();
UpdateHairPickers();
UpdateCMarkingsHair();
UpdateCMarkingsFacialHair();
+ UpdateHeightControls();
+ UpdateWidthControls();
+ UpdateWeight();
_preferenceUnavailableButton.SelectId((int) Profile.PreferenceUnavailable);
}
- protected override void FrameUpdate(FrameEventArgs args)
+ private void UpdateJobPriorities()
{
- base.FrameUpdate(args);
-
- if (_needUpdatePreview)
+ foreach (var prioritySelector in _jobPriorities)
{
- UpdatePreview();
- _needUpdatePreview = false;
+ var jobId = prioritySelector.Proto.ID;
+
+ var priority = Profile?.JobPriorities.GetValueOrDefault(jobId, JobPriority.Never) ?? JobPriority.Never;
+
+ prioritySelector.Priority = priority;
}
}
- private void UpdateJobPriorities()
+ private void UpdateAntagPreferences()
{
- foreach (var prioritySelector in _jobPriorities)
+ foreach (var preferenceSelector in _antagPreferences)
{
- var jobId = prioritySelector.Proto.ID;
+ var antagId = preferenceSelector.Proto.ID;
+ var preference = Profile?.AntagPreferences.Contains(antagId) ?? false;
+ preferenceSelector.Preference = preference;
+ }
+ }
- var priority = Profile?.JobPriorities.GetValueOrDefault(jobId, JobPriority.Never) ?? JobPriority.Never;
+ private void UpdateTraitPreferences()
+ {
+ var points = _configurationManager.GetCVar(CCVars.GameTraitsDefaultPoints);
+ _traitCount = 0;
- prioritySelector.Priority = priority;
+ foreach (var preferenceSelector in _traitPreferences)
+ {
+ var traitId = preferenceSelector.Trait.ID;
+ var preference = Profile?.TraitPreferences.Contains(traitId) ?? false;
+
+ preferenceSelector.Preference = preference;
+
+ if (!preference)
+ continue;
+
+ points += preferenceSelector.Trait.Points;
+ _traitCount += 1;
}
+
+ _traitPointsBar.Value = points;
+ _traitPointsLabel.Text = Loc.GetString("humanoid-profile-editor-traits-header",
+ ("points", points), ("traits", _traitCount),
+ ("maxTraits", _configurationManager.GetCVar(CCVars.GameTraitsMax)));
+
+ IsDirty = true;
+ UpdatePreview();
}
- private abstract class RequirementsSelector : Control
+ // Yeah this is mostly just copied from UpdateLoadouts
+ // This whole file is bad though and a lot of loadout code came from traits originally
+ //TODO Make this file not hell
+ private void UpdateTraits(bool showUnusable)
{
- public T Proto { get; }
- public bool Disabled => _lockStripe.Visible;
+ // Reset trait points so you don't get -14 points or something for no reason
+ var points = _configurationManager.GetCVar(CCVars.GameTraitsDefaultPoints);
+ _traitPointsLabel.Text = Loc.GetString("humanoid-profile-editor-traits-header",
+ ("points", points), ("traits", 0),
+ ("maxTraits", _configurationManager.GetCVar(CCVars.GameTraitsMax)));
+ _traitPointsBar.MaxValue = points;
+ _traitPointsBar.Value = points;
+
+ // Clear current listings
+ _traitPreferences.Clear();
+ _traitsTabs.DisposeAllChildren();
- protected readonly RadioOptions Options;
- private StripeBack _lockStripe;
- private Label _requirementsLabel;
- protected RequirementsSelector(T proto)
+ // Get the highest priority job to use for trait filtering
+ var highJob = _jobPriorities.FirstOrDefault(j => j.Priority == JobPriority.High);
+
+ // Get all trait prototypes
+ var enumeratedTraits = _prototypeManager.EnumeratePrototypes().ToList();
+ // Get all trait categories
+ var categories = _prototypeManager.EnumeratePrototypes().ToList();
+
+ // If showUnusable is false filter out traits that are unusable based on your current character setup
+ var traits = enumeratedTraits.Where(trait =>
+ showUnusable || // Ignore everything if this is true
+ _characterRequirementsSystem.CheckRequirementsValid(
+ trait.Requirements,
+ highJob?.Proto ?? new JobPrototype(),
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
+ _requirements.GetRawPlayTimeTrackers(),
+ _requirements.IsWhitelisted(),
+ _entityManager,
+ _prototypeManager,
+ _configurationManager,
+ out _
+ )
+ ).ToList();
+
+ // Traits to highlight red when showUnusable is true
+ var traitsUnusable = enumeratedTraits.Where(trait =>
+ _characterRequirementsSystem.CheckRequirementsValid(
+ trait.Requirements,
+ highJob?.Proto ?? new JobPrototype(),
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
+ _requirements.GetRawPlayTimeTrackers(),
+ _requirements.IsWhitelisted(),
+ _entityManager,
+ _prototypeManager,
+ _configurationManager,
+ out _
+ )
+ ).ToList();
+
+ // Every trait not in the traits list
+ var otherTraits = enumeratedTraits.Where(trait => !traits.Contains(trait)).ToList();
+
+
+ if (traits.Count == 0)
{
- Proto = proto;
+ _traitsTab.AddChild(new Label { Text = Loc.GetString("humanoid-profile-editor-traits-no-traits") });
+ return;
+ }
- Options = new RadioOptions(RadioOptionsLayout.Horizontal)
+ // Make Uncategorized category
+ var uncategorized = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ VerticalExpand = true,
+ Name = "Uncategorized_0",
+ // I hate ScrollContainers
+ Children =
{
- FirstButtonStyle = StyleBase.ButtonOpenRight,
- ButtonStyle = StyleBase.ButtonOpenBoth,
- LastButtonStyle = StyleBase.ButtonOpenLeft
- };
- //Override default radio option button width
- Options.GenerateItem = GenerateButton;
+ new ScrollContainer
+ {
+ HScrollEnabled = false,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ },
+ },
+ },
+ },
+ };
- Options.OnItemSelected += args => Options.Select(args.Id);
+ _traitsTabs.AddChild(uncategorized);
+ _traitsTabs.SetTabTitle(0, Loc.GetString("trait-category-Uncategorized"));
- _requirementsLabel = new Label()
+
+ // Make categories
+ var currentCategory = 1; // 1 because we already made 0 as Uncategorized, I am not not zero-indexing :)
+ foreach (var category in categories.OrderBy(c => Loc.GetString($"trait-category-{c.ID}")))
+ {
+ // Check for existing category
+ BoxContainer? match = null;
+ foreach (var child in _traitsTabs.Children)
{
- Text = Loc.GetString("role-timer-locked"),
- Visible = true,
- HorizontalAlignment = HAlignment.Center,
- StyleClasses = {StyleBase.StyleClassLabelSubText},
- };
+ if (string.IsNullOrEmpty(child.Name))
+ continue;
+
+ if (child.Name.Split("_")[0] == category.ID)
+ match = (BoxContainer) child;
+ }
+
+ // If there is a category do nothing
+ if (match != null)
+ continue;
- _lockStripe = new StripeBack()
+ // If not, make it
+ var box = new BoxContainer
{
- Visible = false,
- HorizontalExpand = true,
- MouseFilter = MouseFilterMode.Stop,
+ Orientation = LayoutOrientation.Vertical,
+ VerticalExpand = true,
+ Name = $"{category.ID}_{currentCategory}",
+ // I hate ScrollContainers
Children =
{
- _requirementsLabel
- }
+ new ScrollContainer
+ {
+ HScrollEnabled = false,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ },
+ },
+ },
+ },
};
- // Setup must be called after
+ _traitsTabs.AddChild(box);
+ _traitsTabs.SetTabTitle(currentCategory, Loc.GetString($"trait-category-{category.ID}"));
+ currentCategory++;
}
- ///
- /// Actually adds the controls, must be called in the inheriting class' constructor.
- ///
- protected void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
+
+ // Fill categories
+ foreach (var trait in traits.OrderBy(t => -t.Points).ThenBy(t => Loc.GetString($"trait-name-{t.ID}")))
{
- foreach (var (text, value) in items)
- {
- Options.AddItem(Loc.GetString(text), value);
- }
+ var selector = new TraitPreferenceSelector(trait, highJob?.Proto ?? new JobPrototype(),
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
+ traitsUnusable.Contains(trait) ? "" : "ButtonColorRed",
+ _entityManager, _prototypeManager, _configurationManager, _characterRequirementsSystem,
+ _requirements);
- var titleLabel = new Label()
+ // Look for an existing trait category
+ BoxContainer? match = null;
+ foreach (var child in _traitsTabs.Children)
{
- Margin = new Thickness(5f, 0, 5f, 0),
- Text = title,
- MinSize = new Vector2(titleSize, 0),
- MouseFilter = MouseFilterMode.Stop,
- ToolTip = description
- };
+ if (string.IsNullOrEmpty(child.Name))
+ continue;
- var container = new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- };
+ // This is fucked up
+ if (child.Name.Split("_")[0] == trait.Category
+ && child.Children.FirstOrDefault()?.Children.FirstOrDefault(g =>
+ g.GetType() == typeof(BoxContainer)) is { } g)
+ match = (BoxContainer) g;
+ }
- if (icon != null)
- container.AddChild(icon);
- container.AddChild(titleLabel);
- container.AddChild(Options);
- container.AddChild(_lockStripe);
+ // If there is no category put it in Uncategorized
+ if (string.IsNullOrEmpty(match?.Parent?.Parent?.Name)
+ || match.Parent.Parent.Name.Split("_")[0] != trait.Category)
+ uncategorized.AddChild(selector);
+ else
+ match.AddChild(selector);
- AddChild(container);
- }
- public void LockRequirements(FormattedMessage requirements)
- {
- var tooltip = new Tooltip();
- tooltip.SetMessage(requirements);
- _lockStripe.TooltipSupplier = _ => tooltip;
- _lockStripe.Visible = true;
- Options.Visible = false;
+ AddSelector(selector, trait.Points, trait.ID);
}
- // TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
- public void UnlockRequirements()
+ // Add the selected unusable traits to the point counter
+ foreach (var trait in otherTraits.OrderBy(t => -t.Points).ThenBy(t => Loc.GetString($"trait-name-{t.ID}")))
{
- _lockStripe.Visible = false;
- Options.Visible = true;
- }
+ var selector = new TraitPreferenceSelector(trait, highJob?.Proto ?? new JobPrototype(),
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(), "",
+ _entityManager, _prototypeManager, _configurationManager, _characterRequirementsSystem,
+ _requirements);
- private Button GenerateButton(string text, int value)
- {
- return new Button
- {
- Text = text,
- MinWidth = 90
- };
+
+ AddSelector(selector, trait.Points, trait.ID);
}
- }
- private sealed class JobPrioritySelector : RequirementsSelector
- {
- public JobPriority Priority
+
+ // Hide Uncategorized tab if it's empty, other tabs already shouldn't exist if they're empty
+ _traitsTabs.SetTabVisible(0, uncategorized.Children.First().Children.First().Children.Any());
+
+ // Add fake tabs until tab container is happy
+ for (var i = _traitsTabs.ChildCount - 1; i < _traitsTabs.CurrentTab; i++)
{
- get => (JobPriority) Options.SelectedValue;
- set => Options.SelectByValue((int) value);
+ _traitsTabs.AddChild(new BoxContainer());
+ _traitsTabs.SetTabVisible(i + 1, false);
}
- public event Action? PriorityChanged;
+ UpdateTraitPreferences();
+ return;
- public JobPrioritySelector(JobPrototype proto, IPrototypeManager protoMan)
- : base(proto)
- {
- Options.OnItemSelected += args => PriorityChanged?.Invoke(Priority);
- var items = new[]
- {
- ("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
- ("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
- ("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
- ("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
- };
+ void AddSelector(TraitPreferenceSelector selector, int points, string id)
+ {
+ if (points > 0)
+ _traitPointsBar.MaxValue += points;
- var icon = new TextureRect
+ _traitPreferences.Add(selector);
+ selector.PreferenceChanged += preference =>
{
- TextureScale = new Vector2(2, 2),
- VerticalAlignment = VAlignment.Center
+ // Make sure they have enough trait points
+ preference = preference ? CheckPoints(points, preference) : CheckPoints(-points, preference);
+ // Don't allow having too many traits
+ preference = preference && _traitCount + 1 <= _configurationManager.GetCVar(CCVars.GameTraitsMax);
+
+ // Update Preferences
+ Profile = Profile?.WithTraitPreference(id, preference);
+ UpdatePreview();
+ UpdateTraitPreferences();
+ UpdateTraits(_traitsShowUnusableButton.Pressed);
+ UpdateLoadouts(_loadoutsShowUnusableButton.Pressed);
};
- var jobIcon = protoMan.Index(proto.Icon);
- icon.Texture = jobIcon.Icon.Frame0();
-
- Setup(items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
- }
- }
-
- private void UpdateAntagPreferences()
- {
- foreach (var preferenceSelector in _antagPreferences)
- {
- var antagId = preferenceSelector.Proto.ID;
- var preference = Profile?.AntagPreferences.Contains(antagId) ?? false;
- preferenceSelector.Preference = preference;
}
- }
- private void UpdateTraitPreferences()
- {
- foreach (var preferenceSelector in _traitPreferences)
+ bool CheckPoints(int points, bool preference)
{
- var traitId = preferenceSelector.Trait.ID;
- var preference = Profile?.TraitPreferences.Contains(traitId) ?? false;
-
- preferenceSelector.Preference = preference;
+ var temp = _traitPointsBar.Value + points;
+ return preference ? !(temp < 0) : temp < 0;
}
}
@@ -1478,6 +1661,10 @@ private void UpdateLoadoutPreferences()
_loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label", ("points", points), ("max", _loadoutPointsBar.MaxValue));
}
}
+
+ IsDirty = true;
+ _controller.UpdateClothes = true;
+ UpdatePreview();
}
private void UpdateLoadouts(bool showUnusable)
@@ -1498,16 +1685,19 @@ private void UpdateLoadouts(bool showUnusable)
// Get all loadout prototypes
var enumeratedLoadouts = _prototypeManager.EnumeratePrototypes().ToList();
+ // Get all loadout categories
+ var categories = _prototypeManager.EnumeratePrototypes().ToList();
// If showUnusable is false filter out loadouts that are unusable based on your current character setup
var loadouts = enumeratedLoadouts.Where(loadout =>
showUnusable || // Ignore everything if this is true
- _loadoutSystem.CheckRequirementsValid(
+ _characterRequirementsSystem.CheckRequirementsValid(
loadout.Requirements,
highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
- new Dictionary(),
- _entMan,
+ _requirements.GetRawPlayTimeTrackers(),
+ _requirements.IsWhitelisted(),
+ _entityManager,
_prototypeManager,
_configurationManager,
out _
@@ -1516,12 +1706,13 @@ out _
// Loadouts to highlight red when showUnusable is true
var loadoutsUnusable = enumeratedLoadouts.Where(loadout =>
- _loadoutSystem.CheckRequirementsValid(
+ _characterRequirementsSystem.CheckRequirementsValid(
loadout.Requirements,
highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
- new Dictionary(),
- _entMan,
+ _requirements.GetRawPlayTimeTrackers(),
+ _requirements.IsWhitelisted(),
+ _entityManager,
_prototypeManager,
_configurationManager,
out _
@@ -1544,23 +1735,47 @@ out _
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true,
Name = "Uncategorized_0",
+ // I hate ScrollContainers
+ Children =
+ {
+ new ScrollContainer
+ {
+ HScrollEnabled = false,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ Children =
+ {
+ new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ HorizontalExpand = true,
+ VerticalExpand = true,
+ },
+ },
+ },
+ },
};
_loadoutsTabs.AddChild(uncategorized);
- _loadoutsTabs.SetTabTitle(0, Loc.GetString("humanoid-profile-editor-loadouts-uncategorized-tab"));
+ _loadoutsTabs.SetTabTitle(0, Loc.GetString("loadout-category-Uncategorized"));
+
// Make categories
var currentCategory = 1; // 1 because we already made 0 as Uncategorized, I am not not zero-indexing :)
- foreach (var loadout in loadouts.OrderBy(l => l.Category))
+ foreach (var category in categories.OrderBy(c => Loc.GetString($"loadout-category-{c.ID}")))
{
// Check for existing category
BoxContainer? match = null;
foreach (var child in _loadoutsTabs.Children)
{
- if (match != null || child.Name == null)
+ if (string.IsNullOrEmpty(child.Name))
continue;
- if (child.Name.Split("_")[0] == loadout.Category)
- match = (BoxContainer) child;
+
+ // This is fucked up
+ if (child.Name.Split("_")[0] == category.ID
+ && child.Children.FirstOrDefault()?.Children.FirstOrDefault(g =>
+ g.GetType() == typeof(BoxContainer)) is { } g)
+ match = (BoxContainer) g;
}
// If there is a category do nothing
@@ -1572,7 +1787,7 @@ out _
{
Orientation = LayoutOrientation.Vertical,
VerticalExpand = true,
- Name = $"{loadout.Category}_{currentCategory}",
+ Name = $"{category.ID}_{currentCategory}",
// I hate ScrollContainers
Children =
{
@@ -1595,23 +1810,25 @@ out _
};
_loadoutsTabs.AddChild(box);
- _loadoutsTabs.SetTabTitle(currentCategory, Loc.GetString($"loadout-category-{loadout.Category}"));
+ _loadoutsTabs.SetTabTitle(currentCategory, Loc.GetString($"loadout-category-{category.ID}"));
currentCategory++;
}
+
// Fill categories
- foreach (var loadout in loadouts.OrderBy(l => l.ID))
+ foreach (var loadout in loadouts.OrderBy(l => l.Cost).ThenBy(l => Loc.GetString($"loadout-{l.ID}-name")))
{
var selector = new LoadoutPreferenceSelector(loadout, highJob?.Proto ?? new JobPrototype(),
Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(),
- loadoutsUnusable.Contains(loadout) ? "" : "ButtonColorRed", _entMan, _prototypeManager,
- _configurationManager, _loadoutSystem);
+ loadoutsUnusable.Contains(loadout) ? "" : "ButtonColorRed",
+ _entityManager, _prototypeManager, _configurationManager, _characterRequirementsSystem,
+ _requirements);
// Look for an existing loadout category
BoxContainer? match = null;
foreach (var child in _loadoutsTabs.Children)
{
- if (match != null || child.Name == null)
+ if (string.IsNullOrEmpty(child.Name))
continue;
if (child.Name.Split("_")[0] == loadout.Category)
@@ -1619,80 +1836,31 @@ out _
}
// If there is no category put it in Uncategorized
- if (match?.Parent?.Parent?.Name == null)
+ if (string.IsNullOrEmpty(match?.Parent?.Parent?.Name)
+ || match.Parent.Parent.Name.Split("_")[0] != loadout.Category)
uncategorized.AddChild(selector);
else
match.AddChild(selector);
- _loadoutPreferences.Add(selector);
- selector.PreferenceChanged += preference =>
- {
- // Make sure they have enough loadout points
- if (preference)
- {
- var temp = _loadoutPointsBar.Value - loadout.Cost;
- if (temp < 0)
- preference = false;
- else
- {
- _loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label",
- ("points", temp), ("max", _loadoutPointsBar.MaxValue));
- _loadoutPointsBar.Value = temp;
- }
- }
- else
- {
- _loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label",
- ("points", _loadoutPointsBar.Value), ("max", _loadoutPointsBar.MaxValue));
- _loadoutPointsBar.Value += loadout.Cost;
- }
- // Update Preference
- Profile = Profile?.WithLoadoutPreference(loadout.ID, preference);
- IsDirty = true;
- UpdateLoadoutPreferences();
- };
+
+ AddSelector(selector, loadout.Cost, loadout.ID);
}
// Add the selected unusable loadouts to the point counter
- foreach (var loadout in otherLoadouts.OrderBy(l => l.ID))
+ foreach (var loadout in otherLoadouts.OrderBy(l => l.Cost).ThenBy(l => Loc.GetString($"loadout-{l.ID}-name")))
{
var selector = new LoadoutPreferenceSelector(loadout, highJob?.Proto ?? new JobPrototype(),
- Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(), "", _entMan, _prototypeManager,
- _configurationManager, _loadoutSystem);
+ Profile ?? HumanoidCharacterProfile.DefaultWithSpecies(), "",
+ _entityManager, _prototypeManager, _configurationManager, _characterRequirementsSystem,
+ _requirements);
- _loadoutPreferences.Add(selector);
- selector.PreferenceChanged += preference =>
- {
- // Make sure they have enough loadout points
- if (preference)
- {
- var temp = _loadoutPointsBar.Value - loadout.Cost;
- if (temp < 0)
- preference = false;
- else
- {
- _loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label",
- ("points", temp), ("max", _loadoutPointsBar.MaxValue));
- _loadoutPointsBar.Value = temp;
- }
- }
- else
- {
- _loadoutPointsLabel.Text = Loc.GetString("humanoid-profile-editor-loadouts-points-label",
- ("points", _loadoutPointsBar.Value), ("max", _loadoutPointsBar.MaxValue));
- _loadoutPointsBar.Value += loadout.Cost;
- }
- // Update Preference
- Profile = Profile?.WithLoadoutPreference(loadout.ID, preference);
- IsDirty = true;
- UpdateLoadoutPreferences();
- };
+
+ AddSelector(selector, loadout.Cost, loadout.ID);
}
// Hide Uncategorized tab if it's empty, other tabs already shouldn't exist if they're empty
- if (!uncategorized.Children.Any())
- _loadoutsTabs.SetTabVisible(0, false);
+ _loadoutsTabs.SetTabVisible(0, uncategorized.Children.First().Children.First().Children.Any());
// Add fake tabs until tab container is happy
for (var i = _loadoutsTabs.ChildCount - 1; i < _loadoutsTabs.CurrentTab; i++)
@@ -1702,165 +1870,30 @@ out _
}
UpdateLoadoutPreferences();
- }
-
-
- private sealed class AntagPreferenceSelector : RequirementsSelector
- {
- // 0 is yes and 1 is no
- public bool Preference
- {
- get => Options.SelectedValue == 0;
- set => Options.Select((value && !Disabled) ? 0 : 1);
- }
-
- public event Action? PreferenceChanged;
-
- public AntagPreferenceSelector(AntagPrototype proto)
- : base(proto)
- {
- Options.OnItemSelected += args => PreferenceChanged?.Invoke(Preference);
-
- var items = new[]
- {
- ("humanoid-profile-editor-antag-preference-yes-button", 0),
- ("humanoid-profile-editor-antag-preference-no-button", 1)
- };
- var title = Loc.GetString(proto.Name);
- var description = Loc.GetString(proto.Objective);
- Setup(items, title, 250, description);
-
- // immediately lock requirements if they arent met.
- // another function checks Disabled after creating the selector so this has to be done now
- var requirements = IoCManager.Resolve();
- if (proto.Requirements != null && !requirements.CheckRoleTime(proto.Requirements, out var reason))
- {
- LockRequirements(reason);
- }
- }
- }
+ return;
- private sealed class TraitPreferenceSelector : Control
- {
- public TraitPrototype Trait { get; }
- private readonly Button _button;
- public bool Preference
+ void AddSelector(LoadoutPreferenceSelector selector, int points, string id)
{
- get => _button.Pressed;
- set => _button.Pressed = value;
- }
-
- public event Action? PreferenceChanged;
-
- public TraitPreferenceSelector(TraitPrototype trait)
- {
- Trait = trait;
-
- _button = new Button {Text = Loc.GetString(trait.Name)};
- _button.ToggleMode = true;
- _button.OnToggled += OnButtonToggled;
-
- if (trait.Description is { } desc)
- {
- _button.ToolTip = Loc.GetString(desc);
- }
-
- AddChild(new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- Children = { _button },
- });
- }
-
- private void OnButtonToggled(BaseButton.ButtonToggledEventArgs args)
- {
- PreferenceChanged?.Invoke(Preference);
- }
- }
-
- private sealed class LoadoutPreferenceSelector : Control
- {
- public LoadoutPrototype Loadout { get; }
- private readonly Button _button;
-
- public bool Preference
- {
- get => _button.Pressed;
- set => _button.Pressed = value;
- }
-
- public event Action? PreferenceChanged;
-
- public LoadoutPreferenceSelector(LoadoutPrototype loadout, JobPrototype highJob,
- HumanoidCharacterProfile profile, string style, IEntityManager entityManager, IPrototypeManager prototypeManager,
- IConfigurationManager configManager, LoadoutSystem loadoutSystem)
- {
- Loadout = loadout;
-
- // Display the first item in the loadout as a preview
- // TODO: Maybe allow custom icons to be specified in the prototype?
- var dummyLoadoutItem = entityManager.SpawnEntity(loadout.Items.First(), MapCoordinates.Nullspace);
-
- // Create a sprite preview of the loadout item
- var previewLoadout = new SpriteView
+ _loadoutPreferences.Add(selector);
+ selector.PreferenceChanged += preference =>
{
- Scale = new Vector2(1, 1),
- OverrideDirection = Direction.South,
- VerticalAlignment = VAlignment.Center,
- SizeFlagsStretchRatio = 1,
- };
- previewLoadout.SetEntity(dummyLoadoutItem);
-
+ // Make sure they have enough loadout points
+ preference = preference ? CheckPoints(-points, preference) : CheckPoints(points, preference);
- // Create a checkbox to get the loadout
- _button = new Button
- {
- Text = $"[{loadout.Cost}] {(Loc.GetString($"loadout-name-{loadout.ID}") == $"loadout-name-{loadout.ID}"
- ? entityManager.GetComponent(dummyLoadoutItem).EntityName
- : Loc.GetString($"loadout-name-{loadout.ID}"))}",
- VerticalAlignment = VAlignment.Center,
- ToggleMode = true,
+ // Update Preferences
+ Profile = Profile?.WithLoadoutPreference(id, preference);
+ IsDirty = true;
+ UpdateLoadoutPreferences();
+ UpdateLoadouts(_loadoutsShowUnusableButton.Pressed);
+ UpdateTraits(_traitsShowUnusableButton.Pressed);
};
- _button.OnToggled += OnButtonToggled;
- _button.AddStyleClass(style);
-
- var tooltip = new StringBuilder();
- // Add the loadout description to the tooltip if there is one
- var desc = !Loc.TryGetString($"loadout-description-{loadout.ID}", out var description)
- ? entityManager.GetComponent(dummyLoadoutItem).EntityDescription
- : description;
- if (!string.IsNullOrEmpty(desc))
- tooltip.Append($"{Loc.GetString(desc)}");
-
-
- // Get requirement reasons
- loadoutSystem.CheckRequirementsValid(loadout.Requirements, highJob, profile, new Dictionary(), entityManager, prototypeManager, configManager, out var reasons);
-
- // Add requirement reasons to the tooltip
- foreach (var reason in reasons)
- tooltip.Append($"\n{reason.ToMarkup()}");
-
- // Combine the tooltip and format it in the checkbox supplier
- if (tooltip.Length > 0)
- {
- var formattedTooltip = new Tooltip();
- formattedTooltip.SetMessage(FormattedMessage.FromMarkupPermissive(tooltip.ToString()));
- _button.TooltipSupplier = _ => formattedTooltip;
- }
-
-
- // Add the loadout preview and the checkbox to the control
- AddChild(new BoxContainer
- {
- Orientation = LayoutOrientation.Horizontal,
- Children = { previewLoadout, _button },
- });
}
- private void OnButtonToggled(BaseButton.ButtonToggledEventArgs args)
+ bool CheckPoints(int points, bool preference)
{
- PreferenceChanged?.Invoke(Preference);
+ var temp = _loadoutPointsBar.Value + points;
+ return preference ? !(temp < 0) : temp < 0;
}
}
}
diff --git a/Content.Client/Preferences/UI/JobPrioritySelector.cs b/Content.Client/Preferences/UI/JobPrioritySelector.cs
new file mode 100644
index 0000000000..f66102d644
--- /dev/null
+++ b/Content.Client/Preferences/UI/JobPrioritySelector.cs
@@ -0,0 +1,44 @@
+using System.Numerics;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Content.Shared.StatusIcon;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.Utility;
+using Robust.Shared.CPUJob.JobQueues;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.Preferences.UI;
+
+public sealed class JobPrioritySelector : RequirementsSelector
+{
+ public JobPriority Priority
+ {
+ get => (JobPriority) Options.SelectedValue;
+ set => Options.SelectByValue((int) value);
+ }
+
+ public event Action? PriorityChanged;
+
+ public JobPrioritySelector(JobPrototype proto, IPrototypeManager protoMan) : base(proto, proto)
+ {
+ Options.OnItemSelected += _ => PriorityChanged?.Invoke(Priority);
+
+ var items = new[]
+ {
+ ("humanoid-profile-editor-job-priority-high-button", (int) JobPriority.High),
+ ("humanoid-profile-editor-job-priority-medium-button", (int) JobPriority.Medium),
+ ("humanoid-profile-editor-job-priority-low-button", (int) JobPriority.Low),
+ ("humanoid-profile-editor-job-priority-never-button", (int) JobPriority.Never),
+ };
+
+ var icon = new TextureRect
+ {
+ TextureScale = new Vector2(2, 2),
+ VerticalAlignment = VAlignment.Center,
+ };
+ var jobIcon = protoMan.Index(proto.Icon);
+ icon.Texture = jobIcon.Icon.Frame0();
+
+ Setup(items, proto.LocalizedName, 200, proto.LocalizedDescription, icon);
+ }
+}
diff --git a/Content.Client/Preferences/UI/LoadoutPreferenceSelector.cs b/Content.Client/Preferences/UI/LoadoutPreferenceSelector.cs
new file mode 100644
index 0000000000..82d8fd65b3
--- /dev/null
+++ b/Content.Client/Preferences/UI/LoadoutPreferenceSelector.cs
@@ -0,0 +1,130 @@
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Stylesheets;
+using Content.Shared.Clothing.Loadouts.Prototypes;
+using Content.Shared.Customization.Systems;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Configuration;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Preferences.UI;
+
+
+public sealed class LoadoutPreferenceSelector : Control
+{
+ public LoadoutPrototype Loadout { get; }
+ private readonly Button _button;
+
+ public bool Preference
+ {
+ get => _button.Pressed;
+ set => _button.Pressed = value;
+ }
+
+ public event Action? PreferenceChanged;
+
+ public LoadoutPreferenceSelector(LoadoutPrototype loadout, JobPrototype highJob,
+ HumanoidCharacterProfile profile, string style, IEntityManager entityManager, IPrototypeManager prototypeManager,
+ IConfigurationManager configManager, CharacterRequirementsSystem characterRequirementsSystem,
+ JobRequirementsManager jobRequirementsManager)
+ {
+ Loadout = loadout;
+
+ // Display the first item in the loadout as a preview
+ // TODO: Maybe allow custom icons to be specified in the prototype?
+ var dummyLoadoutItem = entityManager.SpawnEntity(loadout.Items.First(), MapCoordinates.Nullspace);
+
+ // Create a sprite preview of the loadout item
+ var previewLoadout = new SpriteView
+ {
+ Scale = new Vector2(1, 1),
+ OverrideDirection = Direction.South,
+ VerticalAlignment = VAlignment.Center,
+ SizeFlagsStretchRatio = 1,
+ };
+ previewLoadout.SetEntity(dummyLoadoutItem);
+
+
+ // Create a checkbox to get the loadout
+ _button = new Button
+ {
+ ToggleMode = true,
+ StyleClasses = { StyleBase.ButtonOpenLeft },
+ Children =
+ {
+ new BoxContainer
+ {
+ Children =
+ {
+ new Label
+ {
+ Text = loadout.Cost.ToString(),
+ StyleClasses = { StyleBase.StyleClassLabelHeading },
+ MinWidth = 32,
+ MaxWidth = 32,
+ ClipText = true,
+ Margin = new Thickness(0, 0, 8, 0),
+ },
+ new Label
+ {
+ Text = Loc.GetString($"loadout-name-{loadout.ID}") == $"loadout-name-{loadout.ID}"
+ ? entityManager.GetComponent(dummyLoadoutItem).EntityName
+ : Loc.GetString($"loadout-name-{loadout.ID}"),
+ },
+ },
+ },
+ },
+ };
+ _button.OnToggled += OnButtonToggled;
+ _button.AddStyleClass(style);
+
+ var tooltip = new StringBuilder();
+ // Add the loadout description to the tooltip if there is one
+ var desc = !Loc.TryGetString($"loadout-description-{loadout.ID}", out var description)
+ ? entityManager.GetComponent(dummyLoadoutItem).EntityDescription
+ : description;
+ if (!string.IsNullOrEmpty(desc))
+ tooltip.Append($"{Loc.GetString(desc)}");
+
+
+ // Get requirement reasons
+ characterRequirementsSystem.CheckRequirementsValid(
+ loadout.Requirements, highJob, profile, new Dictionary(),
+ jobRequirementsManager.IsWhitelisted(),
+ entityManager, prototypeManager, configManager,
+ out var reasons);
+
+ // Add requirement reasons to the tooltip
+ foreach (var reason in reasons)
+ tooltip.Append($"\n{reason.ToMarkup()}");
+
+ // Combine the tooltip and format it in the checkbox supplier
+ if (tooltip.Length > 0)
+ {
+ var formattedTooltip = new Tooltip();
+ formattedTooltip.SetMessage(FormattedMessage.FromMarkupPermissive(tooltip.ToString()));
+ _button.TooltipSupplier = _ => formattedTooltip;
+ }
+
+
+ // Add the loadout preview and the checkbox to the control
+ AddChild(new BoxContainer
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ Children = { previewLoadout, _button },
+ });
+ }
+
+ private void OnButtonToggled(BaseButton.ButtonToggledEventArgs args)
+ {
+ PreferenceChanged?.Invoke(Preference);
+ }
+}
diff --git a/Content.Client/Preferences/UI/RequirementsSelector.cs b/Content.Client/Preferences/UI/RequirementsSelector.cs
new file mode 100644
index 0000000000..83b9695288
--- /dev/null
+++ b/Content.Client/Preferences/UI/RequirementsSelector.cs
@@ -0,0 +1,105 @@
+using System.Numerics;
+using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Roles;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Preferences.UI;
+
+public abstract class RequirementsSelector : BoxContainer where T : IPrototype
+{
+ public T Proto { get; }
+ public JobPrototype HighJob { get; }
+ public bool Disabled => _lockStripe.Visible;
+
+ protected readonly RadioOptions Options;
+ private readonly StripeBack _lockStripe;
+
+ protected RequirementsSelector(T proto, JobPrototype highJob)
+ {
+ Proto = proto;
+ HighJob = highJob;
+
+ Options = new RadioOptions(RadioOptionsLayout.Horizontal)
+ {
+ FirstButtonStyle = StyleBase.ButtonOpenRight,
+ ButtonStyle = StyleBase.ButtonOpenBoth,
+ LastButtonStyle = StyleBase.ButtonOpenLeft,
+ };
+ //Override default radio option button width
+ Options.GenerateItem = GenerateButton;
+ Options.OnItemSelected += args => Options.Select(args.Id);
+
+ var requirementsLabel = new Label
+ {
+ Text = Loc.GetString("role-timer-locked"),
+ Visible = true,
+ HorizontalAlignment = HAlignment.Center,
+ StyleClasses = {StyleBase.StyleClassLabelSubText},
+ };
+
+ _lockStripe = new StripeBack
+ {
+ Visible = false,
+ HorizontalExpand = true,
+ MouseFilter = MouseFilterMode.Stop,
+ Children = { requirementsLabel },
+ };
+ }
+
+ ///
+ /// Actually adds the controls, must be called in the inheriting class' constructor.
+ ///
+ protected void Setup((string, int)[] items, string title, int titleSize, string? description, TextureRect? icon = null)
+ {
+ foreach (var (text, value) in items)
+ Options.AddItem(Loc.GetString(text), value);
+
+ var titleLabel = new Label
+ {
+ Margin = new Thickness(5f, 0, 5f, 0),
+ Text = title,
+ MinSize = new Vector2(titleSize, 0),
+ MouseFilter = MouseFilterMode.Stop,
+ ToolTip = description
+ };
+
+ var container = new BoxContainer { Orientation = LayoutOrientation.Horizontal, };
+
+ if (icon != null)
+ container.AddChild(icon);
+ container.AddChild(titleLabel);
+ container.AddChild(Options);
+ container.AddChild(_lockStripe);
+
+ AddChild(container);
+ }
+
+ public void LockRequirements(FormattedMessage requirements)
+ {
+ var tooltip = new Tooltip();
+ tooltip.SetMessage(requirements);
+ _lockStripe.TooltipSupplier = _ => tooltip;
+ _lockStripe.Visible = true;
+ Options.Visible = false;
+ }
+
+ // TODO: Subscribe to roletimers event. I am too lazy to do this RN But I doubt most people will notice fn
+ public void UnlockRequirements()
+ {
+ _lockStripe.Visible = false;
+ Options.Visible = true;
+ }
+
+ private Button GenerateButton(string text, int value)
+ {
+ return new Button
+ {
+ Text = text,
+ MinWidth = 90
+ };
+ }
+}
diff --git a/Content.Client/Preferences/UI/TraitPreferenceSelector.cs b/Content.Client/Preferences/UI/TraitPreferenceSelector.cs
new file mode 100644
index 0000000000..e9ce1a5e9b
--- /dev/null
+++ b/Content.Client/Preferences/UI/TraitPreferenceSelector.cs
@@ -0,0 +1,107 @@
+using System.Text;
+using Content.Client.Players.PlayTimeTracking;
+using Content.Client.Stylesheets;
+using Content.Shared.Customization.Systems;
+using Content.Shared.Preferences;
+using Content.Shared.Roles;
+using Content.Shared.Traits;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Shared.Configuration;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Preferences.UI;
+
+
+public sealed class TraitPreferenceSelector : Control
+{
+ public TraitPrototype Trait { get; }
+ private readonly Button _button;
+
+ public bool Preference
+ {
+ get => _button.Pressed;
+ set => _button.Pressed = value;
+ }
+
+ public event Action? PreferenceChanged;
+
+ public TraitPreferenceSelector(TraitPrototype trait, JobPrototype highJob,
+ HumanoidCharacterProfile profile, string style, IEntityManager entityManager,
+ IPrototypeManager prototypeManager,
+ IConfigurationManager configManager, CharacterRequirementsSystem characterRequirementsSystem,
+ JobRequirementsManager jobRequirementsManager)
+ {
+ Trait = trait;
+
+ // Create a checkbox to get the loadout
+ _button = new Button
+ {
+ VerticalAlignment = Control.VAlignment.Center,
+ ToggleMode = true,
+ StyleClasses = { StyleBase.ButtonOpenLeft },
+ Children =
+ {
+ new BoxContainer
+ {
+ Children =
+ {
+ new Label
+ {
+ Text = trait.Points.ToString(),
+ StyleClasses = { StyleBase.StyleClassLabelHeading },
+ MinWidth = 32,
+ MaxWidth = 32,
+ ClipText = true,
+ Margin = new Thickness(0, 0, 8, 0),
+ },
+ new Label { Text = Loc.GetString($"trait-name-{trait.ID}") },
+ },
+ },
+ },
+ };
+ _button.OnToggled += OnButtonToggled;
+ _button.AddStyleClass(style);
+
+ var tooltip = new StringBuilder();
+ // Add the loadout description to the tooltip if there is one
+ var desc = Loc.GetString($"trait-description-{trait.ID}");
+ if (!string.IsNullOrEmpty(desc) && desc != $"trait-description-{trait.ID}")
+ tooltip.Append(desc);
+
+
+ // Get requirement reasons
+ characterRequirementsSystem.CheckRequirementsValid(
+ trait.Requirements, highJob, profile, new Dictionary(),
+ jobRequirementsManager.IsWhitelisted(),
+ entityManager, prototypeManager, configManager,
+ out var reasons);
+
+ // Add requirement reasons to the tooltip
+ foreach (var reason in reasons)
+ tooltip.Append($"\n{reason.ToMarkup()}");
+
+ // Combine the tooltip and format it in the checkbox supplier
+ if (tooltip.Length > 0)
+ {
+ var formattedTooltip = new Tooltip();
+ formattedTooltip.SetMessage(FormattedMessage.FromMarkupPermissive(tooltip.ToString()));
+ _button.TooltipSupplier = _ => formattedTooltip;
+ }
+
+
+ // Add the loadout preview and the checkbox to the control
+ AddChild(new BoxContainer
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ Children = { _button },
+ });
+ }
+
+ private void OnButtonToggled(BaseButton.ButtonToggledEventArgs args)
+ {
+ PreferenceChanged?.Invoke(Preference);
+ }
+}
diff --git a/Content.Client/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveVisuals.cs b/Content.Client/Psionics/Glimmer/GlimmerReactiveVisuals.cs
similarity index 100%
rename from Content.Client/Nyanotrasen/Psionics/Glimmer/GlimmerReactiveVisuals.cs
rename to Content.Client/Psionics/Glimmer/GlimmerReactiveVisuals.cs
diff --git a/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsEUI.cs b/Content.Client/Psionics/UI/AcceptPsionicsEUI.cs
similarity index 100%
rename from Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsEUI.cs
rename to Content.Client/Psionics/UI/AcceptPsionicsEUI.cs
diff --git a/Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsWindow.cs b/Content.Client/Psionics/UI/AcceptPsionicsWindow.cs
similarity index 100%
rename from Content.Client/Nyanotrasen/Psionics/UI/AcceptPsionicsWindow.cs
rename to Content.Client/Psionics/UI/AcceptPsionicsWindow.cs
diff --git a/Content.Client/Pulling/PullingSystem.cs b/Content.Client/Pulling/PullingSystem.cs
deleted file mode 100644
index 556dadd00d..0000000000
--- a/Content.Client/Pulling/PullingSystem.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Content.Shared.Pulling;
-using Content.Shared.Pulling.Components;
-using JetBrains.Annotations;
-using Robust.Client.Physics;
-
-namespace Content.Client.Pulling
-{
- [UsedImplicitly]
- public sealed class PullingSystem : SharedPullingSystem
- {
- public override void Initialize()
- {
- base.Initialize();
-
- UpdatesAfter.Add(typeof(PhysicsSystem));
-
- SubscribeLocalEvent(OnPullableMove);
- SubscribeLocalEvent(OnPullableStopMove);
- }
- }
-}
diff --git a/Content.Client/RCD/AlignRCDConstruction.cs b/Content.Client/RCD/AlignRCDConstruction.cs
new file mode 100644
index 0000000000..da7b22c91a
--- /dev/null
+++ b/Content.Client/RCD/AlignRCDConstruction.cs
@@ -0,0 +1,122 @@
+using System.Numerics;
+using Content.Client.Gameplay;
+using Content.Shared.Hands.Components;
+using Content.Shared.Interaction;
+using Content.Shared.RCD.Components;
+using Content.Shared.RCD.Systems;
+using Robust.Client.Placement;
+using Robust.Client.Player;
+using Robust.Client.State;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+
+namespace Content.Client.RCD;
+
+public sealed class AlignRCDConstruction : PlacementMode
+{
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IMapManager _mapManager = default!;
+ [Dependency] private readonly SharedMapSystem _mapSystem = default!;
+ [Dependency] private readonly RCDSystem _rcdSystem = default!;
+ [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IStateManager _stateManager = default!;
+
+ private const float SearchBoxSize = 2f;
+ private const float PlaceColorBaseAlpha = 0.5f;
+
+ private EntityCoordinates _unalignedMouseCoords = default;
+
+ ///
+ /// This placement mode is not on the engine because it is content specific (i.e., for the RCD)
+ ///
+ public AlignRCDConstruction(PlacementManager pMan) : base(pMan)
+ {
+ var dependencies = IoCManager.Instance!;
+ _entityManager = dependencies.Resolve();
+ _mapManager = dependencies.Resolve();
+ _playerManager = dependencies.Resolve();
+ _stateManager = dependencies.Resolve();
+
+ _mapSystem = _entityManager.System();
+ _rcdSystem = _entityManager.System();
+ _transformSystem = _entityManager.System();
+
+ ValidPlaceColor = ValidPlaceColor.WithAlpha(PlaceColorBaseAlpha);
+ }
+
+ public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
+ {
+ _unalignedMouseCoords = ScreenToCursorGrid(mouseScreen);
+ MouseCoords = _unalignedMouseCoords.AlignWithClosestGridTile(SearchBoxSize, _entityManager, _mapManager);
+
+ var gridId = MouseCoords.GetGridUid(_entityManager);
+
+ if (!_entityManager.TryGetComponent(gridId, out var mapGrid))
+ return;
+
+ CurrentTile = _mapSystem.GetTileRef(gridId.Value, mapGrid, MouseCoords);
+
+ float tileSize = mapGrid.TileSize;
+ GridDistancing = tileSize;
+
+ if (pManager.CurrentPermission!.IsTile)
+ {
+ MouseCoords = new EntityCoordinates(MouseCoords.EntityId, new Vector2(CurrentTile.X + tileSize / 2,
+ CurrentTile.Y + tileSize / 2));
+ }
+ else
+ {
+ MouseCoords = new EntityCoordinates(MouseCoords.EntityId, new Vector2(CurrentTile.X + tileSize / 2 + pManager.PlacementOffset.X,
+ CurrentTile.Y + tileSize / 2 + pManager.PlacementOffset.Y));
+ }
+ }
+
+ public override bool IsValidPosition(EntityCoordinates position)
+ {
+ var player = _playerManager.LocalSession?.AttachedEntity;
+
+ // If the destination is out of interaction range, set the placer alpha to zero
+ if (!_entityManager.TryGetComponent(player, out var xform))
+ return false;
+
+ if (!xform.Coordinates.InRange(_entityManager, _transformSystem, position, SharedInteractionSystem.InteractionRange))
+ {
+ InvalidPlaceColor = InvalidPlaceColor.WithAlpha(0);
+ return false;
+ }
+
+ // Otherwise restore the alpha value
+ else
+ {
+ InvalidPlaceColor = InvalidPlaceColor.WithAlpha(PlaceColorBaseAlpha);
+ }
+
+ // Determine if player is carrying an RCD in their active hand
+ if (!_entityManager.TryGetComponent(player, out var hands))
+ return false;
+
+ var heldEntity = hands.ActiveHand?.HeldEntity;
+
+ if (!_entityManager.TryGetComponent(heldEntity, out var rcd))
+ return false;
+
+ // Retrieve the map grid data for the position
+ if (!_rcdSystem.TryGetMapGridData(position, out var mapGridData))
+ return false;
+
+ // Determine if the user is hovering over a target
+ var currentState = _stateManager.CurrentState;
+
+ if (currentState is not GameplayStateBase screen)
+ return false;
+
+ var target = screen.GetClickedEntity(_unalignedMouseCoords.ToMap(_entityManager, _transformSystem));
+
+ // Determine if the RCD operation is valid or not
+ if (!_rcdSystem.IsRCDOperationStillValid(heldEntity.Value, rcd, mapGridData.Value, target, player.Value, false))
+ return false;
+
+ return true;
+ }
+}
diff --git a/Content.Client/RCD/RCDConstructionGhostSystem.cs b/Content.Client/RCD/RCDConstructionGhostSystem.cs
new file mode 100644
index 0000000000..792916b892
--- /dev/null
+++ b/Content.Client/RCD/RCDConstructionGhostSystem.cs
@@ -0,0 +1,78 @@
+using Content.Shared.Hands.Components;
+using Content.Shared.Interaction;
+using Content.Shared.RCD;
+using Content.Shared.RCD.Components;
+using Content.Shared.RCD.Systems;
+using Robust.Client.Placement;
+using Robust.Client.Player;
+using Robust.Shared.Enums;
+
+namespace Content.Client.RCD;
+
+public sealed class RCDConstructionGhostSystem : EntitySystem
+{
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly RCDSystem _rcdSystem = default!;
+ [Dependency] private readonly IPlacementManager _placementManager = default!;
+
+ private string _placementMode = typeof(AlignRCDConstruction).Name;
+ private Direction _placementDirection = default;
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ // Get current placer data
+ var placerEntity = _placementManager.CurrentPermission?.MobUid;
+ var placerProto = _placementManager.CurrentPermission?.EntityType;
+ var placerIsRCD = HasComp(placerEntity);
+
+ // Exit if erasing or the current placer is not an RCD (build mode is active)
+ if (_placementManager.Eraser || (placerEntity != null && !placerIsRCD))
+ return;
+
+ // Determine if player is carrying an RCD in their active hand
+ var player = _playerManager.LocalSession?.AttachedEntity;
+
+ if (!TryComp(player, out var hands))
+ return;
+
+ var heldEntity = hands.ActiveHand?.HeldEntity;
+
+ if (!TryComp(heldEntity, out var rcd))
+ {
+ // If the player was holding an RCD, but is no longer, cancel placement
+ if (placerIsRCD)
+ _placementManager.Clear();
+
+ return;
+ }
+
+ // Update the direction the RCD prototype based on the placer direction
+ if (_placementDirection != _placementManager.Direction)
+ {
+ _placementDirection = _placementManager.Direction;
+ RaiseNetworkEvent(new RCDConstructionGhostRotationEvent(GetNetEntity(heldEntity.Value), _placementDirection));
+ }
+
+ // If the placer has not changed, exit
+ _rcdSystem.UpdateCachedPrototype(heldEntity.Value, rcd);
+
+ if (heldEntity == placerEntity && rcd.CachedPrototype.Prototype == placerProto)
+ return;
+
+ // Create a new placer
+ var newObjInfo = new PlacementInformation
+ {
+ MobUid = heldEntity.Value,
+ PlacementOption = _placementMode,
+ EntityType = rcd.CachedPrototype.Prototype,
+ Range = (int) Math.Ceiling(SharedInteractionSystem.InteractionRange),
+ IsTile = (rcd.CachedPrototype.Mode == RcdMode.ConstructTile),
+ UseEditorContext = false,
+ };
+
+ _placementManager.Clear();
+ _placementManager.BeginPlacing(newObjInfo);
+ }
+}
diff --git a/Content.Client/RCD/RCDMenu.xaml b/Content.Client/RCD/RCDMenu.xaml
new file mode 100644
index 0000000000..b3d5367a5f
--- /dev/null
+++ b/Content.Client/RCD/RCDMenu.xaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/RCD/RCDMenu.xaml.cs b/Content.Client/RCD/RCDMenu.xaml.cs
new file mode 100644
index 0000000000..51ec66ea44
--- /dev/null
+++ b/Content.Client/RCD/RCDMenu.xaml.cs
@@ -0,0 +1,174 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Popups;
+using Content.Shared.RCD;
+using Content.Shared.RCD.Components;
+using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using System.Numerics;
+
+namespace Content.Client.RCD;
+
+[GenerateTypedNameReferences]
+public sealed partial class RCDMenu : RadialMenu
+{
+ [Dependency] private readonly EntityManager _entManager = default!;
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+
+ private readonly SpriteSystem _spriteSystem;
+ private readonly SharedPopupSystem _popup;
+
+ public event Action>? SendRCDSystemMessageAction;
+
+ private EntityUid _owner;
+
+ public RCDMenu(EntityUid owner, RCDMenuBoundUserInterface bui)
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ _spriteSystem = _entManager.System();
+ _popup = _entManager.System();
+
+ _owner = owner;
+
+ // Find the main radial container
+ var main = FindControl("Main");
+
+ if (main == null)
+ return;
+
+ // Populate secondary radial containers
+ if (!_entManager.TryGetComponent(owner, out var rcd))
+ return;
+
+ foreach (var protoId in rcd.AvailablePrototypes)
+ {
+ if (!_protoManager.TryIndex(protoId, out var proto))
+ continue;
+
+ if (proto.Mode == RcdMode.Invalid)
+ continue;
+
+ var parent = FindControl(proto.Category);
+
+ if (parent == null)
+ continue;
+
+ var tooltip = Loc.GetString(proto.SetName);
+
+ if ((proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject) &&
+ proto.Prototype != null && _protoManager.TryIndex(proto.Prototype, out var entProto))
+ {
+ tooltip = Loc.GetString(entProto.Name);
+ }
+
+ tooltip = char.ToUpper(tooltip[0]) + tooltip.Remove(0, 1);
+
+ var button = new RCDMenuButton()
+ {
+ StyleClasses = { "RadialMenuButton" },
+ SetSize = new Vector2(64f, 64f),
+ ToolTip = tooltip,
+ ProtoId = protoId,
+ };
+
+ if (proto.Sprite != null)
+ {
+ var tex = new TextureRect()
+ {
+ VerticalAlignment = VAlignment.Center,
+ HorizontalAlignment = HAlignment.Center,
+ Texture = _spriteSystem.Frame0(proto.Sprite),
+ TextureScale = new Vector2(2f, 2f),
+ };
+
+ button.AddChild(tex);
+ }
+
+ parent.AddChild(button);
+
+ // Ensure that the button that transitions the menu to the associated category layer
+ // is visible in the main radial container (as these all start with Visible = false)
+ foreach (var child in main.Children)
+ {
+ var castChild = child as RadialMenuTextureButton;
+
+ if (castChild is not RadialMenuTextureButton)
+ continue;
+
+ if (castChild.TargetLayer == proto.Category)
+ {
+ castChild.Visible = true;
+ break;
+ }
+ }
+ }
+
+ // Set up menu actions
+ foreach (var child in Children)
+ AddRCDMenuButtonOnClickActions(child);
+
+ OnChildAdded += AddRCDMenuButtonOnClickActions;
+
+ SendRCDSystemMessageAction += bui.SendRCDSystemMessage;
+ }
+
+ private void AddRCDMenuButtonOnClickActions(Control control)
+ {
+ var radialContainer = control as RadialContainer;
+
+ if (radialContainer == null)
+ return;
+
+ foreach (var child in radialContainer.Children)
+ {
+ var castChild = child as RCDMenuButton;
+
+ if (castChild == null)
+ continue;
+
+ castChild.OnButtonUp += _ =>
+ {
+ SendRCDSystemMessageAction?.Invoke(castChild.ProtoId);
+
+ if (_playerManager.LocalSession?.AttachedEntity != null &&
+ _protoManager.TryIndex(castChild.ProtoId, out var proto))
+ {
+ var msg = Loc.GetString("rcd-component-change-mode", ("mode", Loc.GetString(proto.SetName)));
+
+ if (proto.Mode == RcdMode.ConstructTile || proto.Mode == RcdMode.ConstructObject)
+ {
+ var name = Loc.GetString(proto.SetName);
+
+ if (proto.Prototype != null &&
+ _protoManager.TryIndex(proto.Prototype, out var entProto))
+ name = entProto.Name;
+
+ msg = Loc.GetString("rcd-component-change-build-mode", ("name", name));
+ }
+
+ // Popup message
+ _popup.PopupClient(msg, _owner, _playerManager.LocalSession.AttachedEntity);
+ }
+
+ Close();
+ };
+ }
+ }
+}
+
+public sealed class RCDMenuButton : RadialMenuTextureButton
+{
+ public ProtoId ProtoId { get; set; }
+
+ public RCDMenuButton()
+ {
+
+ }
+}
diff --git a/Content.Client/RCD/RCDMenuBoundUserInterface.cs b/Content.Client/RCD/RCDMenuBoundUserInterface.cs
new file mode 100644
index 0000000000..a37dbcecf8
--- /dev/null
+++ b/Content.Client/RCD/RCDMenuBoundUserInterface.cs
@@ -0,0 +1,49 @@
+using Content.Shared.RCD;
+using Content.Shared.RCD.Components;
+using JetBrains.Annotations;
+using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.RCD;
+
+[UsedImplicitly]
+public sealed class RCDMenuBoundUserInterface : BoundUserInterface
+{
+ [Dependency] private readonly IClyde _displayManager = default!;
+ [Dependency] private readonly IInputManager _inputManager = default!;
+
+ private RCDMenu? _menu;
+
+ public RCDMenuBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ IoCManager.InjectDependencies(this);
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _menu = new(Owner, this);
+ _menu.OnClose += Close;
+
+ // Open the menu, centered on the mouse
+ var vpSize = _displayManager.ScreenSize;
+ _menu.OpenCenteredAt(_inputManager.MouseScreenPosition.Position / vpSize);
+ }
+
+ public void SendRCDSystemMessage(ProtoId protoId)
+ {
+ // A predicted message cannot be used here as the RCD UI is closed immediately
+ // after this message is sent, which will stop the server from receiving it
+ SendMessage(new RCDSystemMessage(protoId));
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing) return;
+
+ _menu?.Dispose();
+ }
+}
diff --git a/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs b/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs
index 8c721fa777..ef6283b6ff 100644
--- a/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs
+++ b/Content.Client/Radiation/Overlays/RadiationDebugOverlay.cs
@@ -4,13 +4,12 @@
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
-using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
namespace Content.Client.Radiation.Overlays;
public sealed class RadiationDebugOverlay : Overlay
{
- [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly RadiationSystem _radiation;
@@ -63,7 +62,7 @@ private void DrawScreenRays(OverlayDrawArgs args)
{
var gridUid = _entityManager.GetEntity(netGrid);
- if (!_mapManager.TryGetGrid(gridUid, out var grid))
+ if (!_entityManager.TryGetComponent(gridUid, out var grid))
continue;
foreach (var (tile, rads) in blockers)
@@ -88,7 +87,7 @@ private void DrawScreenResistance(OverlayDrawArgs args)
{
var gridUid = _entityManager.GetEntity(netGrid);
- if (!_mapManager.TryGetGrid(gridUid, out var grid))
+ if (!_entityManager.TryGetComponent(gridUid, out var grid))
continue;
if (query.TryGetComponent(gridUid, out var trs) && trs.MapID != args.MapId)
continue;
@@ -127,7 +126,7 @@ private void DrawWorld(in OverlayDrawArgs args)
{
var gridUid = _entityManager.GetEntity(netGrid);
- if (!_mapManager.TryGetGrid(gridUid, out var grid))
+ if (!_entityManager.TryGetComponent(gridUid, out var grid))
continue;
var (destTile, _) = blockers.Last();
var destWorld = grid.GridTileToWorldPos(destTile);
diff --git a/Content.Client/Remotes/EntitySystems/DoorRemoteSystem.cs b/Content.Client/Remotes/EntitySystems/DoorRemoteSystem.cs
new file mode 100644
index 0000000000..d6a9057f08
--- /dev/null
+++ b/Content.Client/Remotes/EntitySystems/DoorRemoteSystem.cs
@@ -0,0 +1,16 @@
+using Content.Client.Remote.UI;
+using Content.Client.Items;
+using Content.Shared.Remotes.EntitySystems;
+using Content.Shared.Remotes.Components;
+
+namespace Content.Client.Remotes.EntitySystems;
+
+public sealed class DoorRemoteSystem : SharedDoorRemoteSystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ Subs.ItemStatus(ent => new DoorRemoteStatusControl(ent));
+ }
+}
diff --git a/Content.Client/Remotes/UI/DoorRemoteStatusControl.cs b/Content.Client/Remotes/UI/DoorRemoteStatusControl.cs
new file mode 100644
index 0000000000..94589ecdaa
--- /dev/null
+++ b/Content.Client/Remotes/UI/DoorRemoteStatusControl.cs
@@ -0,0 +1,46 @@
+using Content.Client.Message;
+using Content.Client.Stylesheets;
+using Content.Shared.Remotes.Components;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Remote.UI;
+
+public sealed class DoorRemoteStatusControl : Control
+{
+ private readonly Entity _entity;
+ private readonly RichTextLabel _label;
+
+ // set to toggle bolts initially just so that it updates on first pickup of remote
+ private OperatingMode PrevOperatingMode = OperatingMode.placeholderForUiUpdates;
+
+ public DoorRemoteStatusControl(Entity entity)
+ {
+ _entity = entity;
+ _label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
+ AddChild(_label);
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ // only updates the UI if any of the details are different than they previously were
+ if (PrevOperatingMode == _entity.Comp.Mode)
+ return;
+
+ PrevOperatingMode = _entity.Comp.Mode;
+
+ // Update current volume and injector state
+ var modeStringLocalized = Loc.GetString(_entity.Comp.Mode switch
+ {
+ OperatingMode.OpenClose => "door-remote-open-close-text",
+ OperatingMode.ToggleBolts => "door-remote-toggle-bolt-text",
+ OperatingMode.ToggleEmergencyAccess => "door-remote-emergency-access-text",
+ _ => "door-remote-invalid-text"
+ });
+
+ _label.SetMarkup(Loc.GetString("door-remote-mode-label", ("modeString", modeStringLocalized)));
+ }
+}
diff --git a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs
index 86d113defb..2fa862f3df 100644
--- a/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs
+++ b/Content.Client/Replay/Spectator/ReplaySpectatorSystem.Blockers.cs
@@ -3,7 +3,7 @@
using Content.Shared.Inventory.Events;
using Content.Shared.Item;
using Content.Shared.Movement.Events;
-using Content.Shared.Physics.Pull;
+using Content.Shared.Movement.Pulling.Events;
using Content.Shared.Throwing;
namespace Content.Client.Replay.Spectator;
diff --git a/Content.Client/Revenant/RevenantSystem.cs b/Content.Client/Revenant/RevenantSystem.cs
index 6e7d0d2a1b..49d29d8a5f 100644
--- a/Content.Client/Revenant/RevenantSystem.cs
+++ b/Content.Client/Revenant/RevenantSystem.cs
@@ -1,3 +1,5 @@
+using Content.Client.Alerts;
+using Content.Shared.Alert;
using Content.Shared.Revenant;
using Content.Shared.Revenant.Components;
using Robust.Client.GameObjects;
@@ -13,6 +15,7 @@ public override void Initialize()
base.Initialize();
SubscribeLocalEvent(OnAppearanceChange);
+ SubscribeLocalEvent(OnUpdateAlert);
}
private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref AppearanceChangeEvent args)
@@ -36,4 +39,16 @@ private void OnAppearanceChange(EntityUid uid, RevenantComponent component, ref
args.Sprite.LayerSetState(0, component.State);
}
}
+
+ private void OnUpdateAlert(Entity ent, ref UpdateAlertSpriteEvent args)
+ {
+ if (args.Alert.AlertType != AlertType.Essence)
+ return;
+
+ var sprite = args.SpriteViewEnt.Comp;
+ var essence = Math.Clamp(ent.Comp.Essence.Int(), 0, 999);
+ sprite.LayerSetState(RevenantVisualLayers.Digit1, $"{(essence / 100) % 10}");
+ sprite.LayerSetState(RevenantVisualLayers.Digit2, $"{(essence / 10) % 10}");
+ sprite.LayerSetState(RevenantVisualLayers.Digit3, $"{essence % 10}");
+ }
}
diff --git a/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs b/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs
index 035823af43..284c668190 100644
--- a/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs
+++ b/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs
@@ -66,11 +66,11 @@ public BaseShuttleControl(float minRange, float maxRange, float range) : base(mi
protected void DrawData(DrawingHandleScreen handle, string text)
{
- var coordsDimensions = handle.GetDimensions(Font, text, UIScale);
+ var coordsDimensions = handle.GetDimensions(Font, text, 1f);
const float coordsMargins = 5f;
handle.DrawString(Font,
- new Vector2(coordsMargins, Height) - new Vector2(0f, coordsDimensions.Y + coordsMargins),
+ new Vector2(coordsMargins, PixelHeight) - new Vector2(0f, coordsDimensions.Y + coordsMargins),
text,
Color.FromSrgb(IFFComponent.SelfColor));
}
@@ -88,7 +88,6 @@ protected void DrawCircles(DrawingHandleScreen handle)
var cornerDistance = MathF.Sqrt(WorldRange * WorldRange + WorldRange * WorldRange);
var origin = ScalePosition(-new Vector2(Offset.X, -Offset.Y));
- var distOffset = -24f;
for (var radius = minDistance; radius <= maxDistance; radius *= EquatorialMultiplier)
{
diff --git a/Content.Client/Shuttles/UI/MapScreen.xaml.cs b/Content.Client/Shuttles/UI/MapScreen.xaml.cs
index 8430699bae..10800b8c5f 100644
--- a/Content.Client/Shuttles/UI/MapScreen.xaml.cs
+++ b/Content.Client/Shuttles/UI/MapScreen.xaml.cs
@@ -5,6 +5,7 @@
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems;
using Content.Shared.Shuttles.UI.MapObjects;
+using Content.Shared.Timing;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
@@ -38,16 +39,11 @@ public sealed partial class MapScreen : BoxContainer
private EntityUid? _shuttleEntity;
private FTLState _state;
- private float _ftlDuration;
+ private StartEndTime _ftlTime;
private List _beacons = new();
private List _exclusions = new();
- ///
- /// When the next FTL state change happens.
- ///
- private TimeSpan _nextFtlTime;
-
private TimeSpan _nextPing;
private TimeSpan _pingCooldown = TimeSpan.FromSeconds(3);
private TimeSpan _nextMapDequeue;
@@ -114,8 +110,7 @@ public void UpdateState(ShuttleMapInterfaceState state)
_beacons = state.Destinations;
_exclusions = state.Exclusions;
_state = state.FTLState;
- _ftlDuration = state.FTLDuration;
- _nextFtlTime = _timing.CurTime + TimeSpan.FromSeconds(_ftlDuration);
+ _ftlTime = state.FTLTime;
MapRadar.InFtl = true;
MapFTLState.Text = Loc.GetString($"shuttle-console-ftl-state-{_state.ToString()}");
@@ -268,9 +263,10 @@ private void RebuildMapObjects()
while (mapComps.MoveNext(out var mapComp, out var mapXform, out var mapMetadata))
{
- if (!_shuttles.CanFTLTo(_shuttleEntity.Value, mapComp.MapId))
- continue;
-
+ if (_console != null && !_shuttles.CanFTLTo(_shuttleEntity.Value, mapComp.MapId, _console.Value))
+ {
+ continue;
+ }
var mapName = mapMetadata.EntityName;
if (string.IsNullOrEmpty(mapName))
@@ -315,7 +311,6 @@ private void RebuildMapObjects()
};
_mapHeadings.Add(mapComp.MapId, gridContents);
-
foreach (var grid in _mapManager.GetAllMapGrids(mapComp.MapId))
{
_entManager.TryGetComponent(grid.Owner, out IFFComponent? iffComp);
@@ -332,8 +327,8 @@ private void RebuildMapObjects()
{
AddMapObject(mapComp.MapId, gridObj);
}
- else if (iffComp == null ||
- (iffComp.Flags & IFFFlags.Hide) == 0x0)
+ else if (!_shuttles.IsBeaconMap(_mapManager.GetMapEntityId(mapComp.MapId)) && (iffComp == null ||
+ (iffComp.Flags & IFFFlags.Hide) == 0x0))
{
_pendingMapObjects.Add((mapComp.MapId, gridObj));
}
@@ -511,20 +506,8 @@ protected override void FrameUpdate(FrameEventArgs args)
MapRebuildButton.Disabled = false;
}
- var ftlDiff = (float) (_nextFtlTime - _timing.CurTime).TotalSeconds;
-
- float ftlRatio;
-
- if (_ftlDuration.Equals(0f))
- {
- ftlRatio = 1f;
- }
- else
- {
- ftlRatio = Math.Clamp(1f - (ftlDiff / _ftlDuration), 0f, 1f);
- }
-
- FTLBar.Value = ftlRatio;
+ var progress = _ftlTime.ProgressAt(curTime);
+ FTLBar.Value = float.IsFinite(progress) ? progress : 1;
}
protected override void Draw(DrawingHandleScreen handle)
diff --git a/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs b/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs
index 2ce1906d3d..2f35a8dffd 100644
--- a/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs
+++ b/Content.Client/Shuttles/UI/ShuttleMapControl.xaml.cs
@@ -116,7 +116,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args)
var mapTransform = Matrix3.CreateInverseTransform(Offset, Angle.Zero);
- if (beaconsOnly && TryGetBeacon(_beacons, mapTransform, args.RelativePosition, PixelRect, out var foundBeacon, out _))
+ if (beaconsOnly && TryGetBeacon(_beacons, mapTransform, args.RelativePixelPosition, PixelRect, out var foundBeacon, out _))
{
RequestBeaconFTL?.Invoke(foundBeacon.Entity, _ftlAngle);
}
@@ -206,7 +206,8 @@ private void DrawParallax(DrawingHandleScreen handle)
private List GetViewportMapObjects(Matrix3 matty, List mapObjects)
{
var results = new List();
- var viewBox = SizeBox.Scale(1.2f);
+ var enlargement = new Vector2i((int) (16 * UIScale), (int) (16 * UIScale));
+ var viewBox = new UIBox2i(Vector2i.Zero - enlargement, PixelSize + enlargement);
foreach (var mapObj in mapObjects)
{
@@ -398,8 +399,8 @@ protected override void Draw(DrawingHandleScreen handle)
foreach (var (gridUiPos, iffText) in sendStrings)
{
- var textWidth = handle.GetDimensions(_font, iffText, UIScale);
- handle.DrawString(_font, gridUiPos + textWidth with { X = -textWidth.X / 2f }, iffText, adjustedColor);
+ var textWidth = handle.GetDimensions(_font, iffText, 1f);
+ handle.DrawString(_font, gridUiPos + textWidth with { X = -textWidth.X / 2f, Y = textWidth.Y * UIScale }, iffText, adjustedColor);
}
}
@@ -587,7 +588,7 @@ private bool TryGetBeacon(IEnumerable mapObjects, Matrix3 mapTransfo
var distance = (localPos - mousePos).Length();
- if (distance > BeaconSnapRange ||
+ if (distance > BeaconSnapRange * UIScale ||
distance > nearestValue)
{
continue;
diff --git a/Content.Client/StatusIcon/StatusIconOverlay.cs b/Content.Client/StatusIcon/StatusIconOverlay.cs
index 1cfb4c2a55..f8381afdbe 100644
--- a/Content.Client/StatusIcon/StatusIconOverlay.cs
+++ b/Content.Client/StatusIcon/StatusIconOverlay.cs
@@ -3,9 +3,9 @@
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
-using System.Numerics;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
+using System.Numerics;
namespace Content.Client.StatusIcon;
@@ -18,7 +18,7 @@ public sealed class StatusIconOverlay : Overlay
private readonly SpriteSystem _sprite;
private readonly TransformSystem _transform;
private readonly StatusIconSystem _statusIcon;
- private readonly ShaderInstance _shader;
+ private readonly ShaderInstance _unshadedShader;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
@@ -29,7 +29,7 @@ internal StatusIconOverlay()
_sprite = _entity.System();
_transform = _entity.System();
_statusIcon = _entity.System();
- _shader = _prototype.Index("unshaded").Instance();
+ _unshadedShader = _prototype.Index("unshaded").Instance();
}
protected override void Draw(in OverlayDrawArgs args)
@@ -42,8 +42,6 @@ protected override void Draw(in OverlayDrawArgs args)
var scaleMatrix = Matrix3.CreateScale(new Vector2(1, 1));
var rotationMatrix = Matrix3.CreateRotation(-eyeRot);
- handle.UseShader(_shader);
-
var query = _entity.AllEntityQueryEnumerator();
while (query.MoveNext(out var uid, out var comp, out var sprite, out var xform, out var meta))
{
@@ -111,11 +109,16 @@ protected override void Draw(in OverlayDrawArgs args)
}
+ if (proto.IsShaded)
+ handle.UseShader(null);
+ else
+ handle.UseShader(_unshadedShader);
+
var position = new Vector2(xOffset, yOffset);
handle.DrawTexture(texture, position);
}
- }
- handle.UseShader(null);
+ handle.UseShader(null);
+ }
}
}
diff --git a/Content.Client/Store/Ui/StoreBoundUserInterface.cs b/Content.Client/Store/Ui/StoreBoundUserInterface.cs
index b549918d7c..88ad0e3de8 100644
--- a/Content.Client/Store/Ui/StoreBoundUserInterface.cs
+++ b/Content.Client/Store/Ui/StoreBoundUserInterface.cs
@@ -1,22 +1,27 @@
using Content.Shared.Store;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
using System.Linq;
-using System.Threading;
-using Serilog;
-using Timer = Robust.Shared.Timing.Timer;
+using Robust.Shared.Prototypes;
namespace Content.Client.Store.Ui;
[UsedImplicitly]
public sealed class StoreBoundUserInterface : BoundUserInterface
{
+ private IPrototypeManager _prototypeManager = default!;
+
[ViewVariables]
private StoreMenu? _menu;
[ViewVariables]
private string _windowName = Loc.GetString("store-ui-default-title");
+ [ViewVariables]
+ private string _search = string.Empty;
+
+ [ViewVariables]
+ private HashSet _listings = new();
+
public StoreBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
@@ -36,7 +41,7 @@ protected override void Open()
_menu.OnCategoryButtonPressed += (_, category) =>
{
_menu.CurrentCategory = category;
- SendMessage(new StoreRequestUpdateInterfaceMessage());
+ _menu?.UpdateListing();
};
_menu.OnWithdrawAttempt += (_, type, amount) =>
@@ -44,9 +49,10 @@ protected override void Open()
SendMessage(new StoreRequestWithdrawMessage(type, amount));
};
- _menu.OnRefreshButtonPressed += (_) =>
+ _menu.SearchTextUpdated += (_, search) =>
{
- SendMessage(new StoreRequestUpdateInterfaceMessage());
+ _search = search.Trim().ToLowerInvariant();
+ UpdateListingsWithSearchFilter();
};
_menu.OnRefundAttempt += (_) =>
@@ -64,10 +70,10 @@ protected override void UpdateState(BoundUserInterfaceState state)
switch (state)
{
case StoreUpdateState msg:
- _menu.UpdateBalance(msg.Balance);
- _menu.PopulateStoreCategoryButtons(msg.Listings);
+ _listings = msg.Listings;
- _menu.UpdateListing(msg.Listings.ToList());
+ _menu.UpdateBalance(msg.Balance);
+ UpdateListingsWithSearchFilter();
_menu.SetFooterVisibility(msg.ShowFooter);
_menu.UpdateRefund(msg.AllowRefund);
break;
@@ -89,4 +95,19 @@ protected override void Dispose(bool disposing)
_menu?.Close();
_menu?.Dispose();
}
+
+ private void UpdateListingsWithSearchFilter()
+ {
+ if (_menu == null)
+ return;
+
+ var filteredListings = new HashSet(_listings);
+ if (!string.IsNullOrEmpty(_search))
+ {
+ filteredListings.RemoveWhere(listingData => !ListingLocalisationHelpers.GetLocalisedNameOrEntityName(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search) &&
+ !ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(listingData, _prototypeManager).Trim().ToLowerInvariant().Contains(_search));
+ }
+ _menu.PopulateStoreCategoryButtons(filteredListings);
+ _menu.UpdateListing(filteredListings.ToList());
+ }
}
diff --git a/Content.Client/Store/Ui/StoreListingControl.xaml b/Content.Client/Store/Ui/StoreListingControl.xaml
index aefeec17cc..12b4d7b5b3 100644
--- a/Content.Client/Store/Ui/StoreListingControl.xaml
+++ b/Content.Client/Store/Ui/StoreListingControl.xaml
@@ -15,6 +15,7 @@
Margin="0,0,4,0"
MinSize="48 48"
Stretch="KeepAspectCentered" />
+
diff --git a/Content.Client/Store/Ui/StoreListingControl.xaml.cs b/Content.Client/Store/Ui/StoreListingControl.xaml.cs
index bb600588e0..030f07dc7c 100644
--- a/Content.Client/Store/Ui/StoreListingControl.xaml.cs
+++ b/Content.Client/Store/Ui/StoreListingControl.xaml.cs
@@ -1,25 +1,91 @@
+using Content.Client.GameTicking.Managers;
+using Content.Shared.Store;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Graphics;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
namespace Content.Client.Store.Ui;
[GenerateTypedNameReferences]
public sealed partial class StoreListingControl : Control
{
- public StoreListingControl(string itemName, string itemDescription,
- string price, bool canBuy, Texture? texture = null)
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IEntityManager _entity = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ private readonly ClientGameTicker _ticker;
+
+ private readonly ListingData _data;
+
+ private readonly bool _hasBalance;
+ private readonly string _price;
+ public StoreListingControl(ListingData data, string price, bool hasBalance, Texture? texture = null)
{
+ IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
- StoreItemName.Text = itemName;
- StoreItemDescription.SetMessage(itemDescription);
+ _ticker = _entity.System();
+
+ _data = data;
+ _hasBalance = hasBalance;
+ _price = price;
- StoreItemBuyButton.Text = price;
- StoreItemBuyButton.Disabled = !canBuy;
+ StoreItemName.Text = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype);
+ StoreItemDescription.SetMessage(ListingLocalisationHelpers.GetLocalisedDescriptionOrEntityDescription(_data, _prototype));
+
+ UpdateBuyButtonText();
+ StoreItemBuyButton.Disabled = !CanBuy();
StoreItemTexture.Texture = texture;
}
+
+ private bool CanBuy()
+ {
+ if (!_hasBalance)
+ return false;
+
+ var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
+ if (_data.RestockTime > stationTime)
+ return false;
+
+ return true;
+ }
+
+ private void UpdateBuyButtonText()
+ {
+ var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
+ if (_data.RestockTime > stationTime)
+ {
+ var timeLeftToBuy = stationTime - _data.RestockTime;
+ StoreItemBuyButton.Text = timeLeftToBuy.Duration().ToString(@"mm\:ss");
+ }
+ else
+ {
+ StoreItemBuyButton.Text = _price;
+ }
+ }
+
+ private void UpdateName()
+ {
+ var name = ListingLocalisationHelpers.GetLocalisedNameOrEntityName(_data, _prototype);
+
+ var stationTime = _timing.CurTime.Subtract(_ticker.RoundStartTimeSpan);
+ if (_data.RestockTime > stationTime)
+ {
+ name += Loc.GetString("store-ui-button-out-of-stock");
+ }
+
+ StoreItemName.Text = name;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ UpdateBuyButtonText();
+ UpdateName();
+ StoreItemBuyButton.Disabled = !CanBuy();
+ }
}
diff --git a/Content.Client/Store/Ui/StoreMenu.xaml b/Content.Client/Store/Ui/StoreMenu.xaml
index 4b38352a44..843c9dc029 100644
--- a/Content.Client/Store/Ui/StoreMenu.xaml
+++ b/Content.Client/Store/Ui/StoreMenu.xaml
@@ -12,11 +12,6 @@
HorizontalAlignment="Left"
Access="Public"
HorizontalExpand="True" />
-
-
+
+
diff --git a/Content.Client/Store/Ui/StoreMenu.xaml.cs b/Content.Client/Store/Ui/StoreMenu.xaml.cs
index 5dc1ab246b..b7a2c285fe 100644
--- a/Content.Client/Store/Ui/StoreMenu.xaml.cs
+++ b/Content.Client/Store/Ui/StoreMenu.xaml.cs
@@ -1,7 +1,5 @@
using System.Linq;
-using System.Threading;
using Content.Client.Actions;
-using Content.Client.GameTicking.Managers;
using Content.Client.Message;
using Content.Shared.FixedPoint;
using Content.Shared.Store;
@@ -12,7 +10,6 @@
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
namespace Content.Client.Store.Ui;
@@ -21,45 +18,42 @@ public sealed partial class StoreMenu : DefaultWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
- private readonly ClientGameTicker _gameTicker;
private StoreWithdrawWindow? _withdrawWindow;
+ public event EventHandler? SearchTextUpdated;
public event Action? OnListingButtonPressed;
public event Action? OnCategoryButtonPressed;
public event Action? OnWithdrawAttempt;
- public event Action? OnRefreshButtonPressed;
public event Action? OnRefundAttempt;
- public Dictionary Balance = new();
+ public Dictionary, FixedPoint2> Balance = new();
public string CurrentCategory = string.Empty;
+ private List _cachedListings = new();
+
public StoreMenu(string name)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
- _gameTicker = _entitySystem.GetEntitySystem();
-
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
- RefreshButton.OnButtonDown += OnRefreshButtonDown;
RefundButton.OnButtonDown += OnRefundButtonDown;
+ SearchBar.OnTextChanged += _ => SearchTextUpdated?.Invoke(this, SearchBar.Text);
if (Window != null)
Window.Title = name;
}
- public void UpdateBalance(Dictionary balance)
+ public void UpdateBalance(Dictionary, FixedPoint2> balance)
{
Balance = balance;
var currency = balance.ToDictionary(type =>
- (type.Key, type.Value), type => _prototypeManager.Index(type.Key));
+ (type.Key, type.Value), type => _prototypeManager.Index(type.Key));
var balanceStr = string.Empty;
- foreach (var ((type, amount),proto) in currency)
+ foreach (var ((_, amount), proto) in currency)
{
balanceStr += Loc.GetString("store-ui-balance-display", ("amount", amount),
("currency", Loc.GetString(proto.DisplayName, ("amount", 1))));
@@ -79,8 +73,13 @@ public void UpdateBalance(Dictionary balance)
public void UpdateListing(List listings)
{
- var sorted = listings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
+ _cachedListings = listings;
+ UpdateListing();
+ }
+ public void UpdateListing()
+ {
+ var sorted = _cachedListings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
// should probably chunk these out instead. to-do if this clogs the internet tubes.
// maybe read clients prototypes instead?
@@ -96,12 +95,6 @@ public void SetFooterVisibility(bool visible)
TraitorFooter.Visible = visible;
}
-
- private void OnRefreshButtonDown(BaseButton.ButtonEventArgs args)
- {
- OnRefreshButtonPressed?.Invoke(args);
- }
-
private void OnWithdrawButtonDown(BaseButton.ButtonEventArgs args)
{
// check if window is already open
@@ -129,10 +122,8 @@ private void AddListingGui(ListingData listing)
if (!listing.Categories.Contains(CurrentCategory))
return;
- var listingName = Loc.GetString(listing.Name);
- var listingDesc = Loc.GetString(listing.Description);
var listingPrice = listing.Cost;
- var canBuy = CanBuyListing(Balance, listingPrice);
+ var hasBalance = HasListingPrice(Balance, listingPrice);
var spriteSys = _entityManager.EntitySysManager.GetEntitySystem();
@@ -144,12 +135,6 @@ private void AddListingGui(ListingData listing)
{
if (texture == null)
texture = spriteSys.GetPrototypeIcon(listing.ProductEntity).Default;
-
- var proto = _prototypeManager.Index(listing.ProductEntity);
- if (listingName == string.Empty)
- listingName = proto.Name;
- if (listingDesc == string.Empty)
- listingDesc = proto.Description;
}
else if (listing.ProductAction != null)
{
@@ -160,39 +145,15 @@ private void AddListingGui(ListingData listing)
texture = spriteSys.Frame0(action.Icon);
}
}
- var listingInStock = ListingInStock(listing);
- if (listingInStock != GetListingPriceString(listing))
- {
- listingName += " (Out of stock)";
- canBuy = false;
- }
- var newListing = new StoreListingControl(listingName, listingDesc, listingInStock, canBuy, texture);
+ var newListing = new StoreListingControl(listing, GetListingPriceString(listing), hasBalance, texture);
newListing.StoreItemBuyButton.OnButtonDown += args
=> OnListingButtonPressed?.Invoke(args, listing);
StoreListingsContainer.AddChild(newListing);
}
- ///
- /// Return time until available or the cost.
- ///
- ///
- ///
- public string ListingInStock(ListingData listing)
- {
- var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
-
- TimeSpan restockTimeSpan = TimeSpan.FromMinutes(listing.RestockTime);
- if (restockTimeSpan > stationTime)
- {
- var timeLeftToBuy = stationTime - restockTimeSpan;
- return timeLeftToBuy.Duration().ToString(@"mm\:ss");
- }
-
- return GetListingPriceString(listing);
- }
- public bool CanBuyListing(Dictionary currency, Dictionary price)
+ public bool HasListingPrice(Dictionary, FixedPoint2> currency, Dictionary, FixedPoint2> price)
{
foreach (var type in price)
{
@@ -214,7 +175,7 @@ public string GetListingPriceString(ListingData listing)
{
foreach (var (type, amount) in listing.Cost)
{
- var currency = _prototypeManager.Index(type);
+ var currency = _prototypeManager.Index(type);
text += Loc.GetString("store-ui-price-display", ("amount", amount),
("currency", Loc.GetString(currency.DisplayName, ("amount", amount))));
}
@@ -235,7 +196,7 @@ public void PopulateStoreCategoryButtons(HashSet listings)
{
foreach (var cat in listing.Categories)
{
- var proto = _prototypeManager.Index(cat);
+ var proto = _prototypeManager.Index(cat);
if (!allCategories.Contains(proto))
allCategories.Add(proto);
}
@@ -243,20 +204,28 @@ public void PopulateStoreCategoryButtons(HashSet listings)
allCategories = allCategories.OrderBy(c => c.Priority).ToList();
+ // This will reset the Current Category selection if nothing matches the search.
+ if (allCategories.All(category => category.ID != CurrentCategory))
+ CurrentCategory = string.Empty;
+
if (CurrentCategory == string.Empty && allCategories.Count > 0)
CurrentCategory = allCategories.First().ID;
- if (allCategories.Count <= 1)
- return;
-
CategoryListContainer.Children.Clear();
+ if (allCategories.Count < 1)
+ return;
+ var group = new ButtonGroup();
foreach (var proto in allCategories)
{
var catButton = new StoreCategoryButton
{
Text = Loc.GetString(proto.Name),
- Id = proto.ID
+ Id = proto.ID,
+ Pressed = proto.ID == CurrentCategory,
+ Group = group,
+ ToggleMode = true,
+ StyleClasses = { "OpenBoth" }
};
catButton.OnPressed += args => OnCategoryButtonPressed?.Invoke(args, catButton.Id);
@@ -272,7 +241,7 @@ public override void Close()
public void UpdateRefund(bool allowRefund)
{
- RefundButton.Disabled = !allowRefund;
+ RefundButton.Visible = allowRefund;
}
private sealed class StoreCategoryButton : Button
diff --git a/Content.Client/Store/Ui/StoreWithdrawWindow.xaml.cs b/Content.Client/Store/Ui/StoreWithdrawWindow.xaml.cs
index db0e7e6807..7936855756 100644
--- a/Content.Client/Store/Ui/StoreWithdrawWindow.xaml.cs
+++ b/Content.Client/Store/Ui/StoreWithdrawWindow.xaml.cs
@@ -28,12 +28,12 @@ public StoreWithdrawWindow()
IoCManager.InjectDependencies(this);
}
- public void CreateCurrencyButtons(Dictionary balance)
+ public void CreateCurrencyButtons(Dictionary, FixedPoint2> balance)
{
_validCurrencies.Clear();
foreach (var currency in balance)
{
- if (!_prototypeManager.TryIndex(currency.Key, out var proto))
+ if (!_prototypeManager.TryIndex(currency.Key, out var proto))
continue;
_validCurrencies.Add(currency.Value, proto);
diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs
index 13ba259dbc..a10e3eb592 100644
--- a/Content.Client/Stylesheets/StyleNano.cs
+++ b/Content.Client/Stylesheets/StyleNano.cs
@@ -45,6 +45,7 @@ public sealed class StyleNano : StyleBase
public const string StyleClassBorderedWindowPanel = "BorderedWindowPanel";
public const string StyleClassInventorySlotBackground = "InventorySlotBackground";
public const string StyleClassHandSlotHighlight = "HandSlotHighlight";
+ public const string StyleClassChatPanel = "ChatPanel";
public const string StyleClassChatSubPanel = "ChatSubPanel";
public const string StyleClassTransparentBorderedWindowPanel = "TransparentBorderedWindowPanel";
public const string StyleClassHotbarPanel = "HotbarPanel";
@@ -144,6 +145,8 @@ public sealed class StyleNano : StyleBase
public const string StyleClassButtonColorRed = "ButtonColorRed";
public const string StyleClassButtonColorGreen = "ButtonColorGreen";
+ public static readonly Color ChatBackgroundColor = Color.FromHex("#25252ADD");
+
public override Stylesheet Stylesheet { get; }
public StyleNano(IResourceCache resCache) : base(resCache)
@@ -290,7 +293,7 @@ public StyleNano(IResourceCache resCache) : base(resCache)
var buttonTex = resCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var topButtonBase = new StyleBoxTexture
{
- Texture = buttonTex,
+ Texture = buttonTex,
};
topButtonBase.SetPatchMargin(StyleBox.Margin.All, 10);
topButtonBase.SetPadding(StyleBox.Margin.All, 0);
@@ -298,19 +301,19 @@ public StyleNano(IResourceCache resCache) : base(resCache)
var topButtonOpenRight = new StyleBoxTexture(topButtonBase)
{
- Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(0, 0), new Vector2(14, 24))),
+ Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(0, 0), new Vector2(14, 24))),
};
topButtonOpenRight.SetPatchMargin(StyleBox.Margin.Right, 0);
var topButtonOpenLeft = new StyleBoxTexture(topButtonBase)
{
- Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(14, 24))),
+ Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(14, 24))),
};
topButtonOpenLeft.SetPatchMargin(StyleBox.Margin.Left, 0);
var topButtonSquare = new StyleBoxTexture(topButtonBase)
{
- Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(3, 24))),
+ Texture = new AtlasTexture(buttonTex, UIBox2.FromDimensions(new Vector2(10, 0), new Vector2(3, 24))),
};
topButtonSquare.SetPatchMargin(StyleBox.Margin.Horizontal, 0);
@@ -346,12 +349,16 @@ public StyleNano(IResourceCache resCache) : base(resCache)
lineEdit.SetPatchMargin(StyleBox.Margin.All, 3);
lineEdit.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
- var chatSubBGTex = resCache.GetTexture("/Textures/Interface/Nano/chat_sub_background.png");
- var chatSubBG = new StyleBoxTexture
+ var chatBg = new StyleBoxFlat
+ {
+ BackgroundColor = ChatBackgroundColor
+ };
+
+ var chatSubBg = new StyleBoxFlat
{
- Texture = chatSubBGTex,
+ BackgroundColor = ChatBackgroundColor,
};
- chatSubBG.SetPatchMargin(StyleBox.Margin.All, 2);
+ chatSubBg.SetContentMarginOverride(StyleBox.Margin.All, 2);
var actionSearchBoxTex = resCache.GetTexture("/Textures/Interface/Nano/black_panel_dark_thin_border.png");
var actionSearchBox = new StyleBoxTexture
@@ -368,9 +375,9 @@ public StyleNano(IResourceCache resCache) : base(resCache)
};
tabContainerPanel.SetPatchMargin(StyleBox.Margin.All, 2);
- var tabContainerBoxActive = new StyleBoxFlat {BackgroundColor = new Color(64, 64, 64)};
+ var tabContainerBoxActive = new StyleBoxFlat { BackgroundColor = new Color(64, 64, 64) };
tabContainerBoxActive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
- var tabContainerBoxInactive = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 32)};
+ var tabContainerBoxInactive = new StyleBoxFlat { BackgroundColor = new Color(32, 32, 32) };
tabContainerBoxInactive.SetContentMarginOverride(StyleBox.Margin.Horizontal, 5);
var progressBarBackground = new StyleBoxFlat
@@ -409,21 +416,21 @@ public StyleNano(IResourceCache resCache) : base(resCache)
// Placeholder
var placeholderTexture = resCache.GetTexture("/Textures/Interface/Nano/placeholder.png");
- var placeholder = new StyleBoxTexture {Texture = placeholderTexture};
+ var placeholder = new StyleBoxTexture { Texture = placeholderTexture };
placeholder.SetPatchMargin(StyleBox.Margin.All, 19);
placeholder.SetExpandMargin(StyleBox.Margin.All, -5);
placeholder.Mode = StyleBoxTexture.StretchMode.Tile;
- var itemListBackgroundSelected = new StyleBoxFlat {BackgroundColor = new Color(75, 75, 75)};
+ var itemListBackgroundSelected = new StyleBoxFlat { BackgroundColor = new Color(75, 75, 75) };
itemListBackgroundSelected.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
itemListBackgroundSelected.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
- var itemListItemBackgroundDisabled = new StyleBoxFlat {BackgroundColor = new Color(10, 10, 10)};
+ var itemListItemBackgroundDisabled = new StyleBoxFlat { BackgroundColor = new Color(10, 10, 10) };
itemListItemBackgroundDisabled.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
itemListItemBackgroundDisabled.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
- var itemListItemBackground = new StyleBoxFlat {BackgroundColor = new Color(55, 55, 55)};
+ var itemListItemBackground = new StyleBoxFlat { BackgroundColor = new Color(55, 55, 55) };
itemListItemBackground.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
itemListItemBackground.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
- var itemListItemBackgroundTransparent = new StyleBoxFlat {BackgroundColor = Color.Transparent};
+ var itemListItemBackgroundTransparent = new StyleBoxFlat { BackgroundColor = Color.Transparent };
itemListItemBackgroundTransparent.SetContentMarginOverride(StyleBox.Margin.Vertical, 2);
itemListItemBackgroundTransparent.SetContentMarginOverride(StyleBox.Margin.Horizontal, 4);
@@ -489,9 +496,9 @@ public StyleNano(IResourceCache resCache) : base(resCache)
sliderForeBox.SetPatchMargin(StyleBox.Margin.All, 12);
sliderGrabBox.SetPatchMargin(StyleBox.Margin.All, 12);
- var sliderFillGreen = new StyleBoxTexture(sliderFillBox) {Modulate = Color.LimeGreen};
- var sliderFillRed = new StyleBoxTexture(sliderFillBox) {Modulate = Color.Red};
- var sliderFillBlue = new StyleBoxTexture(sliderFillBox) {Modulate = Color.Blue};
+ var sliderFillGreen = new StyleBoxTexture(sliderFillBox) { Modulate = Color.LimeGreen };
+ var sliderFillRed = new StyleBoxTexture(sliderFillBox) { Modulate = Color.Red };
+ var sliderFillBlue = new StyleBoxTexture(sliderFillBox) { Modulate = Color.Blue };
var sliderFillWhite = new StyleBoxTexture(sliderFillBox) { Modulate = Color.White };
var boxFont13 = resCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
@@ -850,19 +857,19 @@ public StyleNano(IResourceCache resCache) : base(resCache)
Element().Pseudo(TextEdit.StylePseudoClassPlaceholder)
.Prop("font-color", Color.Gray),
- // Chat lineedit - we don't actually draw a stylebox around the lineedit itself, we put it around the
- // input + other buttons, so we must clear the default stylebox
- new StyleRule(new SelectorElement(typeof(LineEdit), new[] {StyleClassChatLineEdit}, null, null),
+ // chat subpanels (chat lineedit backing, popup backings)
+ new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {StyleClassChatPanel}, null, null),
new[]
{
- new StyleProperty(LineEdit.StylePropertyStyleBox, new StyleBoxEmpty()),
+ new StyleProperty(PanelContainer.StylePropertyPanel, chatBg),
}),
- // chat subpanels (chat lineedit backing, popup backings)
- new StyleRule(new SelectorElement(typeof(PanelContainer), new[] {StyleClassChatSubPanel}, null, null),
+ // Chat lineedit - we don't actually draw a stylebox around the lineedit itself, we put it around the
+ // input + other buttons, so we must clear the default stylebox
+ new StyleRule(new SelectorElement(typeof(LineEdit), new[] {StyleClassChatLineEdit}, null, null),
new[]
{
- new StyleProperty(PanelContainer.StylePropertyPanel, chatSubBG),
+ new StyleProperty(LineEdit.StylePropertyStyleBox, new StyleBoxEmpty()),
}),
// Action searchbox lineedit
@@ -1468,6 +1475,25 @@ public StyleNano(IResourceCache resCache) : base(resCache)
Element