mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-14 21:55:13 +01:00
Upstream (#3172)
Signed-off-by: Prole <172158352+Prole0@users.noreply.github.com> Co-authored-by: PJBot <pieterjan.briers+bot@gmail.com> Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Co-authored-by: Samuka-C <47865393+Samuka-C@users.noreply.github.com> Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com> Co-authored-by: Partmedia <kevinz5000@gmail.com> Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> Co-authored-by: themias <89101928+themias@users.noreply.github.com> Co-authored-by: Victor Shen <71985089+Vexerot@users.noreply.github.com> Co-authored-by: Ed <96445749+TheShuEd@users.noreply.github.com> Co-authored-by: Milon <milonpl.git@proton.me> Co-authored-by: Kirus59 <145689588+Kirus59@users.noreply.github.com> Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Co-authored-by: Stomf <5dorkydorks@gmail.com> Co-authored-by: drakewill-CRL <46307022+drakewill-CRL@users.noreply.github.com> Co-authored-by: PraxisMapper <praxismapper@gmail.com> Co-authored-by: EmoGarbage404 <retron404@gmail.com> Co-authored-by: lzk <124214523+lzk228@users.noreply.github.com> Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Co-authored-by: IProduceWidgets <107586145+IProduceWidgets@users.noreply.github.com> Co-authored-by: TytosB <54259736+TytosB@users.noreply.github.com> Co-authored-by: abadaba695 <spacestation13thingy@gmail.com> Co-authored-by: kosticia <kosticia46@gmail.com> Co-authored-by: Thinbug <101073555+Thinbug0@users.noreply.github.com> Co-authored-by: pathetic meowmeow <uhhadd@gmail.com> Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Co-authored-by: Boaz1111 <149967078+Boaz1111@users.noreply.github.com> Co-authored-by: ActiveMammmoth <140334666+ActiveMammmoth@users.noreply.github.com> Co-authored-by: Myra <vasilis@pikachu.systems> Co-authored-by: Whatstone <166147148+whatston3@users.noreply.github.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com> Co-authored-by: K-Dynamic <20566341+K-Dynamic@users.noreply.github.com> Co-authored-by: Gentleman-Bird <dcgreen406@gmail.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: BIGZi0348 <svalker0348@gmail.com> Co-authored-by: LaCumbiaDelCoronavirus <90893484+LaCumbiaDelCoronavirus@users.noreply.github.com> Co-authored-by: imatsoup <93290208+imatsoup@users.noreply.github.com> Co-authored-by: Matthew Herber <32679887+happyrobot33@users.noreply.github.com> Co-authored-by: Ertanic <36124833+Ertanic@users.noreply.github.com> Co-authored-by: MissKay1994 <15877268+MissKay1994@users.noreply.github.com> Co-authored-by: Errant <35878406+Errant-4@users.noreply.github.com> Co-authored-by: eoineoineoin <helloworld@eoinrul.es> Co-authored-by: Tiniest Shark <head.rebel@yahoo.com> Co-authored-by: nikitosych <boriszyn@gmail.com> Co-authored-by: Tayrtahn <tayrtahn@gmail.com> Co-authored-by: Perry Fraser <perryprog@users.noreply.github.com> Co-authored-by: YoungThug <ramialanbagy@gmail.com> Co-authored-by: beck-thompson <107373427+beck-thompson@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> Co-authored-by: Southbridge <7013162+southbridge-fur@users.noreply.github.com> Co-authored-by: Vladislav Suchkov <20380250+murolem@users.noreply.github.com> Co-authored-by: Prole <172158352+Prole0@users.noreply.github.com> Co-authored-by: Unkn0wn_Gh0st <shadowstalkermll@gmail.com> Co-authored-by: 3nderall <101940324+3nderall@users.noreply.github.com> Co-authored-by: Radezolid <snappednexus@gmail.com> Co-authored-by: J <billsmith116@gmail.com> Co-authored-by: Ghagliiarghii <68826635+Ghagliiarghii@users.noreply.github.com> Co-authored-by: chromiumboy <50505512+chromiumboy@users.noreply.github.com> Co-authored-by: youtissoum <51883137+youtissoum@users.noreply.github.com> Co-authored-by: Minemoder5000 <minemoder50000@gmail.com> Co-authored-by: Spanky <scott@wearejacob.com> Co-authored-by: Spessmann <156740760+Spessmann@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: brainfood1183 <113240905+brainfood1183@users.noreply.github.com> Co-authored-by: Deerstop <edainturner@gmail.com> Co-authored-by: B_Kirill <153602297+B-Kirill@users.noreply.github.com> Co-authored-by: archee1 <archee3@hotmail.co.uk> Co-authored-by: Cojoke <83733158+Cojoke-dot@users.noreply.github.com> Co-authored-by: Quantum-cross <7065792+Quantum-cross@users.noreply.github.com> Co-authored-by: poklj <compgeek223@gmail.com> Co-authored-by: Krunklehorn <42424291+Krunklehorn@users.noreply.github.com> Co-authored-by: OnyxTheBrave <131422822+OnyxTheBrave@users.noreply.github.com> Co-authored-by: UpAndLeaves <92269094+Alpha-Two@users.noreply.github.com> Co-authored-by: Flareguy <78941145+Flareguy@users.noreply.github.com> Co-authored-by: Zalycon <84675130+Zalycon@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: Verm <32827189+Vermidia@users.noreply.github.com> Co-authored-by: nikthechampiongr <32041239+nikthechampiongr@users.noreply.github.com> Co-authored-by: ScarKy0 <scarky0@onet.eu> Co-authored-by: Dmitry <57028746+dimm00n@users.noreply.github.com>
This commit is contained in:
@@ -127,6 +127,7 @@ csharp_indent_braces = false
|
||||
#csharp_indent_case_contents_when_block = true
|
||||
#csharp_indent_labels = one_less_than_current
|
||||
csharp_indent_switch_labels = true
|
||||
xmldoc_indent_text = zeroindent
|
||||
|
||||
# Space preferences
|
||||
csharp_space_after_cast = false
|
||||
|
||||
6
.github/CODEOWNERS
vendored
6
.github/CODEOWNERS
vendored
@@ -8,8 +8,8 @@
|
||||
*.ftl @ficcialfaint
|
||||
|
||||
# Map files
|
||||
/Resources/Prototypes/Maps/** @Ko4ergaPunk
|
||||
/Resources/Maps/** @Ko4ergaPunk
|
||||
/Resources/Prototypes/Maps/** @Ko4ergaPunk
|
||||
/Resources/Maps/** @Ko4ergaPunk
|
||||
|
||||
# Sprites
|
||||
/Resources/Textures/** @SonicHDC
|
||||
/Resources/Textures/** @SonicHDC
|
||||
|
||||
1
.github/workflows/publish-testing.yml
vendored
1
.github/workflows/publish-testing.yml
vendored
@@ -2,6 +2,7 @@
|
||||
|
||||
concurrency:
|
||||
group: publish-testing
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
3
.github/workflows/publish.yml
vendored
3
.github/workflows/publish.yml
vendored
@@ -2,6 +2,7 @@ name: Publish
|
||||
|
||||
concurrency:
|
||||
group: publish
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -66,12 +67,14 @@ jobs:
|
||||
FORK_ID: ${{ vars.FORK_ID }}
|
||||
|
||||
# - name: Publish changelog (Discord)
|
||||
# continue-on-error: true
|
||||
# run: Tools/actions_changelogs_since_last_run.py
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# DISCORD_WEBHOOK_URL: ${{ secrets.CHANGELOG_DISCORD_WEBHOOK }}
|
||||
|
||||
# - name: Publish changelog (RSS)
|
||||
# continue-on-error: true
|
||||
# run: Tools/actions_changelog_rss.py
|
||||
# env:
|
||||
# CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}
|
||||
|
||||
44
.vscode/tasks.json
vendored
44
.vscode/tasks.json
vendored
@@ -32,6 +32,50 @@
|
||||
"/consoleloggerparameters:'ForceNoAlign;NoSummary'"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "test",
|
||||
"command": "dotnet",
|
||||
"type": "shell",
|
||||
"args": [
|
||||
"test",
|
||||
"--no-build",
|
||||
"--configuration",
|
||||
"DebugOpt",
|
||||
"Content.Tests/Content.Tests.csproj",
|
||||
"--",
|
||||
"NUnit.ConsoleOut=0"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "integration-test",
|
||||
"command": "dotnet",
|
||||
"type": "shell",
|
||||
"args": [
|
||||
"test",
|
||||
"--no-build",
|
||||
"--configuration",
|
||||
"DebugOpt",
|
||||
"Content.IntegrationTests/Content.IntegrationTests.csproj",
|
||||
"--",
|
||||
"NUnit.ConsoleOut=0",
|
||||
"NUnit.MapWarningTo=Failed.ConsoleOut=0",
|
||||
"NUnit.MapWarningTo=Failed"
|
||||
],
|
||||
"group": {
|
||||
"kind": "test"
|
||||
},
|
||||
"presentation": {
|
||||
"reveal": "silent"
|
||||
},
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import subprocess
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
@@ -104,7 +105,21 @@ def reset_solution():
|
||||
with SOLUTION_PATH.open("w") as f:
|
||||
f.write(content)
|
||||
|
||||
def check_for_zip_download():
|
||||
# Check if .git exists,
|
||||
cur_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
if not os.path.isdir(os.path.join(cur_dir, ".git")):
|
||||
print("It appears that you downloaded this repository directly from GitHub. (Using the .zip download option) \n"
|
||||
"When downloading straight from GitHub, it leaves out important information that git needs to function. "
|
||||
"Such as information to download the engine or even the ability to even be able to create contributions. \n"
|
||||
"Please read and follow https://docs.spacestation14.com/en/general-development/setup/setting-up-a-development-environment.html \n"
|
||||
"If you just want a Sandbox Server, you are following the wrong guide! You can download a premade server following the instructions here:"
|
||||
"https://docs.spacestation14.com/en/general-development/setup/server-hosting-tutorial.html \n"
|
||||
"Closing automatically in 30 seconds.")
|
||||
time.sleep(30)
|
||||
exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
check_for_zip_download()
|
||||
install_hooks()
|
||||
update_submodules()
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.IntegrationTests;
|
||||
using Content.IntegrationTests.Pair;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Warps;
|
||||
using Content.Shared.Warps;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.EntitySerialization;
|
||||
|
||||
@@ -174,7 +174,8 @@ namespace Content.Client.Access.UI
|
||||
new List<ProtoId<AccessLevelPrototype>>());
|
||||
|
||||
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
|
||||
// If the job index is < 0 that means they don't have a job registered in the station records.
|
||||
// If the job index is < 0 that means they don't have a job registered in the station records
|
||||
// or the IdCardComponent's JobPrototype field.
|
||||
// For example, a new ID from a box would have no job index.
|
||||
if (jobIndex < 0)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Charges.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -22,6 +23,7 @@ namespace Content.Client.Actions
|
||||
{
|
||||
public delegate void OnActionReplaced(EntityUid actionId);
|
||||
|
||||
[Dependency] private readonly SharedChargesSystem _sharedCharges = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resources = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
@@ -51,29 +53,6 @@ namespace Content.Client.Actions
|
||||
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
var worldActionQuery = EntityQueryEnumerator<WorldTargetActionComponent>();
|
||||
while (worldActionQuery.MoveNext(out var uid, out var action))
|
||||
{
|
||||
UpdateAction(uid, action);
|
||||
}
|
||||
|
||||
var instantActionQuery = EntityQueryEnumerator<InstantActionComponent>();
|
||||
while (instantActionQuery.MoveNext(out var uid, out var action))
|
||||
{
|
||||
UpdateAction(uid, action);
|
||||
}
|
||||
|
||||
var entityActionQuery = EntityQueryEnumerator<EntityTargetActionComponent>();
|
||||
while (entityActionQuery.MoveNext(out var uid, out var action))
|
||||
{
|
||||
UpdateAction(uid, action);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not InstantActionComponentState state)
|
||||
@@ -127,9 +106,6 @@ namespace Content.Client.Actions
|
||||
component.Toggled = state.Toggled;
|
||||
component.Cooldown = state.Cooldown;
|
||||
component.UseDelay = state.UseDelay;
|
||||
component.Charges = state.Charges;
|
||||
component.MaxCharges = state.MaxCharges;
|
||||
component.RenewCharges = state.RenewCharges;
|
||||
component.Container = EnsureEntity<T>(state.Container, uid);
|
||||
component.EntityIcon = EnsureEntity<T>(state.EntityIcon, uid);
|
||||
component.CheckCanInteract = state.CheckCanInteract;
|
||||
@@ -152,7 +128,8 @@ namespace Content.Client.Actions
|
||||
if (!ResolveActionData(actionId, ref action))
|
||||
return;
|
||||
|
||||
action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor;
|
||||
// TODO: Decouple this.
|
||||
action.IconColor = _sharedCharges.GetCurrentCharges(actionId.Value) == 0 ? action.DisabledIconColor : action.OriginalIconColor;
|
||||
|
||||
base.UpdateAction(actionId, action);
|
||||
if (_playerManager.LocalEntity != action.AttachedEntity)
|
||||
@@ -225,6 +202,7 @@ namespace Content.Client.Actions
|
||||
return;
|
||||
|
||||
OnActionAdded?.Invoke(actionId);
|
||||
ActionsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
protected override void ActionRemoved(EntityUid performer, EntityUid actionId, ActionsComponent comp, BaseActionComponent action)
|
||||
@@ -233,6 +211,7 @@ namespace Content.Client.Actions
|
||||
return;
|
||||
|
||||
OnActionRemoved?.Invoke(actionId);
|
||||
ActionsUpdated?.Invoke();
|
||||
}
|
||||
|
||||
public IEnumerable<(EntityUid Id, BaseActionComponent Comp)> GetClientActions()
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -22,10 +23,11 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly EntityLookupSystem _entityLookup;
|
||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||
private readonly SharedRoleSystem _roles;
|
||||
private readonly Font _font;
|
||||
private readonly Font _fontBold;
|
||||
private bool _overlayClassic;
|
||||
private bool _overlaySymbols;
|
||||
private AdminOverlayAntagFormat _overlayFormat;
|
||||
private AdminOverlayAntagSymbolStyle _overlaySymbolStyle;
|
||||
private bool _overlayPlaytime;
|
||||
private bool _overlayStartingJob;
|
||||
private float _ghostFadeDistance;
|
||||
@@ -33,9 +35,10 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
private int _overlayStackMax;
|
||||
private float _overlayMergeDistance;
|
||||
|
||||
//TODO make this adjustable via GUI
|
||||
//TODO make this adjustable via GUI?
|
||||
private readonly ProtoId<RoleTypePrototype>[] _filter =
|
||||
["SoloAntagonist", "TeamAntagonist", "SiliconAntagonist", "FreeAgent"];
|
||||
|
||||
private readonly string _antagLabelClassic = Loc.GetString("admin-overlay-antag-classic");
|
||||
|
||||
public AdminNameOverlay(
|
||||
@@ -45,20 +48,22 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
IResourceCache resourceCache,
|
||||
EntityLookupSystem entityLookup,
|
||||
IUserInterfaceManager userInterfaceManager,
|
||||
IConfigurationManager config)
|
||||
IConfigurationManager config,
|
||||
SharedRoleSystem roles)
|
||||
{
|
||||
_system = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_entityLookup = entityLookup;
|
||||
_userInterfaceManager = userInterfaceManager;
|
||||
_roles = roles;
|
||||
ZIndex = 200;
|
||||
// Setting these to a specific ttf would break the antag symbols
|
||||
_font = resourceCache.NotoStack();
|
||||
_fontBold = resourceCache.NotoStack(variation: "Bold");
|
||||
|
||||
config.OnValueChanged(CCVars.AdminOverlayClassic, (show) => { _overlayClassic = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlaySymbols, (show) => { _overlaySymbols = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayAntagFormat, (show) => { _overlayFormat = UpdateOverlayFormat(show); }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlaySymbolStyle, (show) => { _overlaySymbolStyle = UpdateOverlaySymbolStyle(show); }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayPlaytime, (show) => { _overlayPlaytime = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayStartingJob, (show) => { _overlayStartingJob = show; }, true);
|
||||
config.OnValueChanged(CCVars.AdminOverlayGhostHideDistance, (f) => { _ghostHideDistance = f; }, true);
|
||||
@@ -67,6 +72,22 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
config.OnValueChanged(CCVars.AdminOverlayMergeDistance, (f) => { _overlayMergeDistance = f; }, true);
|
||||
}
|
||||
|
||||
private AdminOverlayAntagFormat UpdateOverlayFormat(string formatString)
|
||||
{
|
||||
if (!Enum.TryParse<AdminOverlayAntagFormat>(formatString, out var format))
|
||||
format = AdminOverlayAntagFormat.Binary;
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
private AdminOverlayAntagSymbolStyle UpdateOverlaySymbolStyle(string symbolString)
|
||||
{
|
||||
if (!Enum.TryParse<AdminOverlayAntagSymbolStyle>(symbolString, out var symbolStyle))
|
||||
symbolStyle = AdminOverlayAntagSymbolStyle.Off;
|
||||
|
||||
return symbolStyle;
|
||||
}
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
@@ -183,34 +204,56 @@ internal sealed class AdminNameOverlay : Overlay
|
||||
currentOffset += lineoffset;
|
||||
}
|
||||
|
||||
// Classic Antag Label
|
||||
if (_overlayClassic && playerInfo.Antag)
|
||||
// Determine antag symbol
|
||||
string? symbol;
|
||||
switch (_overlaySymbolStyle)
|
||||
{
|
||||
var symbol = _overlaySymbols ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
|
||||
var label = _overlaySymbols
|
||||
? Loc.GetString("player-tab-character-name-antag-symbol",
|
||||
("symbol", symbol),
|
||||
("name", _antagLabelClassic))
|
||||
: _antagLabelClassic;
|
||||
color = Color.OrangeRed;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
case AdminOverlayAntagSymbolStyle.Specific:
|
||||
symbol = playerInfo.RoleProto.Symbol;
|
||||
break;
|
||||
case AdminOverlayAntagSymbolStyle.Basic:
|
||||
symbol = Loc.GetString("player-tab-antag-prefix");
|
||||
break;
|
||||
default:
|
||||
case AdminOverlayAntagSymbolStyle.Off:
|
||||
symbol = string.Empty;
|
||||
break;
|
||||
}
|
||||
// Role Type
|
||||
else if (!_overlayClassic && _filter.Contains(playerInfo.RoleProto))
|
||||
|
||||
// Determine antag/role type name
|
||||
string? text;
|
||||
switch (_overlayFormat)
|
||||
{
|
||||
var symbol = _overlaySymbols && playerInfo.Antag ? playerInfo.RoleProto.Symbol : string.Empty;
|
||||
var role = Loc.GetString(playerInfo.RoleProto.Name).ToUpper();
|
||||
var label = _overlaySymbols
|
||||
? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", role))
|
||||
: role;
|
||||
color = playerInfo.RoleProto.Color;
|
||||
color.A = alpha;
|
||||
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
case AdminOverlayAntagFormat.Roletype:
|
||||
color = playerInfo.RoleProto.Color;
|
||||
symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty;
|
||||
text = _filter.Contains(playerInfo.RoleProto)
|
||||
? Loc.GetString(playerInfo.RoleProto.Name).ToUpper()
|
||||
: string.Empty;
|
||||
break;
|
||||
case AdminOverlayAntagFormat.Subtype:
|
||||
color = playerInfo.RoleProto.Color;
|
||||
symbol = _filter.Contains(playerInfo.RoleProto) ? symbol : string.Empty;
|
||||
text = _filter.Contains(playerInfo.RoleProto)
|
||||
? _roles.GetRoleSubtypeLabel(playerInfo.RoleProto.Name, playerInfo.Subtype).ToUpper()
|
||||
: string.Empty;
|
||||
break;
|
||||
default:
|
||||
case AdminOverlayAntagFormat.Binary:
|
||||
color = Color.OrangeRed;
|
||||
symbol = playerInfo.Antag ? symbol : string.Empty;
|
||||
text = playerInfo.Antag ? _antagLabelClassic : string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
// Draw antag label
|
||||
color.A = alpha;
|
||||
var label = !string.IsNullOrEmpty(symbol)
|
||||
? Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", text))
|
||||
: text;
|
||||
args.ScreenHandle.DrawString(_fontBold, screenCoordinates + currentOffset, label, uiScale, color);
|
||||
currentOffset += lineoffset;
|
||||
|
||||
//Save the coordinates and size of the text block, for stack merge check
|
||||
drawnOverlays.Add((screenCoordinatesCenter, currentOffset));
|
||||
}
|
||||
|
||||
15
Content.Client/Administration/OverlayOptions.cs
Normal file
15
Content.Client/Administration/OverlayOptions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Content.Client.Administration;
|
||||
|
||||
public enum AdminOverlayAntagFormat
|
||||
{
|
||||
Binary,
|
||||
Roletype,
|
||||
Subtype
|
||||
}
|
||||
|
||||
public enum AdminOverlayAntagSymbolStyle
|
||||
{
|
||||
Off,
|
||||
Basic,
|
||||
Specific
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Administration.Managers;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -15,6 +16,7 @@ namespace Content.Client.Administration.Systems
|
||||
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||
|
||||
private AdminNameOverlay _adminNameOverlay = default!;
|
||||
|
||||
@@ -30,7 +32,8 @@ namespace Content.Client.Administration.Systems
|
||||
_resourceCache,
|
||||
_entityLookup,
|
||||
_userInterfaceManager,
|
||||
_configurationManager);
|
||||
_configurationManager,
|
||||
_roles);
|
||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||
}
|
||||
|
||||
@@ -46,7 +49,8 @@ namespace Content.Client.Administration.Systems
|
||||
|
||||
public void AdminOverlayOn()
|
||||
{
|
||||
if (_overlayManager.HasOverlay<AdminNameOverlay>()) return;
|
||||
if (_overlayManager.HasOverlay<AdminNameOverlay>())
|
||||
return;
|
||||
_overlayManager.AddOverlay(_adminNameOverlay);
|
||||
OverlayEnabled?.Invoke();
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Content.Client.Administration.Systems
|
||||
var verb = new VvVerb()
|
||||
{
|
||||
Text = Loc.GetString("view-variables"),
|
||||
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/vv.svg.192dpi.png")),
|
||||
Act = () => _clientConsoleHost.ExecuteCommand($"vv {GetNetEntity(args.Target)}"),
|
||||
ClientExclusive = true // opening VV window is client-side. Don't ask server to run this verb.
|
||||
};
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
using System.Threading;
|
||||
using Content.Client.Stylesheets;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Client.Administration.UI;
|
||||
|
||||
public static class AdminUIHelpers
|
||||
{
|
||||
private static void ResetButton(Button button, ConfirmationData data)
|
||||
{
|
||||
data.Cancellation.Cancel();
|
||||
button.ModulateSelfOverride = null;
|
||||
button.Text = data.OriginalText;
|
||||
}
|
||||
|
||||
public static bool RemoveConfirm(Button button, Dictionary<Button, ConfirmationData> confirmations)
|
||||
{
|
||||
if (confirmations.Remove(button, out var data))
|
||||
{
|
||||
ResetButton(button, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void RemoveAllConfirms(Dictionary<Button, ConfirmationData> confirmations)
|
||||
{
|
||||
foreach (var (button, confirmation) in confirmations)
|
||||
{
|
||||
ResetButton(button, confirmation);
|
||||
}
|
||||
|
||||
confirmations.Clear();
|
||||
}
|
||||
|
||||
public static bool TryConfirm(Button button, Dictionary<Button, ConfirmationData> confirmations)
|
||||
{
|
||||
if (RemoveConfirm(button, confirmations))
|
||||
return true;
|
||||
|
||||
var data = new ConfirmationData(new CancellationTokenSource(), button.Text);
|
||||
confirmations[button] = data;
|
||||
|
||||
Timer.Spawn(TimeSpan.FromSeconds(5), () =>
|
||||
{
|
||||
confirmations.Remove(button);
|
||||
button.ModulateSelfOverride = null;
|
||||
button.Text = data.OriginalText;
|
||||
}, data.Cancellation.Token);
|
||||
|
||||
button.ModulateSelfOverride = StyleNano.ButtonColorCautionDefault;
|
||||
button.Text = Loc.GetString("admin-player-actions-confirm");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct ConfirmationData(CancellationTokenSource Cancellation, string? OriginalText);
|
||||
@@ -1,6 +1,7 @@
|
||||
<Control
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<PanelContainer StyleClasses="BackgroundDark">
|
||||
<SplitContainer Orientation="Vertical" ResizeMode="NotResizable">
|
||||
<SplitContainer Orientation="Horizontal" VerticalExpand="True">
|
||||
@@ -18,9 +19,9 @@
|
||||
<Control HorizontalExpand="True" />
|
||||
<Button Visible="False" Name="Bans" Text="{Loc 'admin-player-actions-bans'}" StyleClasses="OpenRight" />
|
||||
<Button Visible="False" Name="Notes" Text="{Loc 'admin-player-actions-notes'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" StyleClasses="OpenBoth" />
|
||||
<controls:ConfirmButton Visible="False" Name="Kick" Text="{Loc 'admin-player-actions-kick'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Ban" Text="{Loc 'admin-player-actions-ban'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" StyleClasses="OpenBoth" />
|
||||
<controls:ConfirmButton Visible="False" Name="Respawn" Text="{Loc 'admin-player-actions-respawn'}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" StyleClasses="OpenBoth" />
|
||||
<Button Visible="False" Name="Follow" Text="{Loc 'admin-player-actions-follow'}" StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</SplitContainer>
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
public AdminAHelpUIHandler AHelpHelper = default!;
|
||||
|
||||
private PlayerInfo? _currentPlayer;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public BwoinkControl()
|
||||
{
|
||||
@@ -178,11 +177,6 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
|
||||
Kick.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(Kick, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Reason field
|
||||
if (_currentPlayer is not null)
|
||||
_console.ExecuteCommand($"kick \"{_currentPlayer.Username}\"");
|
||||
@@ -196,11 +190,6 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
|
||||
Respawn.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(Respawn, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentPlayer is not null)
|
||||
_console.ExecuteCommand($"respawn \"{_currentPlayer.Username}\"");
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<Popup xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client">
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BorderThickness="2" BorderColor="#18181B" BackgroundColor="#25252a"/>
|
||||
@@ -19,7 +20,7 @@
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="EditButton" Text="{Loc admin-notes-edit}"/>
|
||||
<Control HorizontalExpand="True"/>
|
||||
<Button Name="DeleteButton" Text="{Loc admin-notes-delete}" HorizontalAlignment="Right"/>
|
||||
<controls:ConfirmButton Name="DeleteButton" Text="{Loc admin-notes-delete}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" HorizontalAlignment="Right"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -19,12 +19,13 @@
|
||||
<Label Name="SharedConnections"/>
|
||||
|
||||
<BoxContainer Align="Center">
|
||||
<GridContainer Rows="5">
|
||||
<GridContainer Rows="6">
|
||||
<Button Name="NotesButton" Text="{Loc player-panel-show-notes}" SetWidth="136" Disabled="True"/>
|
||||
<Button Name="AhelpButton" Text="{Loc player-panel-help}" Disabled="True"/>
|
||||
<Button Name="FreezeButton" Text = "{Loc player-panel-freeze}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="KickButton" Text="{Loc player-panel-kick}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="DeleteButton" Text="{Loc player-panel-delete}" Disabled="True"/>
|
||||
<Button Name="FollowButton" Text="{Loc player-panel-follow}"/>
|
||||
<Button Name="ShowBansButton" Text="{Loc player-panel-show-bans}" SetWidth="136" Disabled="True"/>
|
||||
<Button Name="LogsButton" Text="{Loc player-panel-logs}" Disabled="True"/>
|
||||
<Button Name="FreezeAndMuteToggleButton" Text="{Loc player-panel-freeze-and-mute}" Disabled="True"/>
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Client.Administration.Managers;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -21,6 +20,7 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
public event Action<string?>? OnKick;
|
||||
public event Action<NetUserId?>? OnOpenBanPanel;
|
||||
public event Action<NetUserId?, bool>? OnWhitelistToggle;
|
||||
public event Action? OnFollow;
|
||||
public event Action? OnFreezeAndMuteToggle;
|
||||
public event Action? OnFreeze;
|
||||
public event Action? OnLogs;
|
||||
@@ -36,7 +36,7 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
RobustXamlLoader.Load(this);
|
||||
_adminManager = adminManager;
|
||||
|
||||
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(PlayerName.Text ?? "");
|
||||
UsernameCopyButton.OnPressed += _ => OnUsernameCopy?.Invoke(TargetUsername ?? "");
|
||||
BanButton.OnPressed += _ => OnOpenBanPanel?.Invoke(TargetPlayer);
|
||||
KickButton.OnPressed += _ => OnKick?.Invoke(TargetUsername);
|
||||
NotesButton.OnPressed += _ => OnOpenNotes?.Invoke(TargetPlayer);
|
||||
@@ -47,6 +47,7 @@ public sealed partial class PlayerPanel : FancyWindow
|
||||
OnWhitelistToggle?.Invoke(TargetPlayer, _isWhitelisted);
|
||||
SetWhitelisted(!_isWhitelisted);
|
||||
};
|
||||
FollowButton.OnPressed += _ => OnFollow?.Invoke();
|
||||
FreezeButton.OnPressed += _ => OnFreeze?.Invoke();
|
||||
FreezeAndMuteToggleButton.OnPressed += _ => OnFreezeAndMuteToggle?.Invoke();
|
||||
LogsButton.OnPressed += _ => OnLogs?.Invoke();
|
||||
|
||||
@@ -38,6 +38,7 @@ public sealed class PlayerPanelEui : BaseEui
|
||||
PlayerPanel.OnLogs += () => SendMessage(new PlayerPanelLogsMessage());
|
||||
PlayerPanel.OnRejuvenate += () => SendMessage(new PlayerPanelRejuvenationMessage());
|
||||
PlayerPanel.OnDelete+= () => SendMessage(new PlayerPanelDeleteMessage());
|
||||
PlayerPanel.OnFollow += () => SendMessage(new PlayerPanelFollowMessage());
|
||||
|
||||
PlayerPanel.OnClose += () => SendMessage(new CloseEuiMessage());
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<cc:UICommandButton Command="callshuttle" Text="{Loc admin-player-actions-window-shuttle}" WindowType="{x:Type at:AdminShuttleWindow}"/>
|
||||
<cc:CommandButton Command="adminlogs" Text="{Loc admin-player-actions-window-admin-logs}"/>
|
||||
<cc:CommandButton Command="faxui" Text="{Loc admin-player-actions-window-admin-fax}"/>
|
||||
<cc:CommandButton Command="achatwindow" Text="{Loc admin-player-actions-window-admin-chat}"/>
|
||||
</GridContainer>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<DefaultWindow
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc admin-player-actions-window-title}" MinSize="425 272">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
@@ -10,9 +11,9 @@
|
||||
</BoxContainer>
|
||||
<cc:PlayerListControl Name="PlayerList" VerticalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="SubmitKickButton" Text="{Loc admin-player-actions-kick}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="SubmitKickButton" Text="{Loc admin-player-actions-kick}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
|
||||
<Button Name="SubmitAHelpButton" Text="{Loc admin-player-actions-ahelp}" Disabled="True"/>
|
||||
<Button Name="SubmitRespawnButton" Text="{Loc admin-player-actions-respawn}" Disabled="True"/>
|
||||
<controls:ConfirmButton Name="SubmitRespawnButton" Text="{Loc admin-player-actions-respawn}" ConfirmationText="{Loc 'admin-player-actions-confirm'}" Disabled="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
public sealed partial class PlayerActionsWindow : DefaultWindow
|
||||
{
|
||||
private PlayerInfo? _selectedPlayer;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public PlayerActionsWindow()
|
||||
{
|
||||
@@ -28,9 +27,6 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
|
||||
private void OnListOnOnSelectionChanged(PlayerInfo? obj)
|
||||
{
|
||||
if (_selectedPlayer != obj)
|
||||
AdminUIHelpers.RemoveAllConfirms(_confirmations);
|
||||
|
||||
_selectedPlayer = obj;
|
||||
var disableButtons = _selectedPlayer == null;
|
||||
SubmitKickButton.Disabled = disableButtons;
|
||||
@@ -43,9 +39,6 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
if (_selectedPlayer == null)
|
||||
return;
|
||||
|
||||
if (!AdminUIHelpers.TryConfirm(SubmitKickButton, _confirmations))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
|
||||
$"kick \"{_selectedPlayer.Username}\" \"{CommandParsing.Escape(ReasonLine.Text)}\"");
|
||||
}
|
||||
@@ -64,9 +57,6 @@ namespace Content.Client.Administration.UI.Tabs.AdminTab
|
||||
if (_selectedPlayer == null)
|
||||
return;
|
||||
|
||||
if (!AdminUIHelpers.TryConfirm(SubmitRespawnButton, _confirmations))
|
||||
return;
|
||||
|
||||
IoCManager.Resolve<IClientConsoleHost>().ExecuteCommand(
|
||||
$"respawn \"{_selectedPlayer.Username}\"");
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
|
||||
public Action<NetEntity>? OnTeleport;
|
||||
public Action<NetEntity>? OnDelete;
|
||||
private readonly Dictionary<Button, ConfirmationData> _confirmations = new();
|
||||
|
||||
public ObjectsTabEntry(IClientAdminManager manager, string name, NetEntity nent, StyleBox styleBox)
|
||||
{
|
||||
@@ -28,13 +27,6 @@ public sealed partial class ObjectsTabEntry : PanelContainer
|
||||
DeleteButton.Disabled = !manager.CanCommand("delete");
|
||||
|
||||
TeleportButton.OnPressed += _ => OnTeleport?.Invoke(nent);
|
||||
DeleteButton.OnPressed += _ =>
|
||||
{
|
||||
if (!AdminUIHelpers.TryConfirm(DeleteButton, _confirmations))
|
||||
{
|
||||
return;
|
||||
}
|
||||
OnDelete?.Invoke(nent);
|
||||
};
|
||||
DeleteButton.OnPressed += _ => OnDelete?.Invoke(nent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ public sealed partial class PlayerTab : Control
|
||||
private bool _ascending = true;
|
||||
private bool _showDisconnected;
|
||||
|
||||
private AdminPlayerTabColorOption _playerTabColorSetting;
|
||||
private AdminPlayerTabRoleTypeOption _playerTabRoleSetting;
|
||||
private AdminPlayerTabSymbolOption _playerTabSymbolSetting;
|
||||
|
||||
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
|
||||
|
||||
public PlayerTab()
|
||||
@@ -44,9 +48,10 @@ public sealed partial class PlayerTab : Control
|
||||
_adminSystem.OverlayEnabled += OverlayEnabled;
|
||||
_adminSystem.OverlayDisabled += OverlayDisabled;
|
||||
|
||||
_config.OnValueChanged(CCVars.AdminPlayerlistSeparateSymbols, PlayerListSettingsChanged);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerlistHighlightedCharacterColor, PlayerListSettingsChanged);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerlistRoleTypeColor, PlayerListSettingsChanged);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerTabRoleSetting, RoleSettingChanged, true);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerTabColorSetting, ColorSettingChanged, true);
|
||||
_config.OnValueChanged(CCVars.AdminPlayerTabSymbolSetting, SymbolSettingChanged, true);
|
||||
|
||||
|
||||
OverlayButton.OnPressed += OverlayButtonPressed;
|
||||
ShowDisconnectedButton.OnPressed += ShowDisconnectedPressed;
|
||||
@@ -113,8 +118,27 @@ public sealed partial class PlayerTab : Control
|
||||
|
||||
#region ListContainer
|
||||
|
||||
private void PlayerListSettingsChanged(bool _)
|
||||
private void RoleSettingChanged(string s)
|
||||
{
|
||||
if (!Enum.TryParse<AdminPlayerTabRoleTypeOption>(s, out var format))
|
||||
format = AdminPlayerTabRoleTypeOption.Subtype;
|
||||
_playerTabRoleSetting = format;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
private void ColorSettingChanged(string s)
|
||||
{
|
||||
if (!Enum.TryParse<AdminPlayerTabColorOption>(s, out var format))
|
||||
format = AdminPlayerTabColorOption.Both;
|
||||
_playerTabColorSetting = format;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
private void SymbolSettingChanged(string s)
|
||||
{
|
||||
if (!Enum.TryParse<AdminPlayerTabSymbolOption>(s, out var format))
|
||||
format = AdminPlayerTabSymbolOption.Specific;
|
||||
_playerTabSymbolSetting = format;
|
||||
RefreshPlayerList(_adminSystem.PlayerList);
|
||||
}
|
||||
|
||||
@@ -140,7 +164,12 @@ public sealed partial class PlayerTab : Control
|
||||
if (data is not PlayerListData { Info: var player})
|
||||
return;
|
||||
|
||||
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
|
||||
var entry = new PlayerTabEntry(
|
||||
player,
|
||||
new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor),
|
||||
_playerTabColorSetting,
|
||||
_playerTabRoleSetting,
|
||||
_playerTabSymbolSetting);
|
||||
button.AddChild(entry);
|
||||
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
|
||||
}
|
||||
|
||||
@@ -1,42 +1,99 @@
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class PlayerTabEntry : PanelContainer
|
||||
{
|
||||
public NetEntity? PlayerEntity;
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat)
|
||||
public PlayerTabEntry(
|
||||
PlayerInfo player,
|
||||
StyleBoxFlat styleBoxFlat,
|
||||
AdminPlayerTabColorOption colorOption,
|
||||
AdminPlayerTabRoleTypeOption roleSetting,
|
||||
AdminPlayerTabSymbolOption symbolSetting)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
var config = IoCManager.Resolve<IConfigurationManager>();
|
||||
var roles = _entMan.System<SharedRoleSystem>();
|
||||
|
||||
UsernameLabel.Text = player.Username;
|
||||
if (!player.Connected)
|
||||
UsernameLabel.StyleClasses.Add("Disabled");
|
||||
JobLabel.Text = player.StartingJob;
|
||||
var separateAntagSymbols = config.GetCVar(CCVars.AdminPlayerlistSeparateSymbols);
|
||||
var genericAntagSymbol = player.Antag ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
|
||||
var roleSymbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
|
||||
var symbol = separateAntagSymbols ? roleSymbol : genericAntagSymbol;
|
||||
|
||||
var colorAntags = false;
|
||||
var colorRoles = false;
|
||||
switch (colorOption)
|
||||
{
|
||||
case AdminPlayerTabColorOption.Off:
|
||||
break;
|
||||
case AdminPlayerTabColorOption.Character:
|
||||
colorAntags = true;
|
||||
break;
|
||||
case AdminPlayerTabColorOption.Roletype:
|
||||
colorRoles = true;
|
||||
break;
|
||||
default:
|
||||
case AdminPlayerTabColorOption.Both:
|
||||
colorAntags = true;
|
||||
colorRoles = true;
|
||||
break;
|
||||
}
|
||||
|
||||
var symbol = string.Empty;
|
||||
switch (symbolSetting)
|
||||
{
|
||||
case AdminPlayerTabSymbolOption.Off:
|
||||
break;
|
||||
case AdminPlayerTabSymbolOption.Basic:
|
||||
symbol = player.Antag ? Loc.GetString("player-tab-antag-prefix") : string.Empty;
|
||||
break;
|
||||
default:
|
||||
case AdminPlayerTabSymbolOption.Specific:
|
||||
symbol = player.Antag ? player.RoleProto.Symbol : string.Empty;
|
||||
break;
|
||||
}
|
||||
|
||||
CharacterLabel.Text = Loc.GetString("player-tab-character-name-antag-symbol", ("symbol", symbol), ("name", player.CharacterName));
|
||||
|
||||
if (player.Antag && config.GetCVar(CCVars.AdminPlayerlistHighlightedCharacterColor))
|
||||
if (player.Antag && colorAntags)
|
||||
CharacterLabel.FontColorOverride = player.RoleProto.Color;
|
||||
if (player.IdentityName != player.CharacterName)
|
||||
CharacterLabel.Text += $" [{player.IdentityName}]";
|
||||
RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
|
||||
if (config.GetCVar(CCVars.AdminPlayerlistRoleTypeColor))
|
||||
|
||||
var roletype = RoleTypeLabel.Text = Loc.GetString(player.RoleProto.Name);
|
||||
var subtype = roles.GetRoleSubtypeLabel(player.RoleProto.Name, player.Subtype);
|
||||
switch (roleSetting)
|
||||
{
|
||||
case AdminPlayerTabRoleTypeOption.RoleTypeSubtype:
|
||||
RoleTypeLabel.Text = roletype != subtype
|
||||
? roletype + " - " +subtype
|
||||
: roletype;
|
||||
break;
|
||||
case AdminPlayerTabRoleTypeOption.SubtypeRoleType:
|
||||
RoleTypeLabel.Text = roletype != subtype
|
||||
? subtype + " - " + roletype
|
||||
: roletype;
|
||||
break;
|
||||
case AdminPlayerTabRoleTypeOption.RoleType:
|
||||
RoleTypeLabel.Text = roletype;
|
||||
break;
|
||||
default:
|
||||
case AdminPlayerTabRoleTypeOption.Subtype:
|
||||
RoleTypeLabel.Text = subtype;
|
||||
break;
|
||||
}
|
||||
|
||||
if (colorRoles)
|
||||
RoleTypeLabel.FontColorOverride = player.RoleProto.Color;
|
||||
BackgroundColorPanel.PanelOverride = styleBoxFlat;
|
||||
OverallPlaytimeLabel.Text = player.PlaytimeString;
|
||||
PlayerEntity = player.NetEntity;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
|
||||
|
||||
public enum AdminPlayerTabColorOption
|
||||
{
|
||||
Off,
|
||||
Character,
|
||||
Roletype,
|
||||
Both
|
||||
}
|
||||
|
||||
public enum AdminPlayerTabRoleTypeOption
|
||||
{
|
||||
RoleType,
|
||||
Subtype,
|
||||
RoleTypeSubtype,
|
||||
SubtypeRoleType
|
||||
}
|
||||
|
||||
public enum AdminPlayerTabSymbolOption
|
||||
{
|
||||
Off,
|
||||
Basic,
|
||||
Specific
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Shared.Alert;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -15,6 +16,7 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _ui = default!;
|
||||
|
||||
public event EventHandler? ClearAlerts;
|
||||
public event EventHandler<IReadOnlyDictionary<AlertKey, AlertState>>? SyncAlerts;
|
||||
@@ -27,6 +29,12 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
SubscribeLocalEvent<AlertsComponent, LocalPlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<AlertsComponent, ComponentHandleState>(OnHandleState);
|
||||
}
|
||||
|
||||
protected override void HandledAlert()
|
||||
{
|
||||
_ui.ClickSound();
|
||||
}
|
||||
|
||||
protected override void LoadPrototypes()
|
||||
{
|
||||
base.LoadPrototypes();
|
||||
@@ -52,8 +60,24 @@ public sealed class ClientAlertsSystem : AlertsSystem
|
||||
if (args.Current is not AlertComponentState cast)
|
||||
return;
|
||||
|
||||
// Save all client-sided alerts to later put back in
|
||||
var clientAlerts = new Dictionary<AlertKey, AlertState>();
|
||||
foreach (var alert in alerts.Comp.Alerts)
|
||||
{
|
||||
if (alert.Key.AlertType != null && TryGet(alert.Key.AlertType.Value, out var alertProto))
|
||||
{
|
||||
if (alertProto.ClientHandled)
|
||||
clientAlerts[alert.Key] = alert.Value;
|
||||
}
|
||||
}
|
||||
|
||||
alerts.Comp.Alerts = new(cast.Alerts);
|
||||
|
||||
foreach (var alert in clientAlerts)
|
||||
{
|
||||
alerts.Comp.Alerts[alert.Key] = alert.Value;
|
||||
}
|
||||
|
||||
UpdateHud(alerts);
|
||||
}
|
||||
|
||||
|
||||
29
Content.Client/Atmos/EntitySystems/GasTankSystem.cs
Normal file
29
Content.Client/Atmos/EntitySystems/GasTankSystem.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.EntitySystems;
|
||||
|
||||
namespace Content.Client.Atmos.EntitySystems;
|
||||
|
||||
public sealed class GasTankSystem : SharedGasTankSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GasTankComponent, AfterAutoHandleStateEvent>(OnGasTankState);
|
||||
}
|
||||
|
||||
private void OnGasTankState(Entity<GasTankComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (UI.TryGetOpenUi(ent.Owner, SharedGasTankUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update<GasTankBoundUserInterfaceState>();
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateUserInterface(Entity<GasTankComponent> ent)
|
||||
{
|
||||
if (UI.TryGetOpenUi(ent.Owner, SharedGasTankUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update<GasTankBoundUserInterfaceState>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Atmos.Piping.Binary.Systems;
|
||||
|
||||
public sealed class GasVolumePumpSystem : SharedGasVolumePumpSystem
|
||||
{
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GasVolumePumpComponent, AfterAutoHandleStateEvent>(OnPumpState);
|
||||
}
|
||||
|
||||
protected override void UpdateUi(Entity<GasVolumePumpComponent> entity)
|
||||
{
|
||||
if (_ui.TryGetOpenUi(entity.Owner, GasVolumePumpUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPumpState(Entity<GasVolumePumpComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateUi(ent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Content.Client.Atmos.UI;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Systems;
|
||||
using Content.Shared.NodeContainer;
|
||||
|
||||
namespace Content.Client.Atmos.Piping.Unary.Systems;
|
||||
|
||||
public sealed class GasCanisterSystem : SharedGasCanisterSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GasCanisterComponent, AfterAutoHandleStateEvent>(OnGasState);
|
||||
}
|
||||
|
||||
private void OnGasState(Entity<GasCanisterComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (UI.TryGetOpenUi<GasCanisterBoundUserInterface>(ent.Owner, GasCanisterUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update<GasCanisterBoundUserInterfaceState>();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DirtyUI(EntityUid uid, GasCanisterComponent? component = null, NodeContainerComponent? nodes = null)
|
||||
{
|
||||
if (UI.TryGetOpenUi<GasCanisterBoundUserInterface>(uid, GasCanisterUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update<GasCanisterBoundUserInterfaceState>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Unary.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -32,22 +35,22 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
private void OnTankEjectPressed()
|
||||
{
|
||||
SendMessage(new GasCanisterHoldingTankEjectMessage());
|
||||
SendPredictedMessage(new GasCanisterHoldingTankEjectMessage());
|
||||
}
|
||||
|
||||
private void OnReleasePressureSet(float value)
|
||||
{
|
||||
SendMessage(new GasCanisterChangeReleasePressureMessage(value));
|
||||
SendPredictedMessage(new GasCanisterChangeReleasePressureMessage(value));
|
||||
}
|
||||
|
||||
private void OnReleaseValveOpenPressed()
|
||||
{
|
||||
SendMessage(new GasCanisterChangeReleaseValveMessage(true));
|
||||
SendPredictedMessage(new GasCanisterChangeReleaseValveMessage(true));
|
||||
}
|
||||
|
||||
private void OnReleaseValveClosePressed()
|
||||
{
|
||||
SendMessage(new GasCanisterChangeReleaseValveMessage(false));
|
||||
SendPredictedMessage(new GasCanisterChangeReleaseValveMessage(false));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -57,17 +60,21 @@ namespace Content.Client.Atmos.UI
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (_window == null || state is not GasCanisterBoundUserInterfaceState cast)
|
||||
if (_window == null || state is not GasCanisterBoundUserInterfaceState cast || !EntMan.TryGetComponent(Owner, out GasCanisterComponent? component))
|
||||
return;
|
||||
|
||||
_window.SetCanisterLabel(cast.CanisterLabel);
|
||||
var canisterLabel = Identity.Name(Owner, EntMan);
|
||||
var tankLabel = component.GasTankSlot.Item != null ? Identity.Name(component.GasTankSlot.Item.Value, EntMan) : null;
|
||||
|
||||
_window.SetCanisterLabel(canisterLabel);
|
||||
_window.SetCanisterPressure(cast.CanisterPressure);
|
||||
_window.SetPortStatus(cast.PortStatus);
|
||||
_window.SetTankLabel(cast.TankLabel);
|
||||
|
||||
_window.SetTankLabel(tankLabel);
|
||||
_window.SetTankPressure(cast.TankPressure);
|
||||
_window.SetReleasePressureRange(cast.ReleasePressureMin, cast.ReleasePressureMax);
|
||||
_window.SetReleasePressure(cast.ReleasePressure);
|
||||
_window.SetReleaseValve(cast.ReleaseValve);
|
||||
_window.SetReleasePressureRange(component.MinReleasePressure, component.MaxReleasePressure);
|
||||
_window.SetReleasePressure(component.ReleasePressure);
|
||||
_window.SetReleaseValve(component.ReleaseValve);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
||||
@@ -13,9 +13,6 @@ namespace Content.Client.Atmos.UI;
|
||||
[UsedImplicitly]
|
||||
public sealed class GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private const float MaxPressure = Atmospherics.MaxOutputPressure;
|
||||
|
||||
[ViewVariables]
|
||||
private GasPressurePumpWindow? _window;
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.Atmos.Piping.Binary.Components;
|
||||
using Content.Shared.IdentityManagement;
|
||||
using Content.Shared.Localizations;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Atmos.UI
|
||||
@@ -14,7 +13,7 @@ namespace Content.Client.Atmos.UI
|
||||
public sealed class GasVolumePumpBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
private const float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||
private float _maxTransferRate;
|
||||
|
||||
[ViewVariables]
|
||||
private GasVolumePumpWindow? _window;
|
||||
@@ -29,38 +28,41 @@ namespace Content.Client.Atmos.UI
|
||||
|
||||
_window = this.CreateWindow<GasVolumePumpWindow>();
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out GasVolumePumpComponent? pump))
|
||||
{
|
||||
_maxTransferRate = pump.MaxTransferRate;
|
||||
}
|
||||
|
||||
_window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
|
||||
_window.PumpTransferRateChanged += OnPumpTransferRatePressed;
|
||||
Update();
|
||||
}
|
||||
|
||||
private void OnToggleStatusButtonPressed()
|
||||
{
|
||||
if (_window is null) return;
|
||||
SendMessage(new GasVolumePumpToggleStatusMessage(_window.PumpStatus));
|
||||
|
||||
SendPredictedMessage(new GasVolumePumpToggleStatusMessage(_window.PumpStatus));
|
||||
}
|
||||
|
||||
private void OnPumpTransferRatePressed(string value)
|
||||
{
|
||||
var rate = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
|
||||
if (rate > MaxTransferRate)
|
||||
rate = MaxTransferRate;
|
||||
rate = Math.Clamp(rate, 0f, _maxTransferRate);
|
||||
|
||||
SendMessage(new GasVolumePumpChangeTransferRateMessage(rate));
|
||||
SendPredictedMessage(new GasVolumePumpChangeTransferRateMessage(rate));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the UI state based on server-sent info
|
||||
/// </summary>
|
||||
/// <param name="state"></param>
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
public override void Update()
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (_window == null || state is not GasVolumePumpBoundUserInterfaceState cast)
|
||||
base.Update();
|
||||
|
||||
if (_window is null || !EntMan.TryGetComponent(Owner, out GasVolumePumpComponent? pump))
|
||||
return;
|
||||
|
||||
_window.Title = cast.PumpLabel;
|
||||
_window.SetPumpStatus(cast.Enabled);
|
||||
_window.SetTransferRate(cast.TransferRate);
|
||||
_window.Title = Identity.Name(Owner, EntMan);
|
||||
_window.SetPumpStatus(pump.Enabled);
|
||||
_window.SetTransferRate(pump.TransferRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MinSize="200 120" Title="Volume Pump">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 5 5 5" SeparationOverride="10">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
@@ -19,4 +20,4 @@
|
||||
<Button Name="SetTransferRateButton" Text="{Loc comp-gas-pump-ui-pump-set-rate}" HorizontalAlignment="Right" Disabled="True"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Content.Client.Atmos.EntitySystems;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Atmos;
|
||||
using Content.Shared.Atmos.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
@@ -16,7 +17,7 @@ namespace Content.Client.Atmos.UI
|
||||
/// Client-side UI used to control a gas volume pump.
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GasVolumePumpWindow : DefaultWindow
|
||||
public sealed partial class GasVolumePumpWindow : FancyWindow
|
||||
{
|
||||
public bool PumpStatus = true;
|
||||
|
||||
|
||||
24
Content.Client/Body/Systems/InternalsSystem.cs
Normal file
24
Content.Client/Body/Systems/InternalsSystem.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Content.Shared.Atmos.Components;
|
||||
using Content.Shared.Body.Components;
|
||||
using Content.Shared.Body.Systems;
|
||||
|
||||
namespace Content.Client.Body.Systems;
|
||||
|
||||
public sealed class InternalsSystem : SharedInternalsSystem
|
||||
{
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<InternalsComponent, AfterAutoHandleStateEvent>(OnInternalsAfterState);
|
||||
}
|
||||
|
||||
private void OnInternalsAfterState(Entity<InternalsComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (ent.Comp.GasTankEntity != null && _ui.TryGetOpenUi(ent.Comp.GasTankEntity.Value, SharedGasTankUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Client.Cargo.UI;
|
||||
using Content.Shared.Cargo.BUI;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.IdentityManagement;
|
||||
@@ -14,6 +15,8 @@ namespace Content.Client.Cargo.BUI
|
||||
{
|
||||
public sealed class CargoOrderConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private readonly SharedCargoSystem _cargoSystem;
|
||||
|
||||
[ViewVariables]
|
||||
private CargoConsoleMenu? _menu;
|
||||
|
||||
@@ -43,6 +46,7 @@ namespace Content.Client.Cargo.BUI
|
||||
|
||||
public CargoOrderConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
_cargoSystem = EntMan.System<SharedCargoSystem>();
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
@@ -57,7 +61,7 @@ namespace Content.Client.Cargo.BUI
|
||||
|
||||
string orderRequester;
|
||||
|
||||
if (EntMan.TryGetComponent<MetaDataComponent>(localPlayer, out var metadata))
|
||||
if (EntMan.EntityExists(localPlayer))
|
||||
orderRequester = Identity.Name(localPlayer.Value, EntMan);
|
||||
else
|
||||
orderRequester = string.Empty;
|
||||
@@ -96,41 +100,54 @@ namespace Content.Client.Cargo.BUI
|
||||
}
|
||||
};
|
||||
|
||||
_menu.OnAccountAction += (account, amount) =>
|
||||
{
|
||||
SendMessage(new CargoConsoleWithdrawFundsMessage(account, amount));
|
||||
};
|
||||
|
||||
_menu.OnToggleUnboundedLimit += _ =>
|
||||
{
|
||||
SendMessage(new CargoConsoleToggleLimitMessage());
|
||||
};
|
||||
|
||||
_menu.OpenCentered();
|
||||
}
|
||||
|
||||
private void Populate(List<CargoOrderData> orders)
|
||||
{
|
||||
if (_menu == null) return;
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
_menu.PopulateProducts();
|
||||
_menu.PopulateCategories();
|
||||
_menu.PopulateOrders(orders);
|
||||
_menu.PopulateAccountActions();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not CargoConsoleInterfaceState cState)
|
||||
if (state is not CargoConsoleInterfaceState cState || !EntMan.TryGetComponent<CargoOrderConsoleComponent>(Owner, out var orderConsole))
|
||||
return;
|
||||
var station = EntMan.GetEntity(cState.Station);
|
||||
|
||||
OrderCapacity = cState.Capacity;
|
||||
OrderCount = cState.Count;
|
||||
BankBalance = cState.Balance;
|
||||
BankBalance = _cargoSystem.GetBalanceFromAccount(station, orderConsole.Account);
|
||||
|
||||
AccountName = cState.Name;
|
||||
|
||||
_menu?.UpdateStation(station);
|
||||
Populate(cState.Orders);
|
||||
_menu?.UpdateCargoCapacity(OrderCount, OrderCapacity);
|
||||
_menu?.UpdateBankData(AccountName, BankBalance);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing) return;
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
_menu?.Dispose();
|
||||
_orderMenu?.Dispose();
|
||||
@@ -170,8 +187,6 @@ namespace Content.Client.Cargo.BUI
|
||||
return;
|
||||
|
||||
SendMessage(new CargoConsoleApproveOrderMessage(row.Order.OrderId));
|
||||
// Most of the UI isn't predicted anyway so.
|
||||
// _menu?.UpdateCargoCapacity(OrderCount + row.Order.Amount, OrderCapacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using Content.Client.Cargo.UI;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Cargo.BUI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class FundingAllocationConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||
{
|
||||
[ViewVariables]
|
||||
private FundingAllocationMenu? _menu;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_menu = this.CreateWindow<FundingAllocationMenu>();
|
||||
|
||||
_menu.OnSavePressed += (dicts, primary, lockbox) =>
|
||||
{
|
||||
SendMessage(new SetFundingAllocationBuiMessage(dicts, primary, lockbox));
|
||||
};
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState message)
|
||||
{
|
||||
base.UpdateState(message);
|
||||
|
||||
if (message is not FundingAllocationConsoleBuiState state)
|
||||
return;
|
||||
|
||||
_menu?.Update(state);
|
||||
}
|
||||
}
|
||||
@@ -75,10 +75,9 @@ public sealed partial class CargoSystem
|
||||
switch (state)
|
||||
{
|
||||
case CargoTelepadState.Teleporting:
|
||||
if (_player.HasRunningAnimation(uid, TelepadBeamKey))
|
||||
return;
|
||||
_player.Stop(uid, player, TelepadIdleKey);
|
||||
_player.Play((uid, player), CargoTelepadBeamAnimation, TelepadBeamKey);
|
||||
_player.Stop((uid, player), TelepadIdleKey);
|
||||
if (!_player.HasRunningAnimation(uid, TelepadBeamKey))
|
||||
_player.Play((uid, player), CargoTelepadBeamAnimation, TelepadBeamKey);
|
||||
break;
|
||||
case CargoTelepadState.Unpowered:
|
||||
sprite.LayerSetVisible(CargoTelepadLayers.Beam, false);
|
||||
|
||||
@@ -3,66 +3,84 @@
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
SetSize="600 600"
|
||||
MinSize="600 600">
|
||||
<BoxContainer Orientation="Vertical" Margin="5 0 5 0">
|
||||
<BoxContainer Orientation="Vertical" Margin="15 5 15 10">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'cargo-console-menu-account-name-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
<Label Name="AccountNameLabel"
|
||||
<RichTextLabel Name="AccountNameLabel"
|
||||
Text="{Loc 'cargo-console-menu-account-name-none-text'}" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'cargo-console-menu-points-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
<Label Name="PointsLabel"
|
||||
<RichTextLabel Name="PointsLabel"
|
||||
Text="$0" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'cargo-console-menu-order-capacity-label'}"
|
||||
StyleClasses="LabelKeyText" />
|
||||
<Label Name="ShuttleCapacityLabel"
|
||||
Text="0/20" />
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<OptionButton Name="Categories"
|
||||
Prefix="{Loc 'cargo-console-menu-categories-label'}"
|
||||
HorizontalExpand="True" />
|
||||
<LineEdit Name="SearchBar"
|
||||
PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<ScrollContainer HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="6">
|
||||
<BoxContainer Name="Products"
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<!-- Products get added here by code -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<PanelContainer VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="6">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#000000" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Text="{Loc 'cargo-console-menu-requests-label'}" />
|
||||
<BoxContainer Name="Requests"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True">
|
||||
<!-- Requests are added here by code -->
|
||||
</BoxContainer>
|
||||
<Label Text="{Loc 'cargo-console-menu-orders-label'}" />
|
||||
<BoxContainer Name="Orders"
|
||||
Orientation="Vertical"
|
||||
StyleClasses="transparentItemList"
|
||||
VerticalExpand="True">
|
||||
<!-- Orders are added here by code -->
|
||||
</BoxContainer>
|
||||
<Control MinHeight="10"/>
|
||||
<TabContainer Name="TabContainer" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" VerticalExpand="True">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<OptionButton Name="Categories"
|
||||
Prefix="{Loc 'cargo-console-menu-categories-label'}"
|
||||
HorizontalExpand="True" />
|
||||
<LineEdit Name="SearchBar"
|
||||
PlaceHolder="{Loc 'cargo-console-menu-search-bar-placeholder'}"
|
||||
HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
<TextureButton VerticalExpand="True" />
|
||||
<Control MinHeight="5"/>
|
||||
<ScrollContainer HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="2">
|
||||
<BoxContainer Name="Products"
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<!-- Products get added here by code -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<Control MinHeight="5" Name="OrdersSpacer"/>
|
||||
<PanelContainer VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="1"
|
||||
Name="Orders">
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#000000" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<ScrollContainer VerticalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Margin="5">
|
||||
<Label Text="{Loc 'cargo-console-menu-requests-label'}" />
|
||||
<BoxContainer Name="Requests"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True">
|
||||
<!-- Requests are added here by code -->
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
<!-- Funds tab -->
|
||||
<BoxContainer Orientation="Vertical" Margin="15">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="TransferLimitLabel" Margin="0 0 15 0"/>
|
||||
<RichTextLabel Name="UnlimitedNotifier" Text="{Loc 'cargo-console-menu-account-action-transfer-limit-unlimited-notifier'}"/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Text="{Loc 'cargo-console-menu-account-action-select'}" Margin="0 0 10 0"/>
|
||||
<OptionButton Name="ActionOptions"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="5"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<RichTextLabel Name="AmountText" Text="{ Loc 'cargo-console-menu-account-action-amount'}"/>
|
||||
<SpinBox Name="TransferSpinBox" MinWidth="100" Value="10"/>
|
||||
</BoxContainer>
|
||||
<Control MinHeight="15"/>
|
||||
<BoxContainer HorizontalAlignment="Center">
|
||||
<Button Name="AccountActionButton" Text="{ Loc 'cargo-console-menu-account-action-button'}" MinHeight="45" MinWidth="120"/>
|
||||
</BoxContainer>
|
||||
<Control VerticalExpand="True"/>
|
||||
<BoxContainer VerticalAlignment="Bottom" HorizontalAlignment="Center">
|
||||
<Button Name="AccountLimitToggleButton" Text="{ Loc 'cargo-console-menu-toggle-account-lock-button'}" MinHeight="45" MinWidth="120"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</TabContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Cargo.Systems;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
@@ -8,6 +9,7 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Cargo.UI
|
||||
@@ -15,30 +17,83 @@ namespace Content.Client.Cargo.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CargoConsoleMenu : FancyWindow
|
||||
{
|
||||
private IEntityManager _entityManager;
|
||||
private IPrototypeManager _protoManager;
|
||||
private SpriteSystem _spriteSystem;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IPrototypeManager _protoManager;
|
||||
private readonly CargoSystem _cargoSystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
private EntityUid _owner;
|
||||
private EntityUid? _station;
|
||||
|
||||
private readonly EntityQuery<CargoOrderConsoleComponent> _orderConsoleQuery;
|
||||
private readonly EntityQuery<StationBankAccountComponent> _bankQuery;
|
||||
|
||||
public event Action<ButtonEventArgs>? OnItemSelected;
|
||||
public event Action<ButtonEventArgs>? OnOrderApproved;
|
||||
public event Action<ButtonEventArgs>? OnOrderCanceled;
|
||||
|
||||
public event Action<ProtoId<CargoAccountPrototype>?, int>? OnAccountAction;
|
||||
|
||||
public event Action<ButtonEventArgs>? OnToggleUnboundedLimit;
|
||||
|
||||
private readonly List<string> _categoryStrings = new();
|
||||
private string? _category;
|
||||
|
||||
public CargoConsoleMenu(EntityUid owner, IEntityManager entMan, IPrototypeManager protoManager, SpriteSystem spriteSystem)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_entityManager = entMan;
|
||||
_protoManager = protoManager;
|
||||
_cargoSystem = entMan.System<CargoSystem>();
|
||||
_spriteSystem = spriteSystem;
|
||||
_owner = owner;
|
||||
|
||||
Title = Loc.GetString("cargo-console-menu-title");
|
||||
_orderConsoleQuery = _entityManager.GetEntityQuery<CargoOrderConsoleComponent>();
|
||||
_bankQuery = _entityManager.GetEntityQuery<StationBankAccountComponent>();
|
||||
|
||||
Title = entMan.GetComponent<MetaDataComponent>(owner).EntityName;
|
||||
|
||||
SearchBar.OnTextChanged += OnSearchBarTextChanged;
|
||||
Categories.OnItemSelected += OnCategoryItemSelected;
|
||||
|
||||
if (entMan.TryGetComponent<CargoOrderConsoleComponent>(owner, out var orderConsole))
|
||||
{
|
||||
var accountProto = _protoManager.Index(orderConsole.Account);
|
||||
AccountNameLabel.Text = Loc.GetString("cargo-console-menu-account-name-format",
|
||||
("color", accountProto.Color),
|
||||
("name", Loc.GetString(accountProto.Name)),
|
||||
("code", Loc.GetString(accountProto.Code)));
|
||||
}
|
||||
|
||||
TabContainer.SetTabTitle(0, Loc.GetString("cargo-console-menu-tab-title-orders"));
|
||||
TabContainer.SetTabTitle(1, Loc.GetString("cargo-console-menu-tab-title-funds"));
|
||||
|
||||
ActionOptions.OnItemSelected += idx =>
|
||||
{
|
||||
ActionOptions.SelectId(idx.Id);
|
||||
};
|
||||
|
||||
TransferSpinBox.IsValid = val =>
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<CargoOrderConsoleComponent>(owner, out var console) ||
|
||||
!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
|
||||
return true;
|
||||
|
||||
return val >= 0 && val <= (int) (console.TransferLimit * bank.Accounts[console.Account]);
|
||||
};
|
||||
|
||||
AccountActionButton.OnPressed += _ =>
|
||||
{
|
||||
var account = (ProtoId<CargoAccountPrototype>?) ActionOptions.SelectedMetadata;
|
||||
OnAccountAction?.Invoke(account, TransferSpinBox.Value);
|
||||
};
|
||||
|
||||
AccountLimitToggleButton.OnPressed += a =>
|
||||
{
|
||||
OnToggleUnboundedLimit?.Invoke(a);
|
||||
};
|
||||
}
|
||||
|
||||
private void OnCategoryItemSelected(OptionButton.ItemSelectedEventArgs args)
|
||||
@@ -144,13 +199,16 @@ namespace Content.Client.Cargo.UI
|
||||
/// </summary>
|
||||
public void PopulateOrders(IEnumerable<CargoOrderData> orders)
|
||||
{
|
||||
Orders.DisposeAllChildren();
|
||||
Requests.DisposeAllChildren();
|
||||
|
||||
foreach (var order in orders)
|
||||
{
|
||||
if (order.Approved)
|
||||
continue;
|
||||
|
||||
var product = _protoManager.Index<EntityPrototype>(order.ProductId);
|
||||
var productName = product.Name;
|
||||
var account = _protoManager.Index(order.Account);
|
||||
|
||||
var row = new CargoOrderRow
|
||||
{
|
||||
@@ -162,37 +220,74 @@ namespace Content.Client.Cargo.UI
|
||||
"cargo-console-menu-populate-orders-cargo-order-row-product-name-text",
|
||||
("productName", productName),
|
||||
("orderAmount", order.OrderQuantity),
|
||||
("orderRequester", order.Requester))
|
||||
("orderRequester", order.Requester),
|
||||
("accountColor", account.Color),
|
||||
("account", Loc.GetString(account.Code)))
|
||||
},
|
||||
Description = {Text = Loc.GetString("cargo-console-menu-order-reason-description",
|
||||
("reason", order.Reason))}
|
||||
Description =
|
||||
{
|
||||
Text = Loc.GetString("cargo-console-menu-order-reason-description",
|
||||
("reason", order.Reason))
|
||||
}
|
||||
};
|
||||
row.Cancel.OnPressed += (args) => { OnOrderCanceled?.Invoke(args); };
|
||||
if (order.Approved)
|
||||
{
|
||||
row.Approve.Visible = false;
|
||||
row.Cancel.Visible = false;
|
||||
Orders.AddChild(row);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Disable based on access.
|
||||
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
|
||||
Requests.AddChild(row);
|
||||
}
|
||||
|
||||
// TODO: Disable based on access.
|
||||
row.Approve.OnPressed += (args) => { OnOrderApproved?.Invoke(args); };
|
||||
Requests.AddChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCargoCapacity(int count, int capacity)
|
||||
public void PopulateAccountActions()
|
||||
{
|
||||
// TODO: Rename + Loc.
|
||||
ShuttleCapacityLabel.Text = $"{count}/{capacity}";
|
||||
if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank) ||
|
||||
!_entityManager.TryGetComponent<CargoOrderConsoleComponent>(_owner, out var console))
|
||||
return;
|
||||
|
||||
var i = 0;
|
||||
ActionOptions.Clear();
|
||||
ActionOptions.AddItem(Loc.GetString("cargo-console-menu-account-action-option-withdraw"), i);
|
||||
i++;
|
||||
foreach (var account in bank.Accounts.Keys)
|
||||
{
|
||||
if (account == console.Account)
|
||||
continue;
|
||||
var accountProto = _protoManager.Index(account);
|
||||
ActionOptions.AddItem(Loc.GetString("cargo-console-menu-account-action-option-transfer",
|
||||
("code", Loc.GetString(accountProto.Code))),
|
||||
i);
|
||||
ActionOptions.SetItemMetadata(i, account);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateBankData(string name, int points)
|
||||
public void UpdateStation(EntityUid station)
|
||||
{
|
||||
AccountNameLabel.Text = name;
|
||||
PointsLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", points.ToString()));
|
||||
_station = station;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!_bankQuery.TryComp(_station, out var bankAccount) ||
|
||||
!_orderConsoleQuery.TryComp(_owner, out var orderConsole))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var balance = _cargoSystem.GetBalanceFromAccount((_station.Value, bankAccount), orderConsole.Account);
|
||||
PointsLabel.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", balance));
|
||||
TransferLimitLabel.Text = Loc.GetString("cargo-console-menu-account-action-transfer-limit",
|
||||
("limit", (int) (balance * orderConsole.TransferLimit)));
|
||||
|
||||
UnlimitedNotifier.Visible = orderConsole.TransferUnbounded;
|
||||
AccountActionButton.Disabled = TransferSpinBox.Value <= 0 ||
|
||||
TransferSpinBox.Value > bankAccount.Accounts[orderConsole.Account] * orderConsole.TransferLimit ||
|
||||
_timing.CurTime < orderConsole.NextAccountActionTime;
|
||||
|
||||
OrdersSpacer.Visible = !orderConsole.SlipPrinter;
|
||||
Orders.Visible = !orderConsole.SlipPrinter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<PanelContainer xmlns="https://spacestation14.io"
|
||||
HorizontalExpand="True">
|
||||
HorizontalExpand="True"
|
||||
Margin="0 1">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<TextureRect Name="Icon"
|
||||
Access="Public"
|
||||
MinSize="32 32"
|
||||
RectClipContent="True" />
|
||||
<Control MinWidth="5"/>
|
||||
<BoxContainer Orientation="Vertical"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<Label Name="ProductName"
|
||||
<RichTextLabel Name="ProductName"
|
||||
Access="Public"
|
||||
HorizontalExpand="True"
|
||||
StyleClasses="LabelSubText"
|
||||
ClipText="True" />
|
||||
StyleClasses="LabelSubText" />
|
||||
<Label Name="Description"
|
||||
Access="Public"
|
||||
HorizontalExpand="True"
|
||||
@@ -23,10 +24,10 @@
|
||||
<Button Name="Approve"
|
||||
Access="Public"
|
||||
Text="{Loc 'cargo-console-menu-cargo-order-row-approve-button'}"
|
||||
StyleClasses="LabelSubText" />
|
||||
StyleClasses="OpenRight" />
|
||||
<Button Name="Cancel"
|
||||
Access="Public"
|
||||
Text="{Loc 'cargo-console-menu-cargo-order-row-cancel-button'}"
|
||||
StyleClasses="LabelSubText" />
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
ToolTip=""
|
||||
Access="Public"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True" />
|
||||
VerticalExpand="True"
|
||||
StyleClasses="OpenBoth"/>
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<TextureRect Name="Icon"
|
||||
@@ -18,7 +19,8 @@
|
||||
<Label Name="PointCost"
|
||||
Access="Public"
|
||||
MinSize="52 32"
|
||||
Align="Right" />
|
||||
Align="Right"
|
||||
Margin="0 0 5 0"/>
|
||||
</PanelContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Content.Client.Cargo.UI
|
||||
{
|
||||
var product = protoManager.Index<EntityPrototype>(order.ProductId);
|
||||
var productName = product.Name;
|
||||
var account = protoManager.Index(order.Account);
|
||||
|
||||
var row = new CargoOrderRow
|
||||
{
|
||||
@@ -47,7 +48,9 @@ namespace Content.Client.Cargo.UI
|
||||
"cargo-console-menu-populate-orders-cargo-order-row-product-name-text",
|
||||
("productName", productName),
|
||||
("orderAmount", order.OrderQuantity - order.NumDispatched),
|
||||
("orderRequester", order.Requester))
|
||||
("orderRequester", order.Requester),
|
||||
("accountColor", account.Color),
|
||||
("account", Loc.GetString(account.Code)))
|
||||
},
|
||||
Description = {Text = Loc.GetString("cargo-console-menu-order-reason-description",
|
||||
("reason", order.Reason))}
|
||||
|
||||
32
Content.Client/Cargo/UI/FundingAllocationMenu.xaml
Normal file
32
Content.Client/Cargo/UI/FundingAllocationMenu.xaml
Normal file
@@ -0,0 +1,32 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||
Title="{Loc 'cargo-funding-alloc-console-menu-title'}">
|
||||
<BoxContainer Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True"
|
||||
Margin="10 5 10 10">
|
||||
<controls:TableContainer Columns="2" HorizontalExpand="True" VerticalExpand="True">
|
||||
<RichTextLabel Name="PrimaryCutLabel" Text="{Loc 'cargo-funding-alloc-console-label-primary-cut'}"/>
|
||||
<SpinBox Name="PrimaryCut"/>
|
||||
<RichTextLabel Name="LockboxCutLabel" Text="{Loc 'cargo-funding-alloc-console-label-lockbox-cut'}"/>
|
||||
<SpinBox Name="LockboxCut"/>
|
||||
</controls:TableContainer>
|
||||
<Label Name="HelpLabel" HorizontalAlignment="Center" StyleClasses="LabelSubText" Margin="0 10"/>
|
||||
<PanelContainer VerticalExpand="True" HorizontalExpand="True" VerticalAlignment="Top" MaxHeight="250">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E"/>
|
||||
</PanelContainer.PanelOverride>
|
||||
<controls:TableContainer Name="EntriesContainer" Columns="4" HorizontalExpand="True" VerticalExpand="True" Margin="5 0">
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-account'}" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-code'}" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-balance'}" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel Text="{Loc 'cargo-funding-alloc-console-label-cut'}" HorizontalAlignment="Center"/>
|
||||
</controls:TableContainer>
|
||||
</PanelContainer>
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="5 0">
|
||||
<Button Name="SaveButton" Text="{Loc 'cargo-funding-alloc-console-button-save'}" Disabled="True"/>
|
||||
<RichTextLabel Name="SaveAlertLabel" HorizontalExpand="True" HorizontalAlignment="Right" Visible="False"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
229
Content.Client/Cargo/UI/FundingAllocationMenu.xaml.cs
Normal file
229
Content.Client/Cargo/UI/FundingAllocationMenu.xaml.cs
Normal file
@@ -0,0 +1,229 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Cargo.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class FundingAllocationMenu : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private readonly EntityQuery<StationBankAccountComponent> _bankQuery;
|
||||
|
||||
public event Action<Dictionary<ProtoId<CargoAccountPrototype>, int>, double, double>? OnSavePressed;
|
||||
|
||||
private EntityUid? _station;
|
||||
private bool _allowPrimaryAccountAllocation;
|
||||
private bool _allowPrimaryCutAdjustment;
|
||||
private bool _lockboxCutEnabled;
|
||||
|
||||
private double _primaryCut;
|
||||
private double _lockboxCut;
|
||||
|
||||
private readonly HashSet<Control> _addedControls = new();
|
||||
private readonly List<SpinBox> _spinBoxes = new();
|
||||
private readonly Dictionary<ProtoId<CargoAccountPrototype>, RichTextLabel> _balanceLabels = new();
|
||||
|
||||
public FundingAllocationMenu()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_bankQuery = _entityManager.GetEntityQuery<StationBankAccountComponent>();
|
||||
|
||||
PrimaryCut.ValueChanged += args =>
|
||||
{
|
||||
_primaryCut = (double)args.Value / 100.0;
|
||||
UpdateButtonDisabled();
|
||||
};
|
||||
|
||||
LockboxCut.ValueChanged += args =>
|
||||
{
|
||||
_lockboxCut = 1.0 - (double)args.Value / 100.0;
|
||||
UpdateButtonDisabled();
|
||||
};
|
||||
|
||||
SaveButton.OnPressed += _ =>
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
|
||||
return;
|
||||
var accounts = EditableAccounts(bank).OrderBy(p => p.Key).Select(p => p.Key).ToList();
|
||||
var dicts = new Dictionary<ProtoId<CargoAccountPrototype>, int>();
|
||||
for (var i = 0; i< accounts.Count; i++)
|
||||
{
|
||||
dicts.Add(accounts[i], _spinBoxes[i].Value);
|
||||
}
|
||||
|
||||
OnSavePressed?.Invoke(dicts, _primaryCut, _lockboxCut);
|
||||
SaveButton.Disabled = true;
|
||||
};
|
||||
|
||||
_cfg.OnValueChanged(CCVars.AllowPrimaryAccountAllocation, enabled => { _allowPrimaryAccountAllocation = enabled; }, true);
|
||||
_cfg.OnValueChanged(CCVars.AllowPrimaryCutAdjustment, enabled => { _allowPrimaryCutAdjustment = enabled; }, true);
|
||||
_cfg.OnValueChanged(CCVars.LockboxCutEnabled, enabled => { _lockboxCutEnabled = enabled; }, true);
|
||||
|
||||
BuildEntries();
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<ProtoId<CargoAccountPrototype>, int>> EditableAccounts(StationBankAccountComponent bank)
|
||||
{
|
||||
foreach (var kvp in bank.Accounts)
|
||||
{
|
||||
if (_allowPrimaryAccountAllocation || kvp.Key != bank.PrimaryAccount)
|
||||
{
|
||||
yield return kvp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildEntries()
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
|
||||
return;
|
||||
|
||||
if (_allowPrimaryCutAdjustment)
|
||||
{
|
||||
HelpLabel.Text = Loc.GetString("cargo-funding-alloc-console-label-help-adjustible");
|
||||
}
|
||||
else
|
||||
{
|
||||
HelpLabel.Text = Loc.GetString("cargo-funding-alloc-console-label-help-non-adjustible",
|
||||
("percent", (int) (bank.PrimaryCut * 100)));
|
||||
}
|
||||
|
||||
foreach (var ctrl in _addedControls)
|
||||
{
|
||||
ctrl.Orphan();
|
||||
}
|
||||
|
||||
_addedControls.Clear();
|
||||
_spinBoxes.Clear();
|
||||
_balanceLabels.Clear();
|
||||
|
||||
_primaryCut = bank.PrimaryCut;
|
||||
_lockboxCut = bank.LockboxCut;
|
||||
|
||||
LockboxCut.OverrideValue(100 - (int)(_lockboxCut * 100));
|
||||
PrimaryCut.OverrideValue((int)(_primaryCut * 100));
|
||||
|
||||
LockboxCut.IsValid = val => val is >= 0 and <= 100;
|
||||
PrimaryCut.IsValid = val => val is >= 0 and <= 100;
|
||||
|
||||
LockboxCut.Visible = _lockboxCutEnabled;
|
||||
LockboxCutLabel.Visible = _lockboxCutEnabled;
|
||||
PrimaryCut.Visible = _allowPrimaryCutAdjustment;
|
||||
PrimaryCutLabel.Visible = _allowPrimaryCutAdjustment;
|
||||
|
||||
var accounts = EditableAccounts(bank).OrderBy(p => p.Key);
|
||||
foreach (var (account, balance) in accounts)
|
||||
{
|
||||
var accountProto = _prototypeManager.Index(account);
|
||||
|
||||
var accountNameLabel = new RichTextLabel
|
||||
{
|
||||
Modulate = accountProto.Color,
|
||||
Margin = new Thickness(0, 0, 10, 0)
|
||||
};
|
||||
accountNameLabel.SetMarkup($"[bold]{Loc.GetString(accountProto.Name)}[/bold]");
|
||||
EntriesContainer.AddChild(accountNameLabel);
|
||||
|
||||
var codeLabel = new RichTextLabel
|
||||
{
|
||||
Text = $"[font=\"Monospace\"]{Loc.GetString(accountProto.Code)}[/font]",
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Margin = new Thickness(5, 0),
|
||||
};
|
||||
EntriesContainer.AddChild(codeLabel);
|
||||
|
||||
var balanceLabel = new RichTextLabel
|
||||
{
|
||||
Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", balance)),
|
||||
HorizontalExpand = true,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Margin = new Thickness(5, 0),
|
||||
};
|
||||
EntriesContainer.AddChild(balanceLabel);
|
||||
|
||||
var box = new SpinBox
|
||||
{
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
Value = (int) (bank.RevenueDistribution[account] * 100),
|
||||
IsValid = val => val is >= 0 and <= 100,
|
||||
};
|
||||
box.ValueChanged += _ => UpdateButtonDisabled();
|
||||
EntriesContainer.AddChild(box);
|
||||
|
||||
_spinBoxes.Add(box);
|
||||
_balanceLabels.Add(account, balanceLabel);
|
||||
_addedControls.Add(accountNameLabel);
|
||||
_addedControls.Add(codeLabel);
|
||||
_addedControls.Add(balanceLabel);
|
||||
_addedControls.Add(box);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateButtonDisabled()
|
||||
{
|
||||
if (!_entityManager.TryGetComponent<StationBankAccountComponent>(_station, out var bank))
|
||||
return;
|
||||
|
||||
var sum = _spinBoxes.Sum(s => s.Value);
|
||||
var incorrectSum = sum != 100;
|
||||
|
||||
var differs = false;
|
||||
var accounts = EditableAccounts(bank).OrderBy(p => p.Key).Select(p => p.Key).ToList();
|
||||
for (var i = 0; i < accounts.Count; i++)
|
||||
{
|
||||
var percent = _spinBoxes[i].Value;
|
||||
if (percent != (int) Math.Round(bank.RevenueDistribution[accounts[i]] * 100))
|
||||
{
|
||||
differs = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
differs = differs || _primaryCut != bank.PrimaryCut || _lockboxCut != bank.LockboxCut;
|
||||
|
||||
SaveButton.Disabled = !differs || incorrectSum;
|
||||
|
||||
var diff = sum - 100;
|
||||
SaveAlertLabel.Visible = incorrectSum;
|
||||
SaveAlertLabel.SetMarkup(Loc.GetString("cargo-funding-alloc-console-label-save-fail",
|
||||
("pos", Math.Sign(diff)),
|
||||
("val", Math.Abs(diff))));
|
||||
}
|
||||
|
||||
public void Update(FundingAllocationConsoleBuiState state)
|
||||
{
|
||||
_station = _entityManager.GetEntity(state.Station);
|
||||
BuildEntries();
|
||||
UpdateButtonDisabled();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!_bankQuery.TryComp(_station, out var bank))
|
||||
return;
|
||||
|
||||
foreach (var (account, label) in _balanceLabels)
|
||||
{
|
||||
label.Text = Loc.GetString("cargo-console-menu-points-amount", ("amount", bank.Accounts[account]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,10 +90,12 @@ public sealed partial class NanoTaskItemPopup : DefaultWindow
|
||||
{
|
||||
if (item is NanoTaskItem task)
|
||||
{
|
||||
var button = task.Priority switch {
|
||||
var button = task.Priority switch
|
||||
{
|
||||
NanoTaskPriority.High => HighButton,
|
||||
NanoTaskPriority.Medium => MediumButton,
|
||||
NanoTaskPriority.Low => LowButton,
|
||||
_ => throw new ArgumentException("Invalid priority"),
|
||||
};
|
||||
button.Pressed = true;
|
||||
DescriptionInput.Text = task.Description;
|
||||
|
||||
@@ -38,10 +38,12 @@ public sealed partial class NanoTaskUiFragment : BoxContainer
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
var container = task.Data.Priority switch {
|
||||
var container = task.Data.Priority switch
|
||||
{
|
||||
NanoTaskPriority.High => HighContainer,
|
||||
NanoTaskPriority.Medium => MediumContainer,
|
||||
NanoTaskPriority.Low => LowContainer,
|
||||
_ => throw new ArgumentException("Invalid priority"),
|
||||
};
|
||||
var control = new NanoTaskItemControl(task);
|
||||
container.AddChild(control);
|
||||
|
||||
52
Content.Client/Charges/ChargesSystem.cs
Normal file
52
Content.Client/Charges/ChargesSystem.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using Content.Client.Actions;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Charges.Components;
|
||||
using Content.Shared.Charges.Systems;
|
||||
|
||||
namespace Content.Client.Charges;
|
||||
|
||||
public sealed class ChargesSystem : SharedChargesSystem
|
||||
{
|
||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||
|
||||
private Dictionary<EntityUid, int> _lastCharges = new();
|
||||
private Dictionary<EntityUid, int> _tempLastCharges = new();
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
// Technically this should probably be in frameupdate but no one will ever notice a tick of delay on this.
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
// Update recharging actions. Server doesn't actually care about this and it's a waste of performance, actions are immediate.
|
||||
var query = AllEntityQuery<AutoRechargeComponent, LimitedChargesComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var recharge, out var charges))
|
||||
{
|
||||
BaseActionComponent? actionComp = null;
|
||||
|
||||
if (!_actions.ResolveActionData(uid, ref actionComp, logError: false))
|
||||
continue;
|
||||
|
||||
var current = GetCurrentCharges((uid, charges, recharge));
|
||||
|
||||
if (!_lastCharges.TryGetValue(uid, out var last) || current != last)
|
||||
{
|
||||
_actions.UpdateAction(uid, actionComp);
|
||||
}
|
||||
|
||||
_tempLastCharges[uid] = current;
|
||||
}
|
||||
|
||||
_lastCharges.Clear();
|
||||
|
||||
foreach (var (uid, value) in _tempLastCharges)
|
||||
{
|
||||
_lastCharges[uid] = value;
|
||||
}
|
||||
|
||||
_tempLastCharges.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
using Content.Shared.Charges.Systems;
|
||||
|
||||
namespace Content.Client.Charges.Systems;
|
||||
|
||||
public sealed class ChargesSystem : SharedChargesSystem { }
|
||||
@@ -16,7 +16,7 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
|
||||
private readonly TimeSpan _typingTimeout = TimeSpan.FromSeconds(2);
|
||||
private TimeSpan _lastTextChange;
|
||||
private bool _isClientTyping;
|
||||
private bool _isClientChatFocused; // Corvax-TypingIndicator
|
||||
private bool _isClientChatFocused;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -32,10 +32,8 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
|
||||
return;
|
||||
|
||||
// client typed something - show typing indicator
|
||||
// Corvax-TypingIndicator-Start
|
||||
_isClientTyping = true;
|
||||
ClientUpdateTyping();
|
||||
// Corvax-TypingIndicator-End
|
||||
_lastTextChange = _time.CurTime;
|
||||
}
|
||||
|
||||
@@ -46,14 +44,10 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
|
||||
return;
|
||||
|
||||
// client submitted text - hide typing indicator
|
||||
// Corvax-TypingIndicator-Start
|
||||
_isClientTyping = false;
|
||||
_isClientChatFocused = false;
|
||||
ClientUpdateTyping();
|
||||
// Corvax-TypingIndicator-End
|
||||
}
|
||||
|
||||
// Corvax-TypingIndicator-Start
|
||||
public void ClientChangedChatFocus(bool isFocused)
|
||||
{
|
||||
// don't update it if player don't want to show typing
|
||||
@@ -64,7 +58,6 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
|
||||
_isClientChatFocused = isFocused;
|
||||
ClientUpdateTyping();
|
||||
}
|
||||
// Corvax-TypingIndicator-End
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
@@ -76,34 +69,25 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
|
||||
var dif = _time.CurTime - _lastTextChange;
|
||||
if (dif > _typingTimeout)
|
||||
{
|
||||
// client didn't typed anything for a long time - hide indicator
|
||||
// Corvax-TypingIndicator-Start
|
||||
// client didn't typed anything for a long time - change indicator
|
||||
_isClientTyping = false;
|
||||
ClientUpdateTyping();
|
||||
// Corvax-TypingIndicator-End
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClientUpdateTyping() // Corvax-TypingIndicator
|
||||
private void ClientUpdateTyping()
|
||||
{
|
||||
// Corvax-TypingIndicator-Start
|
||||
// if (_isClientTyping == isClientTyping)
|
||||
// return;
|
||||
// Corvax-TypingIndicator-End
|
||||
|
||||
// check if player controls any entity.
|
||||
// check if player controls any pawn
|
||||
if (_playerManager.LocalEntity == null)
|
||||
return;
|
||||
|
||||
// Corvax-TypingIndicator-Start
|
||||
// _isClientTyping = isClientTyping;
|
||||
var state = TypingIndicatorState.None;
|
||||
if (_isClientChatFocused)
|
||||
state = _isClientTyping ? TypingIndicatorState.Typing : TypingIndicatorState.Idle;
|
||||
// Corvax-TypingIndicator-End
|
||||
|
||||
// send a networked event to server
|
||||
RaisePredictiveEvent(new TypingChangedEvent(state)); // Corvax-TypingIndicator
|
||||
RaisePredictiveEvent(new TypingChangedEvent(state));
|
||||
}
|
||||
|
||||
private void OnShowTypingChanged(bool showTyping)
|
||||
@@ -111,10 +95,8 @@ public sealed class TypingIndicatorSystem : SharedTypingIndicatorSystem
|
||||
// hide typing indicator immediately if player don't want to show it anymore
|
||||
if (!showTyping)
|
||||
{
|
||||
// Corvax-TypingIndicator-Start
|
||||
_isClientTyping = false;
|
||||
ClientUpdateTyping();
|
||||
// Corvax-TypingIndicator-End
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Chat.TypingIndicator;
|
||||
using Content.Shared.Chat.TypingIndicator;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -35,7 +35,6 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
|
||||
return;
|
||||
}
|
||||
|
||||
//AppearanceSystem.TryGetData<bool>(uid, TypingIndicatorVisuals.IsTyping, out var isTyping, args.Component); // Corvax-TypingIndicator
|
||||
var layerExists = args.Sprite.LayerMapTryGet(TypingIndicatorLayers.Base, out var layer);
|
||||
if (!layerExists)
|
||||
layer = args.Sprite.LayerMapReserveBlank(TypingIndicatorLayers.Base);
|
||||
@@ -44,8 +43,7 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
|
||||
args.Sprite.LayerSetState(layer, proto.TypingState);
|
||||
args.Sprite.LayerSetShader(layer, proto.Shader);
|
||||
args.Sprite.LayerSetOffset(layer, proto.Offset);
|
||||
// args.Sprite.LayerSetVisible(layer, isTyping); // Corvax-TypingIndicator
|
||||
// Corvax-TypingIndicator-Start
|
||||
|
||||
AppearanceSystem.TryGetData<TypingIndicatorState>(uid, TypingIndicatorVisuals.State, out var state);
|
||||
args.Sprite.LayerSetVisible(layer, state != TypingIndicatorState.None);
|
||||
switch (state)
|
||||
@@ -57,6 +55,5 @@ public sealed class TypingIndicatorVisualizerSystem : VisualizerSystem<TypingInd
|
||||
args.Sprite.LayerSetState(layer, proto.TypingState);
|
||||
break;
|
||||
}
|
||||
// Corvax-TypingIndicator-End
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,10 @@ namespace Content.Client.Chemistry.UI
|
||||
("1", ChemMasterReagentAmount.U1, StyleBase.ButtonOpenBoth),
|
||||
("5", ChemMasterReagentAmount.U5, StyleBase.ButtonOpenBoth),
|
||||
("10", ChemMasterReagentAmount.U10, StyleBase.ButtonOpenBoth),
|
||||
("15", ChemMasterReagentAmount.U15, StyleBase.ButtonOpenBoth),
|
||||
("20", ChemMasterReagentAmount.U20, StyleBase.ButtonOpenBoth),
|
||||
("25", ChemMasterReagentAmount.U25, StyleBase.ButtonOpenBoth),
|
||||
("30", ChemMasterReagentAmount.U30, StyleBase.ButtonOpenBoth),
|
||||
("50", ChemMasterReagentAmount.U50, StyleBase.ButtonOpenBoth),
|
||||
("100", ChemMasterReagentAmount.U100, StyleBase.ButtonOpenBoth),
|
||||
(Loc.GetString("chem-master-window-buffer-all-amount"), ChemMasterReagentAmount.All, StyleBase.ButtonOpenLeft),
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed class HyposprayStatusControl : Control
|
||||
PrevMaxVolume = solution.MaxVolume;
|
||||
PrevOnlyAffectsMobs = _parent.Comp.OnlyAffectsMobs;
|
||||
|
||||
var modeStringLocalized = Loc.GetString(_parent.Comp.OnlyAffectsMobs switch
|
||||
var modeStringLocalized = Loc.GetString((_parent.Comp.OnlyAffectsMobs && _parent.Comp.CanContainerDraw) switch
|
||||
{
|
||||
false => "hypospray-all-mode-text",
|
||||
true => "hypospray-mobs-only-mode-text",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Guidebook.Components;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using JetBrains.Annotations;
|
||||
@@ -31,8 +32,7 @@ namespace Content.Client.Chemistry.UI
|
||||
|
||||
// Setup window layout/elements
|
||||
_window = this.CreateWindow<ReagentDispenserWindow>();
|
||||
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
_window.HelpGuidebookIds = EntMan.GetComponent<GuideHelpComponent>(Owner).Guides;
|
||||
_window.SetInfoFromEntity(EntMan, Owner);
|
||||
|
||||
// Setup static button actions.
|
||||
_window.EjectButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentDispenser.OutputSlotName));
|
||||
|
||||
@@ -334,8 +334,11 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId))
|
||||
continue;
|
||||
|
||||
if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers))
|
||||
if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, out var displacementKey))
|
||||
{
|
||||
revealedLayers.Add(displacementKey);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
25
Content.Client/Configurable/ConfigurationSystem.cs
Normal file
25
Content.Client/Configurable/ConfigurationSystem.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Content.Client.Configurable.UI;
|
||||
using Content.Shared.Configurable;
|
||||
|
||||
namespace Content.Client.Configurable;
|
||||
|
||||
public sealed class ConfigurationSystem : SharedConfigurationSystem
|
||||
{
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<ConfigurationComponent, AfterAutoHandleStateEvent>(OnConfigurationState);
|
||||
}
|
||||
|
||||
private void OnConfigurationState(Entity<ConfigurationComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (_uiSystem.TryGetOpenUi<ConfigurationBoundUserInterface>(ent.Owner,
|
||||
ConfigurationComponent.ConfigurationUiKey.Key,
|
||||
out var bui))
|
||||
{
|
||||
bui.Refresh(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Client.GameObjects;
|
||||
using System.Numerics;
|
||||
using System.Text.RegularExpressions;
|
||||
using Content.Shared.Configurable;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
|
||||
namespace Content.Client.Configurable.UI
|
||||
@@ -19,16 +21,53 @@ namespace Content.Client.Configurable.UI
|
||||
base.Open();
|
||||
_menu = this.CreateWindow<ConfigurationMenu>();
|
||||
_menu.OnConfiguration += SendConfiguration;
|
||||
if (EntMan.TryGetComponent(Owner, out ConfigurationComponent? component))
|
||||
Refresh((Owner, component));
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
public void Refresh(Entity<ConfigurationComponent> entity)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not ConfigurationBoundUserInterfaceState configurationState)
|
||||
if (_menu == null)
|
||||
return;
|
||||
|
||||
_menu?.Populate(configurationState);
|
||||
_menu.Column.Children.Clear();
|
||||
_menu.Inputs.Clear();
|
||||
|
||||
foreach (var field in entity.Comp.Config)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
Name = field.Key,
|
||||
Text = field.Key + ":",
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = .2f,
|
||||
MinSize = new Vector2(60, 0)
|
||||
};
|
||||
|
||||
var input = new LineEdit
|
||||
{
|
||||
Name = field.Key + "-input",
|
||||
Text = field.Value ?? "",
|
||||
IsValid = _menu.Validate,
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = .8f
|
||||
};
|
||||
|
||||
_menu.Inputs.Add((field.Key, input));
|
||||
|
||||
var row = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal
|
||||
};
|
||||
|
||||
ConfigurationMenu.CopyProperties(_menu.Row, row);
|
||||
|
||||
row.AddChild(label);
|
||||
row.AddChild(input);
|
||||
_menu.Column.AddChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
@@ -14,10 +10,10 @@ namespace Content.Client.Configurable.UI
|
||||
{
|
||||
public sealed class ConfigurationMenu : DefaultWindow
|
||||
{
|
||||
private readonly BoxContainer _column;
|
||||
private readonly BoxContainer _row;
|
||||
public readonly BoxContainer Column;
|
||||
public readonly BoxContainer Row;
|
||||
|
||||
private readonly List<(string name, LineEdit input)> _inputs;
|
||||
public readonly List<(string name, LineEdit input)> Inputs;
|
||||
|
||||
[ViewVariables]
|
||||
public Regex? Validation { get; internal set; }
|
||||
@@ -28,7 +24,7 @@ namespace Content.Client.Configurable.UI
|
||||
{
|
||||
MinSize = SetSize = new Vector2(300, 250);
|
||||
|
||||
_inputs = new List<(string name, LineEdit input)>();
|
||||
Inputs = new List<(string name, LineEdit input)>();
|
||||
|
||||
Title = Loc.GetString("configuration-menu-device-title");
|
||||
|
||||
@@ -39,14 +35,14 @@ namespace Content.Client.Configurable.UI
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
_column = new BoxContainer
|
||||
Column = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
Margin = new Thickness(8),
|
||||
SeparationOverride = 16,
|
||||
};
|
||||
|
||||
_row = new BoxContainer
|
||||
Row = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 16,
|
||||
@@ -69,61 +65,20 @@ namespace Content.Client.Configurable.UI
|
||||
ModulateSelfOverride = Color.FromHex("#202025")
|
||||
};
|
||||
|
||||
outerColumn.AddChild(_column);
|
||||
outerColumn.AddChild(Column);
|
||||
baseContainer.AddChild(outerColumn);
|
||||
baseContainer.AddChild(confirmButton);
|
||||
Contents.AddChild(baseContainer);
|
||||
}
|
||||
|
||||
public void Populate(ConfigurationBoundUserInterfaceState state)
|
||||
{
|
||||
_column.Children.Clear();
|
||||
_inputs.Clear();
|
||||
|
||||
foreach (var field in state.Config)
|
||||
{
|
||||
var label = new Label
|
||||
{
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
Name = field.Key,
|
||||
Text = field.Key + ":",
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = .2f,
|
||||
MinSize = new Vector2(60, 0)
|
||||
};
|
||||
|
||||
var input = new LineEdit
|
||||
{
|
||||
Name = field.Key + "-input",
|
||||
Text = field.Value ?? "",
|
||||
IsValid = Validate,
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = .8f
|
||||
};
|
||||
|
||||
_inputs.Add((field.Key, input));
|
||||
|
||||
var row = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
CopyProperties(_row, row);
|
||||
|
||||
row.AddChild(label);
|
||||
row.AddChild(input);
|
||||
_column.AddChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConfirm(ButtonEventArgs args)
|
||||
{
|
||||
var config = GenerateDictionary(_inputs, "Text");
|
||||
var config = GenerateDictionary(Inputs, "Text");
|
||||
OnConfiguration?.Invoke(config);
|
||||
Close();
|
||||
}
|
||||
|
||||
private bool Validate(string value)
|
||||
public bool Validate(string value)
|
||||
{
|
||||
return Validation?.IsMatch(value) != false;
|
||||
}
|
||||
@@ -140,7 +95,7 @@ namespace Content.Client.Configurable.UI
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
private static void CopyProperties<T>(T from, T to) where T : Control
|
||||
public static void CopyProperties<T>(T from, T to) where T : Control
|
||||
{
|
||||
foreach (var property in from.AllAttachedProperties)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Construction
|
||||
{
|
||||
@@ -45,7 +47,14 @@ namespace Content.Client.Construction
|
||||
public override void StartHijack(PlacementManager manager)
|
||||
{
|
||||
base.StartHijack(manager);
|
||||
manager.CurrentTextures = _prototype?.Layers.Select(sprite => sprite.DirFrame0()).ToList();
|
||||
|
||||
if (_prototype is null || !_constructionSystem.TryGetRecipePrototype(_prototype.ID, out var targetProtoId))
|
||||
return;
|
||||
|
||||
if (!IoCManager.Resolve<IPrototypeManager>().TryIndex(targetProtoId, out EntityPrototype? proto))
|
||||
return;
|
||||
|
||||
manager.CurrentTextures = SpriteComponent.GetPrototypeTextures(proto, IoCManager.Resolve<IResourceCache>()).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Content.Client.Popups;
|
||||
using Content.Shared.Construction;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Content.Shared.Construction.Steps;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Wall;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -15,6 +14,7 @@ using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Construction
|
||||
{
|
||||
@@ -25,7 +25,6 @@ namespace Content.Client.Construction
|
||||
public sealed class ConstructionSystem : SharedConstructionSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
@@ -33,6 +32,8 @@ namespace Content.Client.Construction
|
||||
private readonly Dictionary<int, EntityUid> _ghosts = new();
|
||||
private readonly Dictionary<string, ConstructionGuide> _guideCache = new();
|
||||
|
||||
private readonly Dictionary<string, string> _recipesMetadataCache = [];
|
||||
|
||||
public bool CraftingEnabled { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -40,6 +41,8 @@ namespace Content.Client.Construction
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
WarmupRecipesCache();
|
||||
|
||||
UpdatesOutsidePrediction = true;
|
||||
SubscribeLocalEvent<LocalPlayerAttachedEvent>(HandlePlayerAttached);
|
||||
SubscribeNetworkEvent<AckStructureConstructionMessage>(HandleAckStructure);
|
||||
@@ -63,6 +66,77 @@ namespace Content.Client.Construction
|
||||
ClearGhost(component.GhostId);
|
||||
}
|
||||
|
||||
public bool TryGetRecipePrototype(string constructionProtoId, [NotNullWhen(true)] out string? targetProtoId)
|
||||
{
|
||||
if (_recipesMetadataCache.TryGetValue(constructionProtoId, out targetProtoId))
|
||||
return true;
|
||||
|
||||
targetProtoId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void WarmupRecipesCache()
|
||||
{
|
||||
foreach (var constructionProto in PrototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||
{
|
||||
if (!PrototypeManager.TryIndex(constructionProto.Graph, out var graphProto))
|
||||
continue;
|
||||
|
||||
if (constructionProto.TargetNode is not { } targetNodeId)
|
||||
continue;
|
||||
|
||||
if (!graphProto.Nodes.TryGetValue(targetNodeId, out var targetNode))
|
||||
continue;
|
||||
|
||||
// Recursion is for wimps.
|
||||
var stack = new Stack<ConstructionGraphNode>();
|
||||
stack.Push(targetNode);
|
||||
|
||||
do
|
||||
{
|
||||
var node = stack.Pop();
|
||||
|
||||
// I never realized if this uid affects anything...
|
||||
// EntityUid? userUid = args.SenderSession.State.ControlledEntity.HasValue
|
||||
// ? GetEntity(args.SenderSession.State.ControlledEntity.Value)
|
||||
// : null;
|
||||
|
||||
// We try to get the id of the target prototype, if it fails, we try going through the edges.
|
||||
if (node.Entity.GetId(null, null, new(EntityManager)) is not { } entityId)
|
||||
{
|
||||
// If the stack is not empty, there is a high probability that the loop will go to infinity.
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
foreach (var edge in node.Edges)
|
||||
{
|
||||
if (graphProto.Nodes.TryGetValue(edge.Target, out var graphNode))
|
||||
stack.Push(graphNode);
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we got the id of the prototype, we exit the “recursion” by clearing the stack.
|
||||
stack.Clear();
|
||||
|
||||
if (!PrototypeManager.TryIndex(constructionProto.ID, out ConstructionPrototype? recipe))
|
||||
continue;
|
||||
|
||||
if (!PrototypeManager.TryIndex(entityId, out var proto))
|
||||
continue;
|
||||
|
||||
var name = recipe.SetName.HasValue ? Loc.GetString(recipe.SetName) : proto.Name;
|
||||
var desc = recipe.SetDescription.HasValue ? Loc.GetString(recipe.SetDescription) : proto.Description;
|
||||
|
||||
recipe.Name = name;
|
||||
recipe.Description = desc;
|
||||
|
||||
_recipesMetadataCache.Add(constructionProto.ID, entityId);
|
||||
} while (stack.Count > 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConstructionGuideReceived(ResponseConstructionGuide ev)
|
||||
{
|
||||
_guideCache[ev.ConstructionId] = ev.Guide;
|
||||
@@ -88,7 +162,7 @@ namespace Content.Client.Construction
|
||||
|
||||
private void HandleConstructionGhostExamined(EntityUid uid, ConstructionGhostComponent component, ExaminedEvent args)
|
||||
{
|
||||
if (component.Prototype == null)
|
||||
if (component.Prototype?.Name is null)
|
||||
return;
|
||||
|
||||
using (args.PushGroup(nameof(ConstructionGhostComponent)))
|
||||
@@ -97,7 +171,7 @@ namespace Content.Client.Construction
|
||||
"construction-ghost-examine-message",
|
||||
("name", component.Prototype.Name)));
|
||||
|
||||
if (!_prototypeManager.TryIndex(component.Prototype.Graph, out ConstructionGraphPrototype? graph))
|
||||
if (!PrototypeManager.TryIndex(component.Prototype.Graph, out var graph))
|
||||
return;
|
||||
|
||||
var startNode = graph.Nodes[component.Prototype.StartNode];
|
||||
@@ -198,6 +272,9 @@ namespace Content.Client.Construction
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetRecipePrototype(prototype.ID, out var targetProtoId) || !PrototypeManager.TryIndex(targetProtoId, out EntityPrototype? targetProto))
|
||||
return false;
|
||||
|
||||
if (GhostPresent(loc))
|
||||
return false;
|
||||
|
||||
@@ -214,16 +291,43 @@ namespace Content.Client.Construction
|
||||
comp.GhostId = ghost.GetHashCode();
|
||||
EntityManager.GetComponent<TransformComponent>(ghost.Value).LocalRotation = dir.ToAngle();
|
||||
_ghosts.Add(comp.GhostId, ghost.Value);
|
||||
|
||||
var sprite = EntityManager.GetComponent<SpriteComponent>(ghost.Value);
|
||||
sprite.Color = new Color(48, 255, 48, 128);
|
||||
|
||||
for (int i = 0; i < prototype.Layers.Count; i++)
|
||||
if (targetProto.TryGetComponent(out IconComponent? icon, EntityManager.ComponentFactory))
|
||||
{
|
||||
sprite.AddBlankLayer(i); // There is no way to actually check if this already exists, so we blindly insert a new one
|
||||
sprite.LayerSetSprite(i, prototype.Layers[i]);
|
||||
sprite.LayerSetShader(i, "unshaded");
|
||||
sprite.LayerSetVisible(i, true);
|
||||
sprite.AddBlankLayer(0);
|
||||
sprite.LayerSetSprite(0, icon.Icon);
|
||||
sprite.LayerSetShader(0, "unshaded");
|
||||
sprite.LayerSetVisible(0, true);
|
||||
}
|
||||
else if (targetProto.Components.TryGetValue("Sprite", out _))
|
||||
{
|
||||
var dummy = EntityManager.SpawnEntity(targetProtoId, MapCoordinates.Nullspace);
|
||||
var targetSprite = EntityManager.EnsureComponent<SpriteComponent>(dummy);
|
||||
EntityManager.System<AppearanceSystem>().OnChangeData(dummy, targetSprite);
|
||||
|
||||
for (var i = 0; i < targetSprite.AllLayers.Count(); i++)
|
||||
{
|
||||
if (!targetSprite[i].Visible || !targetSprite[i].RsiState.IsValid)
|
||||
continue;
|
||||
|
||||
var rsi = targetSprite[i].Rsi ?? targetSprite.BaseRSI;
|
||||
if (rsi is null || !rsi.TryGetState(targetSprite[i].RsiState, out var state) ||
|
||||
state.StateId.Name is null)
|
||||
continue;
|
||||
|
||||
sprite.AddBlankLayer(i);
|
||||
sprite.LayerSetSprite(i, new SpriteSpecifier.Rsi(rsi.Path, state.StateId.Name));
|
||||
sprite.LayerSetShader(i, "unshaded");
|
||||
sprite.LayerSetVisible(i, true);
|
||||
}
|
||||
|
||||
EntityManager.DeleteEntity(dummy);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
if (prototype.CanBuildInImpassable)
|
||||
EnsureComp<WallMountComponent>(ghost.Value).Arc = new(Math.Tau);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io">
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" MinWidth="243" Margin="0 0 5 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 0 0 5">
|
||||
<LineEdit Name="SearchBar" PlaceHolder="Search" HorizontalExpand="True"/>
|
||||
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'construction-menu-search'}" HorizontalExpand="True"/>
|
||||
<OptionButton Name="OptionCategories" Access="Public" MinSize="130 0"/>
|
||||
</BoxContainer>
|
||||
<ItemList Name="Recipes" Access="Public" SelectMode="Single" VerticalExpand="True"/>
|
||||
<controls:ListContainer Name="Recipes" Access="Public" Group="True" Toggle="True" VerticalExpand="True" />
|
||||
<ScrollContainer Name="RecipesGridScrollContainer" VerticalExpand="True" Access="Public" Visible="False">
|
||||
<GridContainer Name="RecipesGrid" Columns="5" Access="Public"/>
|
||||
</ScrollContainer>
|
||||
@@ -18,7 +19,7 @@
|
||||
<Control>
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" Margin="0 0 0 5">
|
||||
<BoxContainer Orientation="Horizontal" Align="Center">
|
||||
<TextureRect Name="TargetTexture" HorizontalAlignment="Right" Stretch="Keep" Margin="0 0 10 0"/>
|
||||
<EntityPrototypeView Name="TargetTexture" HorizontalAlignment="Right" Stretch="Fill" Margin="0 0 10 0"/>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<RichTextLabel Name="TargetName"/>
|
||||
<RichTextLabel Name="TargetDesc"/>
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Construction.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Construction.UI
|
||||
{
|
||||
@@ -28,7 +25,7 @@ namespace Content.Client.Construction.UI
|
||||
bool GridViewButtonPressed { get; set; }
|
||||
bool BuildButtonPressed { get; set; }
|
||||
|
||||
ItemList Recipes { get; }
|
||||
ListContainer Recipes { get; }
|
||||
ItemList RecipeStepList { get; }
|
||||
|
||||
|
||||
@@ -36,14 +33,14 @@ namespace Content.Client.Construction.UI
|
||||
GridContainer RecipesGrid { get; }
|
||||
|
||||
event EventHandler<(string search, string catagory)> PopulateRecipes;
|
||||
event EventHandler<ItemList.Item?> RecipeSelected;
|
||||
event EventHandler<ConstructionMenu.ConstructionMenuListData?> RecipeSelected;
|
||||
event EventHandler RecipeFavorited;
|
||||
event EventHandler<bool> BuildButtonToggled;
|
||||
event EventHandler<bool> EraseButtonToggled;
|
||||
event EventHandler ClearAllGhosts;
|
||||
|
||||
void ClearRecipeInfo();
|
||||
void SetRecipeInfo(string name, string description, Texture iconTexture, bool isItem, bool isFavorite);
|
||||
void SetRecipeInfo(string name, string description, EntityPrototype? targetPrototype, bool isItem, bool isFavorite);
|
||||
void ResetPlacement();
|
||||
|
||||
#region Window Control
|
||||
@@ -94,8 +91,36 @@ namespace Content.Client.Construction.UI
|
||||
Title = Loc.GetString("construction-menu-title");
|
||||
|
||||
BuildButton.Text = Loc.GetString("construction-menu-place-ghost");
|
||||
Recipes.OnItemSelected += obj => RecipeSelected?.Invoke(this, obj.ItemList[obj.ItemIndex]);
|
||||
Recipes.OnItemDeselected += _ => RecipeSelected?.Invoke(this, null);
|
||||
Recipes.ItemPressed += (_, data) => RecipeSelected?.Invoke(this, data as ConstructionMenuListData);
|
||||
Recipes.NoItemSelected += () => RecipeSelected?.Invoke(this, null);
|
||||
Recipes.GenerateItem += (data, button) =>
|
||||
{
|
||||
if (data is not ConstructionMenuListData (var prototype, var targetPrototype))
|
||||
return;
|
||||
|
||||
var entProtoView = new EntityPrototypeView()
|
||||
{
|
||||
SetSize = new(32f),
|
||||
Stretch = SpriteView.StretchMode.Fill,
|
||||
Scale = new(2),
|
||||
Margin = new(0, 2),
|
||||
};
|
||||
entProtoView.SetPrototype(targetPrototype);
|
||||
|
||||
var label = new Label()
|
||||
{
|
||||
Text = prototype.Name,
|
||||
Margin = new(5, 0),
|
||||
};
|
||||
|
||||
var box = new BoxContainer();
|
||||
box.AddChild(entProtoView);
|
||||
box.AddChild(label);
|
||||
|
||||
button.AddChild(box);
|
||||
button.ToolTip = prototype.Description;
|
||||
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
|
||||
};
|
||||
|
||||
SearchBar.OnTextChanged += _ =>
|
||||
PopulateRecipes?.Invoke(this, (SearchBar.Text, Categories[OptionCategories.SelectedId]));
|
||||
@@ -121,7 +146,7 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
public event EventHandler? ClearAllGhosts;
|
||||
public event EventHandler<(string search, string catagory)>? PopulateRecipes;
|
||||
public event EventHandler<ItemList.Item?>? RecipeSelected;
|
||||
public event EventHandler<ConstructionMenuListData?>? RecipeSelected;
|
||||
public event EventHandler? RecipeFavorited;
|
||||
public event EventHandler<bool>? BuildButtonToggled;
|
||||
public event EventHandler<bool>? EraseButtonToggled;
|
||||
@@ -133,13 +158,17 @@ namespace Content.Client.Construction.UI
|
||||
}
|
||||
|
||||
public void SetRecipeInfo(
|
||||
string name, string description, Texture iconTexture, bool isItem, bool isFavorite)
|
||||
string name,
|
||||
string description,
|
||||
EntityPrototype? targetPrototype,
|
||||
bool isItem,
|
||||
bool isFavorite)
|
||||
{
|
||||
BuildButton.Disabled = false;
|
||||
BuildButton.Text = Loc.GetString(isItem ? "construction-menu-place-ghost" : "construction-menu-craft");
|
||||
TargetName.SetMessage(name);
|
||||
TargetDesc.SetMessage(description);
|
||||
TargetTexture.Texture = iconTexture;
|
||||
TargetTexture.SetPrototype(targetPrototype?.ID);
|
||||
FavoriteButton.Visible = true;
|
||||
FavoriteButton.Text = Loc.GetString(
|
||||
isFavorite ? "construction-add-favorite-button" : "construction-remove-from-favorite-button");
|
||||
@@ -150,9 +179,11 @@ namespace Content.Client.Construction.UI
|
||||
BuildButton.Disabled = true;
|
||||
TargetName.SetMessage(string.Empty);
|
||||
TargetDesc.SetMessage(string.Empty);
|
||||
TargetTexture.Texture = null;
|
||||
TargetTexture.SetPrototype(null);
|
||||
FavoriteButton.Visible = false;
|
||||
RecipeStepList.Clear();
|
||||
}
|
||||
|
||||
public sealed record ConstructionMenuListData(ConstructionPrototype Prototype, EntityPrototype TargetPrototype) : ListData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,8 @@ using Robust.Client.Placement;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
|
||||
namespace Content.Client.Construction.UI
|
||||
{
|
||||
@@ -30,18 +28,20 @@ namespace Content.Client.Construction.UI
|
||||
[Dependency] private readonly IPlacementManager _placementManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private readonly IConstructionMenuView _constructionView;
|
||||
private readonly EntityWhitelistSystem _whitelistSystem;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private ConstructionSystem? _constructionSystem;
|
||||
private ConstructionPrototype? _selected;
|
||||
private List<ConstructionPrototype> _favoritedRecipes = [];
|
||||
private Dictionary<string, TextureButton> _recipeButtons = new();
|
||||
private Dictionary<string, ContainerButton> _recipeButtons = new();
|
||||
private string _selectedCategory = string.Empty;
|
||||
private string _favoriteCatName = "construction-category-favorites";
|
||||
private string _forAllCategoryName = "construction-category-all";
|
||||
|
||||
private const string FavoriteCatName = "construction-category-favorites";
|
||||
private const string ForAllCategoryName = "construction-category-all";
|
||||
|
||||
private bool CraftingAvailable
|
||||
{
|
||||
get => _uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.Visible;
|
||||
@@ -98,15 +98,18 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
_placementManager.PlacementChanged += OnPlacementChanged;
|
||||
|
||||
_constructionView.OnClose += () => _uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.Pressed = false;
|
||||
_constructionView.OnClose +=
|
||||
() => _uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.Pressed = false;
|
||||
_constructionView.ClearAllGhosts += (_, _) => _constructionSystem?.ClearAllGhosts();
|
||||
_constructionView.PopulateRecipes += OnViewPopulateRecipes;
|
||||
_constructionView.RecipeSelected += OnViewRecipeSelected;
|
||||
_constructionView.BuildButtonToggled += (_, b) => BuildButtonToggled(b);
|
||||
_constructionView.EraseButtonToggled += (_, b) =>
|
||||
{
|
||||
if (_constructionSystem is null) return;
|
||||
if (b) _placementManager.Clear();
|
||||
if (_constructionSystem is null)
|
||||
return;
|
||||
if (b)
|
||||
_placementManager.Clear();
|
||||
_placementManager.ToggleEraserHijacked(new ConstructionPlacementHijack(_constructionSystem, null));
|
||||
_constructionView.EraseButtonPressed = b;
|
||||
};
|
||||
@@ -117,7 +120,7 @@ namespace Content.Client.Construction.UI
|
||||
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
|
||||
}
|
||||
|
||||
public void OnHudCraftingButtonToggled(ButtonToggledEventArgs args)
|
||||
public void OnHudCraftingButtonToggled(BaseButton.ButtonToggledEventArgs args)
|
||||
{
|
||||
WindowOpen = args.Pressed;
|
||||
}
|
||||
@@ -139,7 +142,7 @@ namespace Content.Client.Construction.UI
|
||||
_constructionView.ResetPlacement();
|
||||
}
|
||||
|
||||
private void OnViewRecipeSelected(object? sender, ItemList.Item? item)
|
||||
private void OnViewRecipeSelected(object? sender, ConstructionMenu.ConstructionMenuListData? item)
|
||||
{
|
||||
if (item is null)
|
||||
{
|
||||
@@ -148,12 +151,15 @@ namespace Content.Client.Construction.UI
|
||||
return;
|
||||
}
|
||||
|
||||
_selected = (ConstructionPrototype) item.Metadata!;
|
||||
if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
|
||||
_selected = item.Prototype;
|
||||
|
||||
if (_placementManager is { IsActive: true, Eraser: false })
|
||||
UpdateGhostPlacement();
|
||||
|
||||
PopulateInfo(_selected);
|
||||
}
|
||||
|
||||
private void OnGridViewRecipeSelected(object? sender, ConstructionPrototype? recipe)
|
||||
private void OnGridViewRecipeSelected(object? _, ConstructionPrototype? recipe)
|
||||
{
|
||||
if (recipe is null)
|
||||
{
|
||||
@@ -163,62 +169,21 @@ namespace Content.Client.Construction.UI
|
||||
}
|
||||
|
||||
_selected = recipe;
|
||||
if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
|
||||
|
||||
if (_placementManager is { IsActive: true, Eraser: false })
|
||||
UpdateGhostPlacement();
|
||||
|
||||
PopulateInfo(_selected);
|
||||
}
|
||||
|
||||
private void OnViewPopulateRecipes(object? sender, (string search, string catagory) args)
|
||||
{
|
||||
var (search, category) = args;
|
||||
if (_constructionSystem is null)
|
||||
return;
|
||||
|
||||
var recipes = new List<ConstructionPrototype>();
|
||||
|
||||
var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName;
|
||||
|
||||
if (isEmptyCategory)
|
||||
_selectedCategory = string.Empty;
|
||||
else
|
||||
_selectedCategory = category;
|
||||
|
||||
foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||
{
|
||||
if (recipe.Hide)
|
||||
continue;
|
||||
|
||||
if (_playerManager.LocalSession == null
|
||||
|| _playerManager.LocalEntity == null
|
||||
|| _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(search))
|
||||
{
|
||||
if (!recipe.Name.ToLowerInvariant().Contains(search.Trim().ToLowerInvariant()))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isEmptyCategory)
|
||||
{
|
||||
if (category == _favoriteCatName)
|
||||
{
|
||||
if (!_favoritedRecipes.Contains(recipe))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (recipe.Category != category)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
recipes.Add(recipe);
|
||||
}
|
||||
|
||||
recipes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCulture));
|
||||
var actualRecipes = GetAndSortRecipes(args);
|
||||
|
||||
var recipesList = _constructionView.Recipes;
|
||||
recipesList.Clear();
|
||||
|
||||
var recipesGrid = _constructionView.RecipesGrid;
|
||||
recipesGrid.RemoveAllChildren();
|
||||
|
||||
@@ -227,60 +192,120 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
if (_constructionView.GridViewButtonPressed)
|
||||
{
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
var itemButton = new TextureButton
|
||||
{
|
||||
TextureNormal = _spriteSystem.Frame0(recipe.Icon),
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
Name = recipe.Name,
|
||||
ToolTip = recipe.Name,
|
||||
Scale = new Vector2(1.35f),
|
||||
ToggleMode = true,
|
||||
};
|
||||
var itemButtonPanelContainer = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = StyleNano.ButtonColorDefault },
|
||||
Children = { itemButton },
|
||||
};
|
||||
|
||||
itemButton.OnToggled += buttonToggledEventArgs =>
|
||||
{
|
||||
SelectGridButton(itemButton, buttonToggledEventArgs.Pressed);
|
||||
|
||||
if (buttonToggledEventArgs.Pressed &&
|
||||
_selected != null &&
|
||||
_recipeButtons.TryGetValue(_selected.Name, out var oldButton))
|
||||
{
|
||||
oldButton.Pressed = false;
|
||||
SelectGridButton(oldButton, false);
|
||||
}
|
||||
|
||||
OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe : null);
|
||||
};
|
||||
|
||||
recipesGrid.AddChild(itemButtonPanelContainer);
|
||||
_recipeButtons[recipe.Name] = itemButton;
|
||||
var isCurrentButtonSelected = _selected == recipe;
|
||||
itemButton.Pressed = isCurrentButtonSelected;
|
||||
SelectGridButton(itemButton, isCurrentButtonSelected);
|
||||
}
|
||||
recipesList.PopulateList([]);
|
||||
PopulateGrid(recipesGrid, actualRecipes);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var recipe in recipes)
|
||||
{
|
||||
recipesList.Add(GetItem(recipe, recipesList));
|
||||
}
|
||||
recipesList.PopulateList(actualRecipes);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectGridButton(TextureButton button, bool select)
|
||||
private void PopulateGrid(GridContainer recipesGrid,
|
||||
IEnumerable<ConstructionMenu.ConstructionMenuListData> actualRecipes)
|
||||
{
|
||||
foreach (var recipe in actualRecipes)
|
||||
{
|
||||
var protoView = new EntityPrototypeView()
|
||||
{
|
||||
Scale = new Vector2(1.2f),
|
||||
};
|
||||
protoView.SetPrototype(recipe.TargetPrototype);
|
||||
|
||||
var itemButton = new ContainerButton()
|
||||
{
|
||||
VerticalAlignment = Control.VAlignment.Center,
|
||||
Name = recipe.TargetPrototype.Name,
|
||||
ToolTip = recipe.TargetPrototype.Name,
|
||||
ToggleMode = true,
|
||||
Children = { protoView },
|
||||
};
|
||||
|
||||
var itemButtonPanelContainer = new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat { BackgroundColor = StyleNano.ButtonColorDefault },
|
||||
Children = { itemButton },
|
||||
};
|
||||
|
||||
itemButton.OnToggled += buttonToggledEventArgs =>
|
||||
{
|
||||
SelectGridButton(itemButton, buttonToggledEventArgs.Pressed);
|
||||
|
||||
if (buttonToggledEventArgs.Pressed &&
|
||||
_selected != null &&
|
||||
_recipeButtons.TryGetValue(_selected.Name!, out var oldButton))
|
||||
{
|
||||
oldButton.Pressed = false;
|
||||
SelectGridButton(oldButton, false);
|
||||
}
|
||||
|
||||
OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe.Prototype : null);
|
||||
};
|
||||
|
||||
recipesGrid.AddChild(itemButtonPanelContainer);
|
||||
_recipeButtons[recipe.Prototype.Name!] = itemButton;
|
||||
var isCurrentButtonSelected = _selected == recipe.Prototype;
|
||||
itemButton.Pressed = isCurrentButtonSelected;
|
||||
SelectGridButton(itemButton, isCurrentButtonSelected);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ConstructionMenu.ConstructionMenuListData> GetAndSortRecipes((string, string) args)
|
||||
{
|
||||
var recipes = new List<ConstructionMenu.ConstructionMenuListData>();
|
||||
|
||||
var (search, category) = args;
|
||||
var isEmptyCategory = string.IsNullOrEmpty(category) || category == ForAllCategoryName;
|
||||
_selectedCategory = isEmptyCategory ? string.Empty : category;
|
||||
|
||||
foreach (var recipe in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||
{
|
||||
if (recipe.Hide)
|
||||
continue;
|
||||
|
||||
if (_playerManager.LocalSession == null
|
||||
|| _playerManager.LocalEntity == null
|
||||
|| _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
|
||||
continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(search) && (recipe.Name is { } name &&
|
||||
!name.Contains(search.Trim(),
|
||||
StringComparison.InvariantCultureIgnoreCase)))
|
||||
continue;
|
||||
|
||||
if (!isEmptyCategory)
|
||||
{
|
||||
if ((category != FavoriteCatName || !_favoritedRecipes.Contains(recipe)) &&
|
||||
recipe.Category != category)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_constructionSystem!.TryGetRecipePrototype(recipe.ID, out var targetProtoId))
|
||||
{
|
||||
Logger.Error("Cannot find the target prototype in the recipe cache with the id \"{0}\" of {1}.",
|
||||
recipe.ID,
|
||||
nameof(ConstructionPrototype));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_prototypeManager.TryIndex(targetProtoId, out EntityPrototype? proto))
|
||||
continue;
|
||||
|
||||
recipes.Add(new(recipe, proto));
|
||||
}
|
||||
|
||||
recipes.Sort(
|
||||
(a, b) => string.Compare(a.Prototype.Name, b.Prototype.Name, StringComparison.InvariantCulture));
|
||||
|
||||
return recipes;
|
||||
}
|
||||
|
||||
private void SelectGridButton(BaseButton button, bool select)
|
||||
{
|
||||
if (button.Parent is not PanelContainer buttonPanel)
|
||||
return;
|
||||
|
||||
button.Modulate = select ? Color.Green : Color.White;
|
||||
button.Modulate = select ? Color.Green : Color.Transparent;
|
||||
var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
|
||||
buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
|
||||
}
|
||||
@@ -302,12 +327,12 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
// hard-coded to show all recipes
|
||||
var idx = 0;
|
||||
categoriesArray[idx++] = _forAllCategoryName;
|
||||
categoriesArray[idx++] = ForAllCategoryName;
|
||||
|
||||
// hard-coded to show favorites if it need
|
||||
if (isFavorites)
|
||||
{
|
||||
categoriesArray[idx++] = _favoriteCatName;
|
||||
categoriesArray[idx++] = FavoriteCatName;
|
||||
}
|
||||
|
||||
var sortedProtoCategories = uniqueCategories.OrderBy(Loc.GetString);
|
||||
@@ -325,18 +350,31 @@ namespace Content.Client.Construction.UI
|
||||
|
||||
if (!string.IsNullOrEmpty(selectCategory) && selectCategory == categoriesArray[i])
|
||||
_constructionView.OptionCategories.SelectId(i);
|
||||
|
||||
}
|
||||
|
||||
_constructionView.Categories = categoriesArray;
|
||||
}
|
||||
|
||||
private void PopulateInfo(ConstructionPrototype prototype)
|
||||
private void PopulateInfo(ConstructionPrototype? prototype)
|
||||
{
|
||||
if (_constructionSystem is null)
|
||||
return;
|
||||
|
||||
_constructionView.ClearRecipeInfo();
|
||||
|
||||
if (prototype is null)
|
||||
return;
|
||||
|
||||
if (!_constructionSystem.TryGetRecipePrototype(prototype.ID, out var targetProtoId))
|
||||
return;
|
||||
|
||||
if (!_prototypeManager.TryIndex(targetProtoId, out EntityPrototype? proto))
|
||||
return;
|
||||
|
||||
_constructionView.SetRecipeInfo(
|
||||
prototype.Name, prototype.Description, _spriteSystem.Frame0(prototype.Icon),
|
||||
prototype.Name!,
|
||||
prototype.Description!,
|
||||
proto,
|
||||
prototype.Type != ConstructionType.Item,
|
||||
!_favoritedRecipes.Contains(prototype));
|
||||
|
||||
@@ -349,16 +387,17 @@ namespace Content.Client.Construction.UI
|
||||
if (_constructionSystem?.GetGuide(prototype) is not { } guide)
|
||||
return;
|
||||
|
||||
|
||||
foreach (var entry in guide.Entries)
|
||||
{
|
||||
var text = entry.Arguments != null
|
||||
? Loc.GetString(entry.Localization, entry.Arguments) : Loc.GetString(entry.Localization);
|
||||
? Loc.GetString(entry.Localization, entry.Arguments)
|
||||
: Loc.GetString(entry.Localization);
|
||||
|
||||
if (entry.EntryNumber is { } number)
|
||||
{
|
||||
text = Loc.GetString("construction-presenter-step-wrapper",
|
||||
("step-number", number), ("text", text));
|
||||
("step-number", number),
|
||||
("text", text));
|
||||
}
|
||||
|
||||
// The padding needs to be applied regardless of text length... (See PadLeft documentation)
|
||||
@@ -369,23 +408,12 @@ namespace Content.Client.Construction.UI
|
||||
}
|
||||
}
|
||||
|
||||
private ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
|
||||
{
|
||||
return new(itemList)
|
||||
{
|
||||
Metadata = recipe,
|
||||
Text = recipe.Name,
|
||||
Icon = _spriteSystem.Frame0(recipe.Icon),
|
||||
TooltipEnabled = true,
|
||||
TooltipText = recipe.Description,
|
||||
};
|
||||
}
|
||||
|
||||
private void BuildButtonToggled(bool pressed)
|
||||
{
|
||||
if (pressed)
|
||||
{
|
||||
if (_selected == null) return;
|
||||
if (_selected == null)
|
||||
return;
|
||||
|
||||
// not bound to a construction system
|
||||
if (_constructionSystem is null)
|
||||
@@ -402,10 +430,11 @@ namespace Content.Client.Construction.UI
|
||||
}
|
||||
|
||||
_placementManager.BeginPlacing(new PlacementInformation
|
||||
{
|
||||
IsTile = false,
|
||||
PlacementOption = _selected.PlacementMode
|
||||
}, new ConstructionPlacementHijack(_constructionSystem, _selected));
|
||||
{
|
||||
IsTile = false,
|
||||
PlacementOption = _selected.PlacementMode
|
||||
},
|
||||
new ConstructionPlacementHijack(_constructionSystem, _selected));
|
||||
|
||||
UpdateGhostPlacement();
|
||||
}
|
||||
@@ -429,38 +458,39 @@ namespace Content.Client.Construction.UI
|
||||
var constructSystem = _systemManager.GetEntitySystem<ConstructionSystem>();
|
||||
|
||||
_placementManager.BeginPlacing(new PlacementInformation()
|
||||
{
|
||||
IsTile = false,
|
||||
PlacementOption = _selected.PlacementMode,
|
||||
}, new ConstructionPlacementHijack(constructSystem, _selected));
|
||||
{
|
||||
IsTile = false,
|
||||
PlacementOption = _selected.PlacementMode,
|
||||
},
|
||||
new ConstructionPlacementHijack(constructSystem, _selected));
|
||||
|
||||
_constructionView.BuildButtonPressed = true;
|
||||
}
|
||||
|
||||
private void OnSystemLoaded(object? sender, SystemChangedArgs args)
|
||||
{
|
||||
if (args.System is ConstructionSystem system) SystemBindingChanged(system);
|
||||
if (args.System is ConstructionSystem system)
|
||||
SystemBindingChanged(system);
|
||||
}
|
||||
|
||||
private void OnSystemUnloaded(object? sender, SystemChangedArgs args)
|
||||
{
|
||||
if (args.System is ConstructionSystem) SystemBindingChanged(null);
|
||||
if (args.System is ConstructionSystem)
|
||||
SystemBindingChanged(null);
|
||||
}
|
||||
|
||||
private void OnViewFavoriteRecipe()
|
||||
{
|
||||
if (_selected is not ConstructionPrototype recipe)
|
||||
if (_selected is null)
|
||||
return;
|
||||
|
||||
if (!_favoritedRecipes.Remove(_selected))
|
||||
_favoritedRecipes.Add(_selected);
|
||||
|
||||
if (_selectedCategory == _favoriteCatName)
|
||||
if (_selectedCategory == FavoriteCatName)
|
||||
{
|
||||
if (_favoritedRecipes.Count > 0)
|
||||
OnViewPopulateRecipes(_constructionView, (string.Empty, _favoriteCatName));
|
||||
else
|
||||
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
|
||||
OnViewPopulateRecipes(_constructionView,
|
||||
_favoritedRecipes.Count > 0 ? (string.Empty, FavoriteCatName) : (string.Empty, string.Empty));
|
||||
}
|
||||
|
||||
PopulateInfo(_selected);
|
||||
@@ -492,6 +522,9 @@ namespace Content.Client.Construction.UI
|
||||
private void BindToSystem(ConstructionSystem system)
|
||||
{
|
||||
_constructionSystem = system;
|
||||
|
||||
OnViewPopulateRecipes(_constructionView, (string.Empty, string.Empty));
|
||||
|
||||
system.ToggleCraftingWindow += SystemOnToggleMenu;
|
||||
system.FlipConstructionPrototype += SystemFlipConstructionPrototype;
|
||||
system.CraftingAvailabilityChanged += SystemCraftingAvailabilityChanged;
|
||||
@@ -533,7 +566,8 @@ namespace Content.Client.Construction.UI
|
||||
if (IsAtFront)
|
||||
{
|
||||
WindowOpen = false;
|
||||
_uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.SetClickPressed(false); // This does not call CraftingButtonToggled
|
||||
_uiManager.GetActiveUIWidget<GameTopMenuBar>()
|
||||
.CraftingButton.SetClickPressed(false); // This does not call CraftingButtonToggled
|
||||
}
|
||||
else
|
||||
_constructionView.MoveToFront();
|
||||
@@ -541,7 +575,8 @@ namespace Content.Client.Construction.UI
|
||||
else
|
||||
{
|
||||
WindowOpen = true;
|
||||
_uiManager.GetActiveUIWidget<GameTopMenuBar>().CraftingButton.SetClickPressed(true); // This does not call CraftingButtonToggled
|
||||
_uiManager.GetActiveUIWidget<GameTopMenuBar>()
|
||||
.CraftingButton.SetClickPressed(true); // This does not call CraftingButtonToggled
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'credits-window-title'}"
|
||||
SetSize="650 650" >
|
||||
<TabContainer>
|
||||
Title="{Loc 'credits-window-title'}"
|
||||
SetSize="650 650">
|
||||
<TabContainer Name="MasterTabContainer">
|
||||
<ScrollContainer Name="Ss14ContributorsTab"
|
||||
HScrollEnabled="False">
|
||||
<BoxContainer Name="Ss14ContributorsContainer"
|
||||
@@ -26,5 +26,11 @@
|
||||
<!-- Licenses get added here by code -->
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer Name="AttributionsTab"
|
||||
HScrollEnabled="False">
|
||||
<BoxContainer Name="AttributionsContainer"
|
||||
Orientation="Vertical"
|
||||
Margin="2 2 0 0" />
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -1,185 +1,382 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Credits;
|
||||
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.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
namespace Content.Client.Credits
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CreditsWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
namespace Content.Client.Credits;
|
||||
|
||||
private static readonly Dictionary<string, int> PatronTierPriority = new()
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CreditsWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
|
||||
private static readonly Dictionary<string, int> PatronTierPriority = new()
|
||||
{
|
||||
["Nuclear Operative"] = 1,
|
||||
["Syndicate Agent"] = 2,
|
||||
["Revolutionary"] = 3,
|
||||
};
|
||||
|
||||
private readonly List<FormattedMessage> _attributions = [];
|
||||
private readonly ISawmill _sawmill = Logger.GetSawmill("Credits");
|
||||
|
||||
private const int AttributionsSourcesPerPage = 50;
|
||||
|
||||
public CreditsWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
TabContainer.SetTabTitle(Ss14ContributorsTab, Loc.GetString("credits-window-ss14contributorslist-tab"));
|
||||
TabContainer.SetTabTitle(PatronsTab, Loc.GetString("credits-window-patrons-tab"));
|
||||
TabContainer.SetTabTitle(LicensesTab, Loc.GetString("credits-window-licenses-tab"));
|
||||
TabContainer.SetTabTitle(AttributionsTab, Loc.GetString("credits-window-attributions-tab"));
|
||||
|
||||
_protoManager.PrototypesReloaded += _ =>
|
||||
{
|
||||
["Nuclear Operative"] = 1,
|
||||
["Syndicate Agent"] = 2,
|
||||
["Revolutionary"] = 3
|
||||
_attributions.Clear();
|
||||
};
|
||||
|
||||
public CreditsWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
MasterTabContainer.OnTabChanged += OnTabChanged;
|
||||
|
||||
TabContainer.SetTabTitle(Ss14ContributorsTab, Loc.GetString("credits-window-ss14contributorslist-tab"));
|
||||
TabContainer.SetTabTitle(PatronsTab, Loc.GetString("credits-window-patrons-tab"));
|
||||
TabContainer.SetTabTitle(LicensesTab, Loc.GetString("credits-window-licenses-tab"));
|
||||
PopulateContributors(Ss14ContributorsContainer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Only populates the tab when they are selected, which reduces lagspike when not looking at attributions.
|
||||
/// </summary>
|
||||
private void OnTabChanged(int tab)
|
||||
{
|
||||
if (tab == Ss14ContributorsTab.GetPositionInParent())
|
||||
PopulateContributors(Ss14ContributorsContainer);
|
||||
else if (tab == PatronsTab.GetPositionInParent())
|
||||
PopulatePatrons(PatronsContainer);
|
||||
else if (tab == LicensesTab.GetPositionInParent())
|
||||
PopulateLicenses(LicensesContainer);
|
||||
else if (tab == AttributionsTab.GetPositionInParent())
|
||||
PopulateAttributions(AttributionsContainer, 0);
|
||||
}
|
||||
|
||||
private async void PopulateAttributions(BoxContainer attributionsContainer, int count)
|
||||
{
|
||||
attributionsContainer.DisposeAllChildren();
|
||||
|
||||
if (_attributions.Count == 0)
|
||||
{
|
||||
var rsi = await CollectRSiAttributions();
|
||||
var rga = await CollectRgaAttributions();
|
||||
|
||||
_attributions.AddRange(rsi);
|
||||
_attributions.AddRange(rga);
|
||||
}
|
||||
|
||||
private void PopulateLicenses(BoxContainer licensesContainer)
|
||||
foreach (var message in _attributions.Skip(count).Take(AttributionsSourcesPerPage))
|
||||
{
|
||||
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
|
||||
{
|
||||
licensesContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = entry.Name});
|
||||
var rich = new RichTextLabel();
|
||||
rich.SetMessage(message);
|
||||
attributionsContainer.AddChild(rich);
|
||||
}
|
||||
|
||||
// We split these line by line because otherwise
|
||||
// the LGPL causes Clyde to go out of bounds in the rendering code.
|
||||
foreach (var line in entry.License.Split("\n"))
|
||||
var container = new BoxContainer { Orientation = LayoutOrientation.Horizontal };
|
||||
|
||||
var previousPageButton = new Button { Text = "Previous Page" };
|
||||
previousPageButton.OnPressed +=
|
||||
_ => PopulateAttributions(attributionsContainer, count - AttributionsSourcesPerPage);
|
||||
|
||||
var nextPageButton = new Button { Text = "Next Page" };
|
||||
nextPageButton.OnPressed +=
|
||||
_ => PopulateAttributions(attributionsContainer, count + AttributionsSourcesPerPage);
|
||||
|
||||
if (count - AttributionsSourcesPerPage >= 0)
|
||||
container.AddChild(previousPageButton);
|
||||
if (count + AttributionsSourcesPerPage < _attributions.Count)
|
||||
container.AddChild(nextPageButton);
|
||||
|
||||
attributionsContainer.AddChild(container);
|
||||
}
|
||||
|
||||
private Task<List<FormattedMessage>> CollectRSiAttributions()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var rsiStreams = _resourceManager.ContentFindFiles("/Textures/")
|
||||
.Where(p => p.ToString().EndsWith(".rsi/meta.json"));
|
||||
|
||||
var attrs = new List<FormattedMessage>();
|
||||
|
||||
foreach (var stream in rsiStreams)
|
||||
{
|
||||
try
|
||||
{
|
||||
licensesContainer.AddChild(new Label {Text = line, FontColorOverride = new Color(200, 200, 200)});
|
||||
var m = new FormattedMessage();
|
||||
|
||||
var yamlStream = _resourceManager.ContentFileReadYaml(stream);
|
||||
|
||||
if (yamlStream.Documents[0].RootNode.ToDataNode() is not MappingDataNode map)
|
||||
throw new Exception("meta.json is not a mapping.");
|
||||
|
||||
if (!map.TryGet("copyright", out var copyrightNode))
|
||||
throw new Exception("Missing the copyright field.");
|
||||
|
||||
if (!map.TryGet("states", out var statesNode))
|
||||
throw new Exception("Missing the states field.");
|
||||
|
||||
if (statesNode is not SequenceDataNode states)
|
||||
throw new Exception("Missing a list of states.");
|
||||
|
||||
var copyright = copyrightNode.ToString();
|
||||
var files = states.Select(n => (MappingDataNode)n)
|
||||
.Select(n => n.Get("name") + ".png");
|
||||
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-directory",
|
||||
("directory", stream.Directory.ToString())));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-files",
|
||||
("files", string.Join(", ", files))));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-copyright",
|
||||
("copyright", copyright)));
|
||||
m.AddText("\n");
|
||||
|
||||
attrs.Add(m);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var m = new FormattedMessage();
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-failed",
|
||||
("file", stream.ToString())));
|
||||
m.AddText("\n");
|
||||
_sawmill.Error($"{stream.ToString()}\n{e}");
|
||||
attrs.Add(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulatePatrons(BoxContainer patronsContainer)
|
||||
return attrs;
|
||||
});
|
||||
}
|
||||
|
||||
private Task<List<FormattedMessage>> CollectRgaAttributions()
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var patrons = LoadPatrons();
|
||||
var rgaStreams = _resourceManager.ContentFindFiles("/")
|
||||
.Where(p => p.Filename == "attributions.yml");
|
||||
|
||||
// Do not show "become a patron" button on Steam builds
|
||||
// since Patreon violates Valve's rules about alternative storefronts.
|
||||
var linkPatreon = _cfg.GetCVar(CCVars.InfoLinksPatreon);
|
||||
if (!_cfg.GetCVar(CCVars.BrandingSteam) && linkPatreon != "")
|
||||
var attrs = new List<FormattedMessage>();
|
||||
|
||||
foreach (var stream in rgaStreams)
|
||||
{
|
||||
Button patronButton;
|
||||
patronsContainer.AddChild(patronButton = new Button
|
||||
try
|
||||
{
|
||||
Text = Loc.GetString("credits-window-become-patron-button"),
|
||||
HorizontalAlignment = HAlignment.Center
|
||||
});
|
||||
var yamlStream = _resourceManager.ContentFileReadYaml(stream);
|
||||
|
||||
patronButton.OnPressed +=
|
||||
_ => IoCManager.Resolve<IUriOpener>().OpenUri(linkPatreon);
|
||||
if (yamlStream.Documents[0].RootNode.ToDataNode() is not SequenceDataNode sequence)
|
||||
throw new Exception("Attributions file is not a list of attributions.");
|
||||
|
||||
foreach (var attribution in sequence.Sequence)
|
||||
{
|
||||
var m = new FormattedMessage();
|
||||
|
||||
if (attribution is not MappingDataNode map)
|
||||
throw new Exception("Attribution is not a mapping.");
|
||||
|
||||
if (!map.TryGet("files", out var filesNode))
|
||||
throw new Exception("Attribution does not list files.");
|
||||
|
||||
if (!map.TryGet("copyright", out var copyrightNode))
|
||||
throw new Exception("Attribution does not copyright.");
|
||||
|
||||
if (!map.TryGet("license", out var licenseNode))
|
||||
throw new Exception("Attribution does not identify a license.");
|
||||
|
||||
if (!map.TryGet("source", out var sourceNode))
|
||||
throw new Exception("Attribution does not identify a source.");
|
||||
|
||||
var files = _serialization.Read<string[]>(filesNode, notNullableOverride: true);
|
||||
var copyright = copyrightNode.ToString();
|
||||
var license = licenseNode.ToString();
|
||||
var source = sourceNode.ToString();
|
||||
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-directory",
|
||||
("directory", stream.Directory.ToString())));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-files",
|
||||
("files", string.Join(", ", files))));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-copyright",
|
||||
("copyright", copyright)));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(
|
||||
_loc.GetString("credits-window-attributions-license", ("license", license)));
|
||||
m.AddText("\n");
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-source", ("source", source)));
|
||||
m.AddText("\n");
|
||||
|
||||
attrs.Add(m);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var m = new FormattedMessage();
|
||||
m.AddMarkupPermissive(_loc.GetString("credits-window-attributions-failed",
|
||||
("file", stream.ToString())));
|
||||
m.AddText("\n");
|
||||
_sawmill.Error($"{stream.ToString()}\n{e}");
|
||||
attrs.Add(m);
|
||||
}
|
||||
}
|
||||
|
||||
var first = true;
|
||||
foreach (var tier in patrons.GroupBy(p => p.Tier).OrderBy(p => PatronTierPriority[p.Key]))
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
patronsContainer.AddChild(new Control {MinSize = new Vector2(0, 10)});
|
||||
}
|
||||
return attrs;
|
||||
});
|
||||
}
|
||||
|
||||
first = false;
|
||||
patronsContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = $"{tier.Key}"});
|
||||
|
||||
var msg = string.Join(", ", tier.OrderBy(p => p.Name).Select(p => p.Name));
|
||||
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(msg);
|
||||
|
||||
patronsContainer.AddChild(label);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PatronEntry> LoadPatrons()
|
||||
private void PopulateLicenses(BoxContainer licensesContainer)
|
||||
{
|
||||
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
|
||||
{
|
||||
var yamlStream = _resourceManager.ContentFileReadYaml(new ("/Credits/Patrons.yml"));
|
||||
var sequence = (YamlSequenceNode) yamlStream.Documents[0].RootNode;
|
||||
licensesContainer.AddChild(new Label
|
||||
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = entry.Name });
|
||||
|
||||
return sequence
|
||||
.Cast<YamlMappingNode>()
|
||||
.Select(m => new PatronEntry(m["Name"].AsString(), m["Tier"].AsString()));
|
||||
}
|
||||
|
||||
private void PopulateContributors(BoxContainer ss14ContributorsContainer)
|
||||
{
|
||||
Button contributeButton;
|
||||
|
||||
ss14ContributorsContainer.AddChild(new BoxContainer
|
||||
// We split these line by line because otherwise
|
||||
// the LGPL causes Clyde to go out of bounds in the rendering code.
|
||||
foreach (var line in entry.License.Split("\n"))
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
SeparationOverride = 20,
|
||||
Children =
|
||||
{
|
||||
new Label {Text = Loc.GetString("credits-window-contributor-encouragement-label") },
|
||||
(contributeButton = new Button {Text = Loc.GetString("credits-window-contribute-button")})
|
||||
}
|
||||
});
|
||||
|
||||
var first = true;
|
||||
|
||||
void AddSection(string title, string path, bool markup = false)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
ss14ContributorsContainer.AddChild(new Control {MinSize = new Vector2(0, 10)});
|
||||
}
|
||||
|
||||
first = false;
|
||||
ss14ContributorsContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = title});
|
||||
|
||||
var label = new RichTextLabel();
|
||||
var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
|
||||
if (markup)
|
||||
{
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
|
||||
}
|
||||
else
|
||||
{
|
||||
label.SetMessage(text);
|
||||
}
|
||||
|
||||
ss14ContributorsContainer.AddChild(label);
|
||||
}
|
||||
|
||||
AddSection(Loc.GetString("credits-window-contributors-section-title"), "GitHub.txt");
|
||||
AddSection(Loc.GetString("credits-window-codebases-section-title"), "SpaceStation13.txt");
|
||||
AddSection(Loc.GetString("credits-window-tts-title"), "TTS.txt"); // Corvax-TTS
|
||||
AddSection(Loc.GetString("credits-window-original-remake-team-section-title"), "OriginalRemake.txt");
|
||||
AddSection(Loc.GetString("credits-window-special-thanks-section-title"), "SpecialThanks.txt", true);
|
||||
|
||||
var linkGithub = _cfg.GetCVar(CCVars.InfoLinksGithub);
|
||||
|
||||
contributeButton.OnPressed += _ =>
|
||||
IoCManager.Resolve<IUriOpener>().OpenUri(linkGithub);
|
||||
|
||||
if (linkGithub == "")
|
||||
contributeButton.Visible = false;
|
||||
}
|
||||
|
||||
private sealed class PatronEntry
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Tier { get; }
|
||||
|
||||
public PatronEntry(string name, string tier)
|
||||
{
|
||||
Name = name;
|
||||
Tier = tier;
|
||||
licensesContainer.AddChild(new Label { Text = line, FontColorOverride = new Color(200, 200, 200) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopulatePatrons(BoxContainer patronsContainer)
|
||||
{
|
||||
var patrons = LoadPatrons();
|
||||
|
||||
// Do not show "become a patron" button on Steam builds
|
||||
// since Patreon violates Valve's rules about alternative storefronts.
|
||||
var linkPatreon = _cfg.GetCVar(CCVars.InfoLinksPatreon);
|
||||
if (!_cfg.GetCVar(CCVars.BrandingSteam) && linkPatreon != "")
|
||||
{
|
||||
Button patronButton;
|
||||
patronsContainer.AddChild(patronButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("credits-window-become-patron-button"),
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
});
|
||||
|
||||
patronButton.OnPressed +=
|
||||
_ => IoCManager.Resolve<IUriOpener>().OpenUri(linkPatreon);
|
||||
}
|
||||
|
||||
var first = true;
|
||||
foreach (var tier in patrons.GroupBy(p => p.Tier).OrderBy(p => PatronTierPriority[p.Key]))
|
||||
{
|
||||
if (!first)
|
||||
patronsContainer.AddChild(new Control { MinSize = new Vector2(0, 10) });
|
||||
|
||||
first = false;
|
||||
patronsContainer.AddChild(new Label
|
||||
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = $"{tier.Key}" });
|
||||
|
||||
var msg = string.Join(", ", tier.OrderBy(p => p.Name).Select(p => p.Name));
|
||||
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(msg);
|
||||
|
||||
patronsContainer.AddChild(label);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PatronEntry> LoadPatrons()
|
||||
{
|
||||
var yamlStream = _resourceManager.ContentFileReadYaml(new ResPath("/Credits/Patrons.yml"));
|
||||
var sequence = (YamlSequenceNode)yamlStream.Documents[0].RootNode;
|
||||
|
||||
return sequence
|
||||
.Cast<YamlMappingNode>()
|
||||
.Select(m => new PatronEntry(m["Name"].AsString(), m["Tier"].AsString()));
|
||||
}
|
||||
|
||||
private void PopulateContributors(BoxContainer ss14ContributorsContainer)
|
||||
{
|
||||
Button contributeButton;
|
||||
|
||||
ss14ContributorsContainer.AddChild(new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
SeparationOverride = 20,
|
||||
Children =
|
||||
{
|
||||
new Label { Text = Loc.GetString("credits-window-contributor-encouragement-label") },
|
||||
(contributeButton = new Button { Text = Loc.GetString("credits-window-contribute-button") }),
|
||||
},
|
||||
});
|
||||
|
||||
var first = true;
|
||||
|
||||
void AddSection(string title, string path, bool markup = false)
|
||||
{
|
||||
if (!first)
|
||||
ss14ContributorsContainer.AddChild(new Control { MinSize = new Vector2(0, 10) });
|
||||
|
||||
first = false;
|
||||
ss14ContributorsContainer.AddChild(new Label
|
||||
{ StyleClasses = { StyleBase.StyleClassLabelHeading }, Text = title });
|
||||
|
||||
var label = new RichTextLabel();
|
||||
var text = _resourceManager.ContentFileReadAllText($"/Credits/{path}");
|
||||
if (markup)
|
||||
label.SetMessage(FormattedMessage.FromMarkupOrThrow(text.Trim()));
|
||||
else
|
||||
label.SetMessage(text);
|
||||
|
||||
ss14ContributorsContainer.AddChild(label);
|
||||
}
|
||||
|
||||
AddSection(Loc.GetString("credits-window-tts-title"), "TTS.txt"); // Corvax-TTS
|
||||
AddSection(Loc.GetString("credits-window-contributors-section-title"), "GitHub.txt");
|
||||
AddSection(Loc.GetString("credits-window-codebases-section-title"), "SpaceStation13.txt");
|
||||
AddSection(Loc.GetString("credits-window-original-remake-team-section-title"), "OriginalRemake.txt");
|
||||
AddSection(Loc.GetString("credits-window-special-thanks-section-title"), "SpecialThanks.txt", true);
|
||||
|
||||
var linkGithub = _cfg.GetCVar(CCVars.InfoLinksGithub);
|
||||
|
||||
contributeButton.OnPressed += _ =>
|
||||
IoCManager.Resolve<IUriOpener>().OpenUri(linkGithub);
|
||||
|
||||
if (linkGithub == "")
|
||||
contributeButton.Visible = false;
|
||||
}
|
||||
|
||||
private sealed class PatronEntry
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Tier { get; }
|
||||
|
||||
public PatronEntry(string name, string tier)
|
||||
{
|
||||
Name = name;
|
||||
Tier = tier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,52 +213,14 @@ public sealed partial class CriminalRecordsConsoleWindow : FancyWindow
|
||||
return;
|
||||
}
|
||||
|
||||
var entries = listing.ToList();
|
||||
entries.Sort((a, b) => string.Compare(a.Value, b.Value, StringComparison.Ordinal));
|
||||
// `entries` now contains the definitive list of items which should be in
|
||||
// our list of records and is in the order we want to present those items.
|
||||
|
||||
// Walk through the existing items in RecordListing and in the updated listing
|
||||
// in parallel to synchronize the items in RecordListing with `entries`.
|
||||
int i = RecordListing.Count - 1;
|
||||
int j = entries.Count - 1;
|
||||
while (i >= 0 && j >= 0)
|
||||
{
|
||||
var strcmp = string.Compare(RecordListing[i].Text, entries[j].Value, StringComparison.Ordinal);
|
||||
if (strcmp == 0)
|
||||
{
|
||||
// This item exists in both RecordListing and `entries`. Nothing to do.
|
||||
i--;
|
||||
j--;
|
||||
}
|
||||
else if (strcmp > 0)
|
||||
{
|
||||
// Item exists in RecordListing, but not in `entries`. Remove it.
|
||||
RecordListing.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
else if (strcmp < 0)
|
||||
{
|
||||
// A new entry which doesn't exist in RecordListing. Create it.
|
||||
RecordListing.Insert(i + 1, new ItemList.Item(RecordListing){Text = entries[j].Value, Metadata = entries[j].Key});
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
// Any remaining items in RecordListing don't exist in `entries`, so remove them
|
||||
while (i >= 0)
|
||||
{
|
||||
RecordListing.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
// And finally, any remaining items in `entries`, don't exist in RecordListing. Create them.
|
||||
while (j >= 0)
|
||||
{
|
||||
RecordListing.Insert(0, new ItemList.Item(RecordListing){ Text = entries[j].Value, Metadata = entries[j].Key });
|
||||
j--;
|
||||
}
|
||||
var entries = listing.Select(i => new ItemList.Item(RecordListing) {
|
||||
Text = i.Value,
|
||||
Metadata = i.Key
|
||||
}).ToList();
|
||||
entries.Sort((a, b) => string.Compare(a.Text, b.Text, StringComparison.Ordinal));
|
||||
RecordListing.SetItems(entries, (a,b) => string.Compare(a.Text, b.Text));
|
||||
}
|
||||
|
||||
private void PopulateRecordContainer(GeneralStationRecord stationRecord, CriminalRecord criminalRecord)
|
||||
{
|
||||
var specifier = new SpriteSpecifier.Rsi(new ResPath("Interface/Misc/job_icons.rsi"), "Unknown");
|
||||
|
||||
7
Content.Client/Damage/Systems/StaminaSystem.cs
Normal file
7
Content.Client/Damage/Systems/StaminaSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.Damage.Systems;
|
||||
|
||||
namespace Content.Client.Damage.Systems;
|
||||
|
||||
public sealed partial class StaminaSystem : SharedStaminaSystem
|
||||
{
|
||||
}
|
||||
@@ -13,7 +13,7 @@ namespace Content.Client.Decals
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprites = default!;
|
||||
|
||||
private DecalOverlay _overlay = default!;
|
||||
private DecalOverlay? _overlay;
|
||||
|
||||
private HashSet<uint> _removedUids = new();
|
||||
private readonly List<Vector2i> _removedChunks = new();
|
||||
@@ -31,6 +31,9 @@ namespace Content.Client.Decals
|
||||
|
||||
public void ToggleOverlay()
|
||||
{
|
||||
if (_overlay == null)
|
||||
return;
|
||||
|
||||
if (_overlayManager.HasOverlay<DecalOverlay>())
|
||||
{
|
||||
_overlayManager.RemoveOverlay(_overlay);
|
||||
@@ -44,6 +47,10 @@ namespace Content.Client.Decals
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
if (_overlay == null)
|
||||
return;
|
||||
|
||||
_overlayManager.RemoveOverlay(_overlay);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,3 +43,9 @@ public enum DeliveryVisualLayers : byte
|
||||
Breakage,
|
||||
Trash,
|
||||
}
|
||||
|
||||
public enum DeliverySpawnerVisualLayers : byte
|
||||
{
|
||||
Contents,
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.DeviceNetwork.Systems;
|
||||
|
||||
namespace Content.Client.DeviceNetwork.Systems;
|
||||
|
||||
public sealed class DeviceNetworkSystem : SharedDeviceNetworkSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -9,22 +9,36 @@ public sealed class DisplacementMapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISerializationManager _serialization = default!;
|
||||
|
||||
public bool TryAddDisplacement(DisplacementData data, SpriteComponent sprite, int index, string key, HashSet<string> revealedLayers)
|
||||
/// <summary>
|
||||
/// Attempting to apply a displacement map to a specific layer of SpriteComponent
|
||||
/// </summary>
|
||||
/// <param name="data">Information package for applying the displacement map</param>
|
||||
/// <param name="sprite">SpriteComponent</param>
|
||||
/// <param name="index">Index of the layer where the new map layer will be added</param>
|
||||
/// <param name="key">Unique layer key, which will determine which layer to apply displacement map to</param>
|
||||
/// <param name="displacementKey">The key of the new displacement map layer added by this function.</param>
|
||||
/// <returns></returns>
|
||||
public bool TryAddDisplacement(DisplacementData data,
|
||||
SpriteComponent sprite,
|
||||
int index,
|
||||
object key,
|
||||
out string displacementKey)
|
||||
{
|
||||
displacementKey = $"{key}-displacement";
|
||||
|
||||
if (key.ToString() is null)
|
||||
return false;
|
||||
|
||||
if (data.ShaderOverride != null)
|
||||
sprite.LayerSetShader(index, data.ShaderOverride);
|
||||
|
||||
var displacementKey = $"{key}-displacement";
|
||||
if (!revealedLayers.Add(displacementKey))
|
||||
{
|
||||
Log.Warning($"Duplicate key for DISPLACEMENT: {displacementKey}.");
|
||||
return false;
|
||||
}
|
||||
if (sprite.LayerMapTryGet(displacementKey, out var oldIndex))
|
||||
sprite.RemoveLayer(oldIndex);
|
||||
|
||||
//allows you not to write it every time in the YML
|
||||
foreach (var pair in data.SizeMaps)
|
||||
{
|
||||
pair.Value.CopyToShaderParameters??= new()
|
||||
pair.Value.CopyToShaderParameters ??= new()
|
||||
{
|
||||
LayerKey = "dummy",
|
||||
ParameterTexture = "displacementMap",
|
||||
@@ -45,21 +59,22 @@ public sealed class DisplacementMapSystem : EntitySystem
|
||||
if (actualRSI is not null)
|
||||
{
|
||||
if (actualRSI.Size.X != actualRSI.Size.Y)
|
||||
Log.Warning($"DISPLACEMENT: {displacementKey} has a resolution that is not 1:1, things can look crooked");
|
||||
{
|
||||
Log.Warning(
|
||||
$"DISPLACEMENT: {displacementKey} has a resolution that is not 1:1, things can look crooked");
|
||||
}
|
||||
|
||||
var layerSize = actualRSI.Size.X;
|
||||
if (data.SizeMaps.ContainsKey(layerSize))
|
||||
displacementDataLayer = data.SizeMaps[layerSize];
|
||||
if (data.SizeMaps.TryGetValue(layerSize, out var map))
|
||||
displacementDataLayer = map;
|
||||
}
|
||||
|
||||
var displacementLayer = _serialization.CreateCopy(displacementDataLayer, notNullableOverride: true);
|
||||
displacementLayer.CopyToShaderParameters!.LayerKey = key;
|
||||
displacementLayer.CopyToShaderParameters!.LayerKey = key.ToString() ?? "this is impossible";
|
||||
|
||||
sprite.AddLayer(displacementLayer, index);
|
||||
sprite.LayerMapSet(displacementKey, index);
|
||||
|
||||
revealedLayers.Add(displacementKey);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
|
||||
namespace Content.Client.Disposal;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed partial class DisposalUnitComponent : SharedDisposalUnitComponent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using Content.Client.Disposal.Unit;
|
||||
using Content.Client.Power.EntitySystems;
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.Disposal.Mailing;
|
||||
|
||||
public sealed class MailingUnitBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
public MailingUnitWindow? MailingUnitWindow;
|
||||
|
||||
public MailingUnitBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private void ButtonPressed(DisposalUnitComponent.UiButton button)
|
||||
{
|
||||
SendMessage(new DisposalUnitComponent.UiButtonPressedMessage(button));
|
||||
// If we get client-side power stuff then we can predict the button presses but for now we won't as it stuffs
|
||||
// the pressure lerp up.
|
||||
}
|
||||
|
||||
private void TargetSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
var item = args.ItemList[args.ItemIndex];
|
||||
SendMessage(new TargetSelectedMessage(item.Text));
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
MailingUnitWindow = this.CreateWindow<MailingUnitWindow>();
|
||||
MailingUnitWindow.OpenCenteredRight();
|
||||
|
||||
MailingUnitWindow.Eject.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Eject);
|
||||
MailingUnitWindow.Engage.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Engage);
|
||||
MailingUnitWindow.Power.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Power);
|
||||
|
||||
MailingUnitWindow.TargetListContainer.OnItemSelected += TargetSelected;
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out MailingUnitComponent? component))
|
||||
Refresh((Owner, component));
|
||||
}
|
||||
|
||||
public void Refresh(Entity<MailingUnitComponent> entity)
|
||||
{
|
||||
if (MailingUnitWindow == null)
|
||||
return;
|
||||
|
||||
// TODO: This should be decoupled from disposals
|
||||
if (EntMan.TryGetComponent(entity.Owner, out DisposalUnitComponent? disposals))
|
||||
{
|
||||
var disposalSystem = EntMan.System<DisposalUnitSystem>();
|
||||
|
||||
var disposalState = disposalSystem.GetState(Owner, disposals);
|
||||
var fullPressure = disposalSystem.EstimatedFullPressure(Owner, disposals);
|
||||
|
||||
MailingUnitWindow.UnitState.Text = Loc.GetString($"disposal-unit-state-{disposalState}");
|
||||
MailingUnitWindow.FullPressure = fullPressure;
|
||||
MailingUnitWindow.PressureBar.UpdatePressure(fullPressure);
|
||||
MailingUnitWindow.Power.Pressed = EntMan.System<PowerReceiverSystem>().IsPowered(Owner);
|
||||
MailingUnitWindow.Engage.Pressed = disposals.Engaged;
|
||||
}
|
||||
|
||||
MailingUnitWindow.Title = Loc.GetString("ui-mailing-unit-window-title", ("tag", entity.Comp.Tag ?? " "));
|
||||
//UnitTag.Text = state.Tag;
|
||||
MailingUnitWindow.Target.Text = entity.Comp.Target;
|
||||
|
||||
var entries = entity.Comp.TargetList.Select(target => new ItemList.Item(MailingUnitWindow.TargetListContainer) {
|
||||
Text = target,
|
||||
Selected = target == entity.Comp.Target
|
||||
}).ToList();
|
||||
MailingUnitWindow.TargetListContainer.SetItems(entries);
|
||||
}
|
||||
}
|
||||
22
Content.Client/Disposal/Mailing/MailingUnitSystem.cs
Normal file
22
Content.Client/Disposal/Mailing/MailingUnitSystem.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.Disposal.Mailing;
|
||||
|
||||
namespace Content.Client.Disposal.Mailing;
|
||||
|
||||
public sealed class MailingUnitSystem : SharedMailingUnitSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<MailingUnitComponent, AfterAutoHandleStateEvent>(OnMailingState);
|
||||
}
|
||||
|
||||
private void OnMailingState(Entity<MailingUnitComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (UserInterfaceSystem.TryGetOpenUi<MailingUnitBoundUserInterface>(ent.Owner, MailingUnitUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Refresh(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Disposal.UI"
|
||||
MinSize="300 400"
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:disposal="clr-namespace:Content.Client.Disposal"
|
||||
MinSize="300 400"
|
||||
SetSize="300 400"
|
||||
Resizable="False">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="8">
|
||||
<Label Text="{Loc 'ui-mailing-unit-target-label'}" />
|
||||
<Label Name="Target"
|
||||
Access="Public"
|
||||
Text="" />
|
||||
</BoxContainer>
|
||||
<ItemList Name="TargetListContainer"
|
||||
@@ -18,14 +20,15 @@
|
||||
</ItemList>
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="4">
|
||||
<Label Text="{Loc 'ui-disposal-unit-label-state'}" />
|
||||
<Label Name="UnitState"
|
||||
<Label Name="UnitState" Access="Public"
|
||||
Text="{Loc 'ui-disposal-unit-label-status'}" />
|
||||
</BoxContainer>
|
||||
<Control MinSize="0 5" />
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
SeparationOverride="4">
|
||||
<Label Text="{Loc 'ui-disposal-unit-label-pressure'}" />
|
||||
<ui:PressureBar Name="PressureBar"
|
||||
<disposal:PressureBar Name="PressureBar"
|
||||
Access="Public"
|
||||
MinSize="190 20"
|
||||
HorizontalAlignment="Right"
|
||||
MinValue="0"
|
||||
@@ -50,4 +53,4 @@
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
27
Content.Client/Disposal/Mailing/MailingUnitWindow.xaml.cs
Normal file
27
Content.Client/Disposal/Mailing/MailingUnitWindow.xaml.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Disposal.Mailing
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="Shared.Disposal.Components.MailingUnitComponent"/>
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MailingUnitWindow : FancyWindow
|
||||
{
|
||||
public TimeSpan FullPressure;
|
||||
|
||||
public MailingUnitWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
PressureBar.UpdatePressure(FullPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Unit;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Disposal.UI;
|
||||
namespace Content.Client.Disposal;
|
||||
|
||||
public sealed class PressureBar : ProgressBar
|
||||
{
|
||||
@@ -1,187 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.DragDrop;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
|
||||
|
||||
namespace Content.Client.Disposal.Systems;
|
||||
|
||||
public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
|
||||
private const string AnimationKey = "disposal_unit_animation";
|
||||
|
||||
private const string DefaultFlushState = "disposal-flush";
|
||||
private const string DefaultChargeState = "disposal-charging";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, PreventCollideEvent>(OnPreventCollide);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, CanDropTargetEvent>(OnCanDragDropOn);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, GotEmaggedEvent>(OnEmagged);
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<DisposalUnitComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, DisposalUnitComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not DisposalUnitComponentState state)
|
||||
return;
|
||||
|
||||
component.FlushSound = state.FlushSound;
|
||||
component.State = state.State;
|
||||
component.NextPressurized = state.NextPressurized;
|
||||
component.AutomaticEngageTime = state.AutomaticEngageTime;
|
||||
component.NextFlush = state.NextFlush;
|
||||
component.Powered = state.Powered;
|
||||
component.Engaged = state.Engaged;
|
||||
component.RecentlyEjected.Clear();
|
||||
component.RecentlyEjected.AddRange(EnsureEntityList<DisposalUnitComponent>(state.RecentlyEjected, uid));
|
||||
}
|
||||
|
||||
public override bool HasDisposals(EntityUid? uid)
|
||||
{
|
||||
return HasComp<DisposalUnitComponent>(uid);
|
||||
}
|
||||
|
||||
public override bool ResolveDisposals(EntityUid uid, [NotNullWhen(true)] ref SharedDisposalUnitComponent? component)
|
||||
{
|
||||
if (component != null)
|
||||
return true;
|
||||
|
||||
TryComp<DisposalUnitComponent>(uid, out var storage);
|
||||
component = storage;
|
||||
return component != null;
|
||||
}
|
||||
|
||||
public override void DoInsertDisposalUnit(EntityUid uid, EntityUid toInsert, EntityUid user, SharedDisposalUnitComponent? disposal = null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, SharedDisposalUnitComponent sharedDisposalUnit, ComponentInit args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(uid, out var sprite) || !TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
return;
|
||||
|
||||
UpdateState(uid, sharedDisposalUnit, sprite, appearance);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, SharedDisposalUnitComponent unit, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
UpdateState(uid, unit, args.Sprite, args.Component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update visuals and tick animation
|
||||
/// </summary>
|
||||
private void UpdateState(EntityUid uid, SharedDisposalUnitComponent unit, SpriteComponent sprite, AppearanceComponent appearance)
|
||||
{
|
||||
if (!_appearanceSystem.TryGetData<VisualState>(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.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.OverlayFlushing)
|
||||
{
|
||||
if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
|
||||
{
|
||||
var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.OverlayFlush, out var flushLayer)
|
||||
? sprite.LayerGetState(flushLayer)
|
||||
: new RSI.StateId(DefaultFlushState);
|
||||
|
||||
// Setup the flush animation to play
|
||||
var anim = new Animation
|
||||
{
|
||||
Length = unit.FlushDelay,
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = DisposalUnitVisualLayers.OverlayFlush,
|
||||
KeyFrames =
|
||||
{
|
||||
// Play the flush animation
|
||||
new AnimationTrackSpriteFlick.KeyFrame(flushState, 0),
|
||||
// Return to base state (though, depending on how the unit is
|
||||
// configured we might get an appearance change event telling
|
||||
// us to go to charging state)
|
||||
new AnimationTrackSpriteFlick.KeyFrame(chargingState, (float) unit.FlushDelay.TotalSeconds)
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (unit.FlushSound != null)
|
||||
{
|
||||
anim.AnimationTracks.Add(
|
||||
new AnimationTrackPlaySound
|
||||
{
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(unit.FlushSound), 0)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_animationSystem.Play(uid, anim, AnimationKey);
|
||||
}
|
||||
}
|
||||
else if (state == VisualState.OverlayCharging)
|
||||
sprite.LayerSetState(DisposalUnitVisualLayers.OverlayFlush, chargingState);
|
||||
else
|
||||
_animationSystem.Stop(uid, AnimationKey);
|
||||
|
||||
if (!_appearanceSystem.TryGetData<HandleState>(uid, Visuals.Handle, out var handleState, appearance))
|
||||
handleState = HandleState.Normal;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal);
|
||||
|
||||
if (!_appearanceSystem.TryGetData<LightStates>(uid, Visuals.Light, out var lightState, appearance))
|
||||
lightState = LightStates.Off;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayCharging,
|
||||
(lightState & LightStates.Charging) != 0);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayReady,
|
||||
(lightState & LightStates.Ready) != 0);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFull,
|
||||
(lightState & LightStates.Full) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DisposalUnitVisualLayers : byte
|
||||
{
|
||||
Unanchored,
|
||||
Base,
|
||||
BaseCharging,
|
||||
OverlayFlush,
|
||||
OverlayCharging,
|
||||
OverlayReady,
|
||||
OverlayFull,
|
||||
OverlayEngaged
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalRouterComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
namespace Content.Client.Disposal.Tube
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="DisposalRouterWindow"/> and updates it when new server messages are received.
|
||||
@@ -1,11 +1,10 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalRouterComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
namespace Content.Client.Disposal.Tube
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="SharedDisposalRouterComponent"/>
|
||||
@@ -1,9 +1,8 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalTaggerComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
namespace Content.Client.Disposal.Tube
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="DisposalTaggerWindow"/> and updates it when new server messages are received.
|
||||
@@ -1,11 +1,10 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalTaggerComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
namespace Content.Client.Disposal.Tube
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="SharedDisposalTaggerComponent"/>
|
||||
8
Content.Client/Disposal/Tube/DisposalTubeSystem.cs
Normal file
8
Content.Client/Disposal/Tube/DisposalTubeSystem.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using Content.Shared.Disposal.Unit;
|
||||
|
||||
namespace Content.Client.Disposal.Tube;
|
||||
|
||||
public sealed class DisposalTubeSystem : SharedDisposalTubeSystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
using Content.Client.Disposal.Systems;
|
||||
using Content.Shared.Disposal;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="MailingUnitWindow"/> or a <see cref="DisposalUnitWindow"/> and updates it when new server messages are received.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class DisposalUnitBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
// What are you doing here
|
||||
[ViewVariables]
|
||||
public MailingUnitWindow? MailingUnitWindow;
|
||||
|
||||
[ViewVariables]
|
||||
public DisposalUnitWindow? DisposalUnitWindow;
|
||||
|
||||
public DisposalUnitBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private void ButtonPressed(UiButton button)
|
||||
{
|
||||
SendMessage(new UiButtonPressedMessage(button));
|
||||
// If we get client-side power stuff then we can predict the button presses but for now we won't as it stuffs
|
||||
// the pressure lerp up.
|
||||
}
|
||||
|
||||
private void TargetSelected(ItemList.ItemListSelectedEventArgs args)
|
||||
{
|
||||
var item = args.ItemList[args.ItemIndex];
|
||||
SendMessage(new TargetSelectedMessage(item.Text));
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
if (UiKey is MailingUnitUiKey)
|
||||
{
|
||||
MailingUnitWindow = new MailingUnitWindow();
|
||||
|
||||
MailingUnitWindow.OpenCenteredRight();
|
||||
MailingUnitWindow.OnClose += Close;
|
||||
|
||||
MailingUnitWindow.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
|
||||
MailingUnitWindow.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
|
||||
MailingUnitWindow.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
|
||||
|
||||
MailingUnitWindow.TargetListContainer.OnItemSelected += TargetSelected;
|
||||
}
|
||||
else if (UiKey is DisposalUnitUiKey)
|
||||
{
|
||||
DisposalUnitWindow = new DisposalUnitWindow();
|
||||
|
||||
DisposalUnitWindow.OpenCenteredRight();
|
||||
DisposalUnitWindow.OnClose += Close;
|
||||
|
||||
DisposalUnitWindow.Eject.OnPressed += _ => ButtonPressed(UiButton.Eject);
|
||||
DisposalUnitWindow.Engage.OnPressed += _ => ButtonPressed(UiButton.Engage);
|
||||
DisposalUnitWindow.Power.OnPressed += _ => ButtonPressed(UiButton.Power);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not MailingUnitBoundUserInterfaceState && state is not DisposalUnitBoundUserInterfaceState)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case MailingUnitBoundUserInterfaceState mailingUnitState:
|
||||
MailingUnitWindow?.UpdateState(mailingUnitState);
|
||||
break;
|
||||
|
||||
case DisposalUnitBoundUserInterfaceState disposalUnitState:
|
||||
DisposalUnitWindow?.UpdateState(disposalUnitState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (!disposing)
|
||||
return;
|
||||
|
||||
MailingUnitWindow?.Dispose();
|
||||
DisposalUnitWindow?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="SharedDisposalUnitComponent"/>
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DisposalUnitWindow : DefaultWindow
|
||||
{
|
||||
public TimeSpan FullPressure;
|
||||
|
||||
public DisposalUnitWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the interface state for the disposals window.
|
||||
/// </summary>
|
||||
/// <returns>true if we should stop updating every frame.</returns>
|
||||
public void UpdateState(DisposalUnitBoundUserInterfaceState state)
|
||||
{
|
||||
Title = state.UnitName;
|
||||
UnitState.Text = state.UnitState;
|
||||
Power.Pressed = state.Powered;
|
||||
Engage.Pressed = state.Engaged;
|
||||
FullPressure = state.FullPressureTime;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
PressureBar.UpdatePressure(FullPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using Content.Shared.Disposal;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Disposal.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="MailingUnitComponent"/>
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MailingUnitWindow : DefaultWindow
|
||||
{
|
||||
public TimeSpan FullPressure;
|
||||
|
||||
public MailingUnitWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the interface state for the disposals window.
|
||||
/// </summary>
|
||||
/// <returns>true if we should stop updating every frame.</returns>
|
||||
public bool UpdateState(MailingUnitBoundUserInterfaceState state)
|
||||
{
|
||||
var disposalState = state.DisposalState;
|
||||
|
||||
Title = Loc.GetString("ui-mailing-unit-window-title", ("tag", state.Tag ?? " "));
|
||||
UnitState.Text = disposalState.UnitState;
|
||||
FullPressure = disposalState.FullPressureTime;
|
||||
var pressureReached = PressureBar.UpdatePressure(disposalState.FullPressureTime);
|
||||
Power.Pressed = disposalState.Powered;
|
||||
Engage.Pressed = disposalState.Engaged;
|
||||
|
||||
//UnitTag.Text = state.Tag;
|
||||
Target.Text = state.Target;
|
||||
|
||||
TargetListContainer.Clear();
|
||||
foreach (var target in state.TargetList)
|
||||
{
|
||||
TargetListContainer.AddItem(target);
|
||||
}
|
||||
|
||||
return !disposalState.Powered || pressureReached;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
PressureBar.UpdatePressure(FullPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Content.Client.Disposal.Mailing;
|
||||
using Content.Client.Power.EntitySystems;
|
||||
using Content.Shared.Disposal.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Disposal.Unit
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a <see cref="MailingUnitWindow"/> or a <see cref="_disposalUnitWindow"/> and updates it when new server messages are received.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public sealed class DisposalUnitBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
[ViewVariables] private DisposalUnitWindow? _disposalUnitWindow;
|
||||
|
||||
public DisposalUnitBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private void ButtonPressed(DisposalUnitComponent.UiButton button)
|
||||
{
|
||||
SendPredictedMessage(new DisposalUnitComponent.UiButtonPressedMessage(button));
|
||||
// If we get client-side power stuff then we can predict the button presses but for now we won't as it stuffs
|
||||
// the pressure lerp up.
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_disposalUnitWindow = this.CreateWindow<DisposalUnitWindow>();
|
||||
|
||||
_disposalUnitWindow.OpenCenteredRight();
|
||||
|
||||
_disposalUnitWindow.Eject.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Eject);
|
||||
_disposalUnitWindow.Engage.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Engage);
|
||||
_disposalUnitWindow.Power.OnPressed += _ => ButtonPressed(DisposalUnitComponent.UiButton.Power);
|
||||
|
||||
if (EntMan.TryGetComponent(Owner, out DisposalUnitComponent? component))
|
||||
{
|
||||
Refresh((Owner, component));
|
||||
}
|
||||
}
|
||||
|
||||
public void Refresh(Entity<DisposalUnitComponent> entity)
|
||||
{
|
||||
if (_disposalUnitWindow == null)
|
||||
return;
|
||||
|
||||
var disposalSystem = EntMan.System<DisposalUnitSystem>();
|
||||
|
||||
_disposalUnitWindow.Title = EntMan.GetComponent<MetaDataComponent>(entity.Owner).EntityName;
|
||||
|
||||
var state = disposalSystem.GetState(entity.Owner, entity.Comp);
|
||||
|
||||
_disposalUnitWindow.UnitState.Text = Loc.GetString($"disposal-unit-state-{state}");
|
||||
_disposalUnitWindow.Power.Pressed = EntMan.System<PowerReceiverSystem>().IsPowered(Owner);
|
||||
_disposalUnitWindow.Engage.Pressed = entity.Comp.Engaged;
|
||||
_disposalUnitWindow.FullPressure = disposalSystem.EstimatedFullPressure(entity.Owner, entity.Comp);
|
||||
}
|
||||
}
|
||||
}
|
||||
151
Content.Client/Disposal/Unit/DisposalUnitSystem.cs
Normal file
151
Content.Client/Disposal/Unit/DisposalUnitSystem.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Content.Shared.Disposal.Components;
|
||||
using Content.Shared.Disposal.Unit;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Client.Disposal.Unit;
|
||||
|
||||
public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
private const string AnimationKey = "disposal_unit_animation";
|
||||
|
||||
private const string DefaultFlushState = "disposal-flush";
|
||||
private const string DefaultChargeState = "disposal-charging";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, AfterAutoHandleStateEvent>(OnHandleState);
|
||||
|
||||
SubscribeLocalEvent<DisposalUnitComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, DisposalUnitComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
UpdateUI((uid, component));
|
||||
}
|
||||
|
||||
protected override void UpdateUI(Entity<DisposalUnitComponent> entity)
|
||||
{
|
||||
if (_uiSystem.TryGetOpenUi<DisposalUnitBoundUserInterface>(entity.Owner, DisposalUnitComponent.DisposalUnitUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Refresh(entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDisposalInit(Entity<DisposalUnitComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
base.OnDisposalInit(ent, ref args);
|
||||
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite) || !TryComp<AppearanceComponent>(ent, out var appearance))
|
||||
return;
|
||||
|
||||
UpdateState(ent, sprite, appearance);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(Entity<DisposalUnitComponent> ent, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
UpdateState(ent, args.Sprite, args.Component);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update visuals and tick animation
|
||||
/// </summary>
|
||||
private void UpdateState(Entity<DisposalUnitComponent> ent, SpriteComponent sprite, AppearanceComponent appearance)
|
||||
{
|
||||
if (!_appearanceSystem.TryGetData<DisposalUnitComponent.VisualState>(ent, DisposalUnitComponent.Visuals.VisualState, out var state, appearance))
|
||||
return;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.Unanchored, state == DisposalUnitComponent.VisualState.UnAnchored);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.Base, state == DisposalUnitComponent.VisualState.Anchored);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFlush, state == DisposalUnitComponent.VisualState.OverlayFlushing);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.BaseCharging, state == DisposalUnitComponent.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 == DisposalUnitComponent.VisualState.OverlayFlushing)
|
||||
{
|
||||
if (!_animationSystem.HasRunningAnimation(ent, AnimationKey))
|
||||
{
|
||||
var flushState = sprite.LayerMapTryGet(DisposalUnitVisualLayers.OverlayFlush, out var flushLayer)
|
||||
? sprite.LayerGetState(flushLayer)
|
||||
: new RSI.StateId(DefaultFlushState);
|
||||
|
||||
// Setup the flush animation to play
|
||||
var anim = new Animation
|
||||
{
|
||||
Length = ent.Comp.FlushDelay,
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = DisposalUnitVisualLayers.OverlayFlush,
|
||||
KeyFrames =
|
||||
{
|
||||
// Play the flush animation
|
||||
new AnimationTrackSpriteFlick.KeyFrame(flushState, 0),
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (ent.Comp.FlushSound != null)
|
||||
{
|
||||
anim.AnimationTracks.Add(
|
||||
new AnimationTrackPlaySound
|
||||
{
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackPlaySound.KeyFrame(_audioSystem.ResolveSound(ent.Comp.FlushSound), 0)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_animationSystem.Play(ent, anim, AnimationKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
_animationSystem.Stop(ent.Owner, AnimationKey);
|
||||
|
||||
if (!_appearanceSystem.TryGetData<DisposalUnitComponent.HandleState>(ent, DisposalUnitComponent.Visuals.Handle, out var handleState, appearance))
|
||||
handleState = DisposalUnitComponent.HandleState.Normal;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != DisposalUnitComponent.HandleState.Normal);
|
||||
|
||||
if (!_appearanceSystem.TryGetData<DisposalUnitComponent.LightStates>(ent, DisposalUnitComponent.Visuals.Light, out var lightState, appearance))
|
||||
lightState = DisposalUnitComponent.LightStates.Off;
|
||||
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayCharging,
|
||||
(lightState & DisposalUnitComponent.LightStates.Charging) != 0);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayReady,
|
||||
(lightState & DisposalUnitComponent.LightStates.Ready) != 0);
|
||||
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayFull,
|
||||
(lightState & DisposalUnitComponent.LightStates.Full) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DisposalUnitVisualLayers : byte
|
||||
{
|
||||
Unanchored,
|
||||
Base,
|
||||
BaseCharging,
|
||||
OverlayFlush,
|
||||
OverlayCharging,
|
||||
OverlayReady,
|
||||
OverlayFull,
|
||||
OverlayEngaged
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Disposal.UI"
|
||||
MinSize="300 140"
|
||||
SetSize="300 140"
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
xmlns:disposal="clr-namespace:Content.Client.Disposal"
|
||||
MinSize="300 140"
|
||||
SetSize="300 160"
|
||||
Resizable="False">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Vertical" Margin="10">
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
SeparationOverride="4">
|
||||
<Label Text="{Loc 'ui-disposal-unit-label-state'}" />
|
||||
<Label Name="UnitState"
|
||||
<Label Name="UnitState" Access="Public"
|
||||
Text="{Loc 'ui-disposal-unit-label-status'}" />
|
||||
</BoxContainer>
|
||||
<Control MinSize="0 5" />
|
||||
<BoxContainer Orientation="Horizontal"
|
||||
SeparationOverride="4">
|
||||
<Label Text="{Loc 'ui-disposal-unit-label-pressure'}" />
|
||||
<ui:PressureBar Name="PressureBar"
|
||||
<disposal:PressureBar Name="PressureBar"
|
||||
MinSize="190 20"
|
||||
HorizontalAlignment="Right"
|
||||
MinValue="0"
|
||||
@@ -23,7 +24,7 @@
|
||||
Value="0.5" />
|
||||
</BoxContainer>
|
||||
<Control MinSize="0 10" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Button Name="Engage"
|
||||
Access="Public"
|
||||
Text="{Loc 'ui-disposal-unit-button-flush'}"
|
||||
@@ -39,4 +40,4 @@
|
||||
StyleClasses="OpenLeft" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
28
Content.Client/Disposal/Unit/DisposalUnitWindow.xaml.cs
Normal file
28
Content.Client/Disposal/Unit/DisposalUnitWindow.xaml.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Disposal.Unit
|
||||
{
|
||||
/// <summary>
|
||||
/// Client-side UI used to control a <see cref="Shared.Disposal.Components.DisposalUnitComponent"/>
|
||||
/// </summary>
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class DisposalUnitWindow : FancyWindow
|
||||
{
|
||||
public TimeSpan FullPressure;
|
||||
|
||||
public DisposalUnitWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
PressureBar.UpdatePressure(FullPressure);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Doors;
|
||||
@@ -14,6 +15,30 @@ public sealed class FirelockSystem : SharedFirelockSystem
|
||||
SubscribeLocalEvent<FirelockComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
protected override void OnComponentStartup(Entity<FirelockComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
base.OnComponentStartup(ent, ref args);
|
||||
if(!TryComp<DoorComponent>(ent.Owner, out var door))
|
||||
return;
|
||||
|
||||
door.ClosedSpriteStates.Add((DoorVisualLayers.BaseUnlit, ent.Comp.WarningLightSpriteState));
|
||||
door.OpenSpriteStates.Add((DoorVisualLayers.BaseUnlit, ent.Comp.WarningLightSpriteState));
|
||||
|
||||
((Animation)door.OpeningAnimation).AnimationTracks.Add(new AnimationTrackSpriteFlick()
|
||||
{
|
||||
LayerKey = DoorVisualLayers.BaseUnlit,
|
||||
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.OpeningLightSpriteState, 0f) },
|
||||
}
|
||||
);
|
||||
|
||||
((Animation)door.ClosingAnimation).AnimationTracks.Add(new AnimationTrackSpriteFlick()
|
||||
{
|
||||
LayerKey = DoorVisualLayers.BaseUnlit,
|
||||
KeyFrames = { new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.ClosingLightSpriteState, 0f) },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, FirelockComponent comp, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
|
||||
75
Content.Client/Doors/TurnstileSystem.cs
Normal file
75
Content.Client/Doors/TurnstileSystem.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Doors.Systems;
|
||||
using Content.Shared.Examine;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Doors;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public sealed class TurnstileSystem : SharedTurnstileSystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
|
||||
|
||||
private static EntProtoId _examineArrow = "TurnstileArrow";
|
||||
|
||||
private const string AnimationKey = "Turnstile";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<TurnstileComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<TurnstileComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(Entity<TurnstileComponent> ent, ref AnimationCompletedEvent args)
|
||||
{
|
||||
if (args.Key != AnimationKey)
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
sprite.LayerSetState(TurnstileVisualLayers.Base, new RSI.StateId(ent.Comp.DefaultState));
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<TurnstileComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
Spawn(_examineArrow, new EntityCoordinates(ent, 0, 0));
|
||||
}
|
||||
|
||||
protected override void PlayAnimation(EntityUid uid, string stateId)
|
||||
{
|
||||
if (!TryComp<AnimationPlayerComponent>(uid, out var animation) || !TryComp<SpriteComponent>(uid, out var sprite))
|
||||
return;
|
||||
var ent = (uid, animation);
|
||||
|
||||
if (_animationPlayer.HasRunningAnimation(animation, AnimationKey))
|
||||
_animationPlayer.Stop(ent, AnimationKey);
|
||||
|
||||
if (sprite.BaseRSI == null || !sprite.BaseRSI.TryGetState(stateId, out var state))
|
||||
return;
|
||||
var animLength = state.AnimationLength;
|
||||
|
||||
var anim = new Animation
|
||||
{
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackSpriteFlick
|
||||
{
|
||||
LayerKey = TurnstileVisualLayers.Base,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackSpriteFlick.KeyFrame(state.StateId, 0f),
|
||||
},
|
||||
},
|
||||
},
|
||||
Length = TimeSpan.FromSeconds(animLength),
|
||||
};
|
||||
|
||||
_animationPlayer.Play(ent, anim, AnimationKey);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ using Content.Client.Replay;
|
||||
using Content.Client.Screenshot;
|
||||
using Content.Client.Singularity;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Ame.Components;
|
||||
@@ -74,6 +75,7 @@ namespace Content.Client.Entry
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
|
||||
[Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
@@ -227,6 +229,15 @@ namespace Content.Client.Entry
|
||||
{
|
||||
_debugMonitorManager.FrameUpdate();
|
||||
}
|
||||
|
||||
if (level == ModUpdateLevel.PreEngine)
|
||||
{
|
||||
if (_baseClient.RunLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame)
|
||||
{
|
||||
var updateSystem = _entitySystemManager.GetEntitySystem<BuiPreTickUpdateSystem>();
|
||||
updateSystem.RunUpdates();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,11 @@ namespace Content.Client.Examine
|
||||
[Dependency] private readonly VerbSystem _verbSystem = default!;
|
||||
[Dependency] private readonly SpriteSystem _sprite = default!;
|
||||
|
||||
private List<Verb> _verbList = new();
|
||||
|
||||
public const string StyleClassEntityTooltip = "entity-tooltip";
|
||||
|
||||
private EntityUid _examinedEntity;
|
||||
private EntityUid _lastExaminedEntity;
|
||||
private Popup? _examineTooltipOpen;
|
||||
private ScreenCoordinates _popupPos;
|
||||
private CancellationTokenSource? _requestCancelTokenSource;
|
||||
@@ -158,13 +159,13 @@ namespace Content.Client.Examine
|
||||
var entity = GetEntity(ev.EntityUid);
|
||||
|
||||
OpenTooltip(player.Value, entity, ev.CenterAtCursor, ev.OpenAtOldTooltip, ev.KnowTarget);
|
||||
UpdateTooltipInfo(player.Value, entity, ev.Message, ev.Verbs);
|
||||
UpdateTooltipInfo(player.Value, entity, ev.Message, ev.Verbs, getVerbs: false);
|
||||
}
|
||||
|
||||
public override void SendExamineTooltip(EntityUid player, EntityUid target, FormattedMessage message, bool getVerbs, bool centerAtCursor)
|
||||
{
|
||||
OpenTooltip(player, target, centerAtCursor, false);
|
||||
UpdateTooltipInfo(player, target, message);
|
||||
OpenTooltip(player, target, centerAtCursor);
|
||||
UpdateTooltipInfo(player, target, message, getVerbs: getVerbs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -259,7 +260,7 @@ namespace Content.Client.Examine
|
||||
/// <summary>
|
||||
/// Fills the examine tooltip with a message and buttons if applicable.
|
||||
/// </summary>
|
||||
public void UpdateTooltipInfo(EntityUid player, EntityUid target, FormattedMessage message, List<Verb>? verbs=null)
|
||||
public void UpdateTooltipInfo(EntityUid player, EntityUid target, FormattedMessage message, List<Verb>? verbs=null, bool getVerbs = true)
|
||||
{
|
||||
var vBox = _examineTooltipOpen?.GetChild(0).GetChild(0);
|
||||
if (vBox == null)
|
||||
@@ -283,9 +284,29 @@ namespace Content.Client.Examine
|
||||
break;
|
||||
}
|
||||
|
||||
verbs ??= new List<Verb>();
|
||||
var totalVerbs = _verbSystem.GetLocalVerbs(target, player, typeof(ExamineVerb));
|
||||
totalVerbs.UnionWith(verbs);
|
||||
|
||||
// We still need client-exclusive verbs even when the server sends its data in so if that's the case
|
||||
// we remove any non-client-exclusive verbs.
|
||||
if (!getVerbs)
|
||||
{
|
||||
_verbList.AddRange(totalVerbs);
|
||||
|
||||
foreach (var verb in _verbList)
|
||||
{
|
||||
if (!verb.ClientExclusive)
|
||||
{
|
||||
totalVerbs.Remove(verb);
|
||||
}
|
||||
}
|
||||
|
||||
_verbList.Clear();
|
||||
}
|
||||
|
||||
if (verbs != null)
|
||||
{
|
||||
totalVerbs.UnionWith(verbs);
|
||||
}
|
||||
|
||||
AddVerbsToTooltip(totalVerbs);
|
||||
}
|
||||
@@ -394,15 +415,14 @@ namespace Content.Client.Examine
|
||||
if (!IsClientSide(entity))
|
||||
{
|
||||
// Ask server for extra examine info.
|
||||
if (entity != _lastExaminedEntity)
|
||||
unchecked
|
||||
{
|
||||
_idCounter += 1;
|
||||
if (_idCounter == int.MaxValue)
|
||||
_idCounter = 0;
|
||||
}
|
||||
RaiseNetworkEvent(new ExamineSystemMessages.RequestExamineInfoMessage(GetNetEntity(entity), _idCounter, true));
|
||||
}
|
||||
|
||||
RaiseLocalEvent(entity, new ClientExaminedEvent(entity, playerEnt.Value));
|
||||
_lastExaminedEntity = entity;
|
||||
}
|
||||
|
||||
private void CloseTooltip()
|
||||
|
||||
@@ -93,7 +93,7 @@ public sealed partial class TriggerSystem
|
||||
break;
|
||||
case ProximityTriggerVisuals.Active:
|
||||
if (_player.HasRunningAnimation(uid, player, AnimKey)) return;
|
||||
_player.Play(uid, player, _flasherAnimation, AnimKey);
|
||||
_player.Play((uid, player), _flasherAnimation, AnimKey);
|
||||
break;
|
||||
case ProximityTriggerVisuals.Off:
|
||||
default:
|
||||
|
||||
@@ -34,7 +34,7 @@ public sealed class EyeLerpingSystem : EntitySystem
|
||||
SubscribeLocalEvent<LerpingEyeComponent, LocalPlayerDetachedEvent>(OnDetached);
|
||||
|
||||
UpdatesAfter.Add(typeof(TransformSystem));
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
UpdatesAfter.Add(typeof(Robust.Client.Physics.PhysicsSystem));
|
||||
UpdatesBefore.Add(typeof(SharedEyeSystem));
|
||||
UpdatesOutsidePrediction = true;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user