Compare commits

..

1 Commits

Author SHA1 Message Date
Ed
c8ce6d0141 weather is broken 2024-04-05 23:03:10 +03:00
7644 changed files with 116474 additions and 482113 deletions

View File

@@ -1,5 +1,4 @@
root = true
[*]
charset = utf-8
@@ -13,7 +12,6 @@ tab_width = 4
#end_of_line = crlf
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 120
#### .NET Coding Conventions ####
@@ -279,7 +277,7 @@ dotnet_naming_style.t_upper_camel_case_style.capitalization = pascal_case
dotnet_naming_style.t_upper_camel_case_style.required_prefix = T
dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.constants_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.constants_symbols.applicable_kinds = field
dotnet_naming_symbols.constants_symbols.required_modifiers = const
@@ -318,32 +316,27 @@ dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static, readonly
dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
dotnet_naming_symbols.property_symbols.applicable_accessibilities = *
dotnet_naming_symbols.property_symbols.applicable_kinds = property
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.public_fields_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.public_fields_symbols.applicable_kinds = field
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public, internal, protected, protected_internal, private_protected
dotnet_naming_symbols.static_readonly_symbols.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
dotnet_naming_symbols.static_readonly_symbols.applicable_kinds = field
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static, readonly
dotnet_naming_symbols.static_readonly_symbols.required_modifiers = static,readonly
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_accessibilities = *
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace, class, struct, enum, delegate
dotnet_naming_symbols.types_and_namespaces_symbols.applicable_kinds = namespace,class,struct,enum,delegate
dotnet_naming_symbols.type_parameters_symbols.applicable_accessibilities = *
dotnet_naming_symbols.type_parameters_symbols.applicable_kinds = type_parameter
# ReSharper properties
resharper_braces_for_ifelse = required_for_multiline
resharper_csharp_wrap_arguments_style = chop_if_long
resharper_csharp_wrap_parameters_style = chop_if_long
resharper_keep_existing_attribute_arrangement = true
resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long
resharper_csharp_trailing_comma_in_multiline_lists = true
[*.{csproj,xml,yml,yaml,dll.config,msbuildproj,targets,props}]
indent_size = 2

6
.github/CODEOWNERS vendored
View File

@@ -15,7 +15,6 @@
/Content.*/GameTicking/ @moonheart08 @EmoGarbage404
/Resources/ServerInfo/ @moonheart08 @Chief-Engineer
/Resources/ServerInfo/Guidebook/ @moonheart08 @EmoGarbage404
/Resources/ServerInfo/Guidebook/ServerRules/ @Chief-Engineer
/Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer
@@ -24,12 +23,11 @@
/Resources/Prototypes/Body/ @DrSmugleaf # suffering
/Resources/Prototypes/Entities/Mobs/Player/ @DrSmugleaf
/Resources/Prototypes/Entities/Mobs/Species/ @DrSmugleaf
/Resources/Prototypes/Guidebook/rules.yml @Chief-Engineer
/Content.*/Body/ @DrSmugleaf
/Content.YAMLLinter @DrSmugleaf
/Content.Shared/Damage/ @DrSmugleaf
/Content.*/Anomaly/ @EmoGarbage404 @TheShuEd
/Content.*/Anomaly/ @EmoGarbage404
/Content.*/Lathe/ @EmoGarbage404
/Content.*/Materials/ @EmoGarbage404
/Content.*/Mech/ @EmoGarbage404
@@ -37,7 +35,7 @@
/Content.*/Stack/ @EmoGarbage404
/Content.*/Xenoarchaeology/ @EmoGarbage404
/Content.*/Zombies/ @EmoGarbage404
/Resources/Prototypes/Entities/Structures/Specific/anomalies.yml @EmoGarbage404 @TheShuEd
/Resources/Prototypes/Entities/Structures/Specific/anomalies.yml @EmoGarbage404
/Resources/Prototypes/Research/ @EmoGarbage404
/Content.*/Forensics/ @ficcialfaint

View File

@@ -1,17 +1,43 @@
<!-- Please read these guidelines before opening your PR: https://docs.spacestation14.io/en/getting-started/pr-guideline -->
<!-- The text between the arrows are comments - they will not be visible on your PR. -->
## About the PR
<!-- What did you change in this PR? -->
<!-- Что вы изменили в своем пулл реквесте? -->
## Why / Balance
<!-- Why was it changed? Link any discussions or issues here. Please discuss how this would affect game balance. -->
<!-- Зачем нужно это изменение? Прикрепите любые обсуждения или проблемы здесь. Опишите, как это повлияет на текущий баланс игры. -->
## Technical details
<!-- If this is a code change, summarize at high level how your new code works. This makes it easier to review. -->
## Media
<!--
PRs which make ingame changes (adding clothing, items, new features, etc) are required to have media attached that showcase the changes.
Small fixes/refactors are exempt.
Any media may be used in SS14 progress reports, with clear credit given.
If you're unsure whether your PR will require media, ask a maintainer.
Check the box below to confirm that you have in fact seen this (put an X in the brackets, like [X]):
-->
<!--
Пулл реквесты, которые несут за собой игровые изменения (добавления одежды, предметов и так далее) требуют чтобы вы прикрепили скриншоты или видеоролики, демонстрирующие эти изменения.
Небольшие исправления не считаются.
- [ ] I have added screenshots/videos to this PR showcasing its changes ingame, **or** this PR does not require an ingame showcase
## Breaking changes
<!--
List any breaking changes, including namespace, public class/method/field changes, prototype renames; and provide instructions for fixing them. This will be pasted in #codebase-changes.
-->
**Changelog**
<!--
Make players aware of new features and changes that could affect how they play the game by adding a Changelog entry. Please read the Changelog guidelines located at: https://docs.spacestation14.io/en/getting-started/pr-guideline#changelog
-->
<!--
Make sure to take this Changelog template out of the comment block in order for it to show up.
:cl:
- add: Added fun!
- remove: Removed fun!
- tweak: Changed fun!
- fix: Fixed fun!
-->

16
.github/labeler.yml vendored
View File

@@ -1,18 +1,12 @@
"Changes: Sprites":
- changed-files:
- any-glob-to-any-file: '**/*.rsi/*.png'
- '**/*.rsi/*.png'
"Changes: Map":
- changed-files:
- any-glob-to-any-file:
- 'Resources/Maps/*.yml'
- 'Resources/Prototypes/Maps/*.yml'
- 'Resources/Maps/*.yml'
- 'Resources/Prototypes/Maps/*.yml'
"Changes: UI":
- changed-files:
- any-glob-to-any-file: '**/*.xaml*'
- '**/*.xaml*'
"No C#":
- changed-files:
# Equiv to any-glob-to-all as long as this has one matcher. If ALL changed files are not C# files, then apply label.
- all-globs-to-all-files: "!**/*.cs"
- all: ["!**/*.cs"]

View File

@@ -75,17 +75,15 @@
"license":{
"$id":"#/properties/license",
"default":"",
"description":"The license for the associated icon states. Restricted to CP14-compatible asset licenses.",
"description":"The license for the associated icon states. Restricted to SS14-compatible asset licenses.",
"enum":[
"CC-BY-SA-3.0",
"CC-BY-SA-4.0",
"CC0-1.0",
"CC-BY-NC-3.0",
"CC-BY-NC-4.0",
"CC-BY-NC-SA-3.0",
"CC-BY-NC-SA-4.0",
"CC0-1.0",
"All rights reserved for the CrystallPunk14 project only"
"CC0-1.0"
],
"examples":[
"CC-BY-SA-3.0"

View File

@@ -1,20 +1,18 @@
name: Check Merge Conflicts
on:
push:
branches:
- master
pull_request_target:
types:
- opened
- synchronize
- reopened
- ready_for_review
jobs:
Label:
if: ( github.event.pull_request.draft == false ) && ( github.actor != 'PJBot' )
if: github.actor != 'PJBot'
runs-on: ubuntu-latest
steps:
- name: Check for Merge Conflicts
uses: eps1lon/actions-label-merge-conflict@v3.0.0
uses: ike709/actions-label-merge-conflict@9eefdd17e10566023c46d2dc6dc04fcb8ec76142
with:
dirtyLabel: "Merge Conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -6,9 +6,8 @@ on:
jobs:
labeler:
if: github.actor != 'PJBot'
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
- uses: actions/labeler@v3
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View File

@@ -15,12 +15,9 @@ jobs:
- name: Get changed files
id: files
uses: Ana06/get-changed-files@v2.3.0
uses: Ana06/get-changed-files@v1.2
with:
format: 'space-delimited'
filter: |
**.rsi
**.png
- name: Diff changed RSIs
id: diff

View File

@@ -21,5 +21,5 @@ jobs:
with:
schema: RobustToolbox/Schemas/rga.yml
path_pattern: .*attributions.ya?ml$
validators_path: Schemas/rga_validators.py
validators_requirements: Schemas/rga_requirements.txt
validators_path: RobustToolbox/Schemas/rga_validators.py
validators_requirements: RobustToolbox/Schemas/rga_requirements.txt

View File

@@ -23,4 +23,4 @@ jobs:
pip3 install --ignore-installed --user pillow jsonschema
- name: Validate RSIs
run: |
python3 Schemas/validate_rsis.py Resources/
python3 RobustToolbox/Schemas/validate_rsis.py Resources/

View File

@@ -1,273 +0,0 @@
#nullable enable
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Shared.Clothing.Components;
using Content.Shared.Doors.Components;
using Content.Shared.Item;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Random;
namespace Content.Benchmarks;
/// <summary>
/// Benchmarks for comparing the speed of various component fetching/lookup related methods, including directed event
/// subscriptions
/// </summary>
[Virtual]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class ComponentQueryBenchmark
{
public const string Map = "Maps/atlas.yml";
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private MapId _mapId = new(10);
private EntityQuery<ItemComponent> _itemQuery;
private EntityQuery<ClothingComponent> _clothingQuery;
private EntityQuery<MapComponent> _mapQuery;
private EntityUid[] _items = default!;
[GlobalSetup]
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(typeof(QueryBenchSystem).Assembly);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
_itemQuery = _entMan.GetEntityQuery<ItemComponent>();
_clothingQuery = _entMan.GetEntityQuery<ClothingComponent>();
_mapQuery = _entMan.GetEntityQuery<MapComponent>();
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
_pair.Server.WaitPost(() =>
{
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
}).GetAwaiter().GetResult();
_items = new EntityUid[_entMan.Count<ItemComponent>()];
var i = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out _))
{
_items[i++] = uid;
}
}
[GlobalCleanup]
public async Task Cleanup()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
#region TryComp
/// <summary>
/// Baseline TryComp benchmark. When the benchmark was created, around 40% of the items were clothing.
/// </summary>
[Benchmark(Baseline = true)]
[BenchmarkCategory("TryComp")]
public int TryComp()
{
var hashCode = 0;
foreach (var uid in _items)
{
if (_clothingQuery.TryGetComponent(uid, out var clothing))
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Variant of <see cref="TryComp"/> that is meant to always fail to get a component.
/// </summary>
[Benchmark]
[BenchmarkCategory("TryComp")]
public int TryCompFail()
{
var hashCode = 0;
foreach (var uid in _items)
{
if (_mapQuery.TryGetComponent(uid, out var map))
hashCode = HashCode.Combine(hashCode, map.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Variant of <see cref="TryComp"/> that is meant to always succeed getting a component.
/// </summary>
[Benchmark]
[BenchmarkCategory("TryComp")]
public int TryCompSucceed()
{
var hashCode = 0;
foreach (var uid in _items)
{
if (_itemQuery.TryGetComponent(uid, out var item))
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Variant of <see cref="TryComp"/> that uses `Resolve()` to try get the component.
/// </summary>
[Benchmark]
[BenchmarkCategory("TryComp")]
public int Resolve()
{
var hashCode = 0;
foreach (var uid in _items)
{
DoResolve(uid, ref hashCode);
}
return hashCode;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void DoResolve(EntityUid uid, ref int hash, ClothingComponent? clothing = null)
{
if (_clothingQuery.Resolve(uid, ref clothing, false))
hash = HashCode.Combine(hash, clothing.GetHashCode());
}
#endregion
#region Enumeration
[Benchmark]
[BenchmarkCategory("Item Enumerator")]
public int SingleItemEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var item))
{
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Item Enumerator")]
public int DoubleItemEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent>();
while (enumerator.MoveNext(out _, out var item))
{
hashCode = HashCode.Combine(hashCode, item.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Item Enumerator")]
public int TripleItemEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ClothingComponent, ItemComponent, TransformComponent>();
while (enumerator.MoveNext(out _, out _, out var xform))
{
hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Airlock Enumerator")]
public int SingleAirlockEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent>();
while (enumerator.MoveNext(out var airlock))
{
hashCode = HashCode.Combine(hashCode, airlock.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Airlock Enumerator")]
public int DoubleAirlockEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent>();
while (enumerator.MoveNext(out _, out var door))
{
hashCode = HashCode.Combine(hashCode, door.GetHashCode());
}
return hashCode;
}
[Benchmark]
[BenchmarkCategory("Airlock Enumerator")]
public int TripleAirlockEnumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<AirlockComponent, DoorComponent, TransformComponent>();
while (enumerator.MoveNext(out _, out _, out var xform))
{
hashCode = HashCode.Combine(hashCode, xform.GetHashCode());
}
return hashCode;
}
#endregion
[Benchmark(Baseline = true)]
[BenchmarkCategory("Events")]
public int StructEvents()
{
var ev = new QueryBenchEvent();
foreach (var uid in _items)
{
_entMan.EventBus.RaiseLocalEvent(uid, ref ev);
}
return ev.HashCode;
}
}
[ByRefEvent]
public struct QueryBenchEvent
{
public int HashCode;
}
public sealed class QueryBenchSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ClothingComponent, QueryBenchEvent>(OnEvent);
}
private void OnEvent(EntityUid uid, ClothingComponent component, ref QueryBenchEvent args)
{
args.HashCode = HashCode.Combine(args.HashCode, component.GetHashCode());
}
}

View File

@@ -47,7 +47,6 @@ namespace Content.Benchmarks
var componentFactory = new Mock<IComponentFactory>();
componentFactory.Setup(p => p.GetComponent<DummyComponent>()).Returns(new DummyComponent());
componentFactory.Setup(m => m.GetIndex(typeof(DummyComponent))).Returns(CompIdx.Index<DummyComponent>());
componentFactory.Setup(p => p.GetRegistration(It.IsAny<DummyComponent>())).Returns(dummyReg);
componentFactory.Setup(p => p.GetAllRegistrations()).Returns(new[] { dummyReg });
componentFactory.Setup(p => p.GetAllRefTypes()).Returns(new[] { CompIdx.Index<DummyComponent>() });

View File

@@ -0,0 +1,137 @@
#nullable enable
using System;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Shared.Clothing.Components;
using Content.Shared.Item;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Random;
namespace Content.Benchmarks;
[Virtual]
public class EntityQueryBenchmark
{
public const string Map = "Maps/atlas.yml";
private TestPair _pair = default!;
private IEntityManager _entMan = default!;
private MapId _mapId = new MapId(10);
private EntityQuery<ClothingComponent> _clothingQuery;
[GlobalSetup]
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup(null);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
_pair.Server.WaitPost(() =>
{
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
}).GetAwaiter().GetResult();
_clothingQuery = _entMan.GetEntityQuery<ClothingComponent>();
// Apparently ~40% of entities are items, and 1 in 6 of those are clothing.
/*
var entCount = _entMan.EntityCount;
var itemCount = _entMan.Count<ItemComponent>();
var clothingCount = _entMan.Count<ClothingComponent>();
var itemRatio = (float) itemCount / entCount;
var clothingRatio = (float) clothingCount / entCount;
Console.WriteLine($"Entities: {entCount}. Items: {itemRatio:P2}. Clothing: {clothingRatio:P2}.");
*/
}
[GlobalCleanup]
public async Task Cleanup()
{
await _pair.DisposeAsync();
PoolManager.Shutdown();
}
[Benchmark]
public int HasComponent()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_entMan.HasComponent<ClothingComponent>(uid))
hashCode = HashCode.Combine(hashCode, uid.Id);
}
return hashCode;
}
[Benchmark]
public int HasComponentQuery()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_clothingQuery.HasComponent(uid))
hashCode = HashCode.Combine(hashCode, uid.Id);
}
return hashCode;
}
[Benchmark]
public int TryGetComponent()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_entMan.TryGetComponent(uid, out ClothingComponent? clothing))
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
[Benchmark]
public int TryGetComponentQuery()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent>();
while (enumerator.MoveNext(out var uid, out var _))
{
if (_clothingQuery.TryGetComponent(uid, out var clothing))
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
/// <summary>
/// Enumerate all entities with both an item and clothing component.
/// </summary>
[Benchmark]
public int Enumerator()
{
var hashCode = 0;
var enumerator = _entMan.AllEntityQueryEnumerator<ItemComponent, ClothingComponent>();
while (enumerator.MoveNext(out var _, out var clothing))
{
hashCode = HashCode.Combine(hashCode, clothing.GetHashCode());
}
return hashCode;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -26,7 +26,7 @@ public class MapLoadBenchmark
public void Setup()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
PoolManager.Startup(null);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
var server = _pair.Server;
@@ -46,7 +46,7 @@ public class MapLoadBenchmark
PoolManager.Shutdown();
}
public static readonly string[] MapsSource = { "Empty", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry", "Oasis" };
public static readonly string[] MapsSource = { "Empty", "Box", "Bagel", "Dev", "CentComm", "Atlas", "Core", "TestTeg", "Saltern", "Packed", "Omega", "Cluster", "Reach", "Origin", "Meta", "Marathon", "Europa", "MeteorArena", "Fland", "Barratry" };
[ParamsSource(nameof(MapsSource))]
public string Map;

View File

@@ -1,17 +1,19 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Content.IntegrationTests;
using Content.IntegrationTests.Pair;
using Content.Server.Mind;
using Content.Server.Warps;
using Robust.Server.GameObjects;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Random;
@@ -47,7 +49,7 @@ public class PvsBenchmark
#if !DEBUG
ProgramShared.PathOffset = "../../../../";
#endif
PoolManager.Startup();
PoolManager.Startup(null);
_pair = PoolManager.GetServerClient().GetAwaiter().GetResult();
_entMan = _pair.Server.ResolveDependency<IEntityManager>();
@@ -56,20 +58,15 @@ public class PvsBenchmark
_pair.Server.CfgMan.SetCVar(CVars.NetPvsAsync, false);
_sys = _entMan.System<SharedTransformSystem>();
SetupAsync().Wait();
}
private async Task SetupAsync()
{
// Spawn the map
_pair.Server.ResolveDependency<IRobustRandom>().SetSeed(42);
await _pair.Server.WaitPost(() =>
_pair.Server.WaitPost(() =>
{
var success = _entMan.System<MapLoaderSystem>().TryLoad(_mapId, Map, out _);
if (!success)
throw new Exception("Map load failed");
_pair.Server.MapMan.DoMapInitialize(_mapId);
});
}).Wait();
// Get list of ghost warp positions
_spawns = _entMan.AllComponentsList<WarpPointComponent>()
@@ -79,19 +76,17 @@ public class PvsBenchmark
Array.Resize(ref _players, PlayerCount);
// Spawn "Players"
_players = await _pair.Server.AddDummySessions(PlayerCount);
await _pair.Server.WaitPost(() =>
// Spawn "Players".
_pair.Server.WaitPost(() =>
{
var mind = _pair.Server.System<MindSystem>();
for (var i = 0; i < PlayerCount; i++)
{
var pos = _spawns[i % _spawns.Length];
var uid =_entMan.SpawnEntity("MobHuman", pos);
_pair.Server.ConsoleHost.ExecuteCommand($"setoutfit {_entMan.GetNetEntity(uid)} CaptainGear");
mind.ControlMob(_players[i].UserId, uid);
_players[i] = new DummySession{AttachedEntity = uid};
}
});
}).Wait();
// Repeatedly move players around so that they "explore" the map and see lots of entities.
// This will populate their PVS data with out-of-view entities.
@@ -173,4 +168,20 @@ public class PvsBenchmark
}).Wait();
_pair.Server.PvsTick(_players);
}
private sealed class DummySession : ICommonSession
{
public SessionStatus Status => SessionStatus.InGame;
public EntityUid? AttachedEntity {get; set; }
public NetUserId UserId => default;
public string Name => string.Empty;
public short Ping => default;
public INetChannel Channel { get; set; } = default!;
public LoginType AuthType => default;
public HashSet<EntityUid> ViewSubscriptions { get; } = new();
public DateTime ConnectedTime { get; set; }
public SessionState State => default!;
public SessionData Data => default!;
public bool ClientSide { get; set; }
}
}

View File

@@ -32,7 +32,7 @@ public class SpawnEquipDeleteBenchmark
public async Task SetupAsync()
{
ProgramShared.PathOffset = "../../../../";
PoolManager.Startup();
PoolManager.Startup(null);
_pair = await PoolManager.GetServerClient();
var server = _pair.Server;
@@ -58,7 +58,7 @@ public class SpawnEquipDeleteBenchmark
for (var i = 0; i < N; i++)
{
_entity = server.EntMan.SpawnAttachedTo(Mob, _coords);
_spawnSys.EquipStartingGear(_entity, _gear);
_spawnSys.EquipStartingGear(_entity, _gear, null);
server.EntMan.DeleteEntity(_entity);
}
});

View File

@@ -9,20 +9,20 @@ namespace Content.Client.Access;
public sealed class AccessOverlay : Overlay
{
private const string TextFontPath = "/Fonts/NotoSans/NotoSans-Regular.ttf";
private const int TextFontSize = 12;
private readonly IEntityManager _entityManager;
private readonly SharedTransformSystem _transformSystem;
private readonly EntityLookupSystem _lookup;
private readonly SharedTransformSystem _xform;
private readonly Font _font;
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
public AccessOverlay(IEntityManager entityManager, IResourceCache resourceCache, SharedTransformSystem transformSystem)
public AccessOverlay(IEntityManager entManager, IResourceCache cache, EntityLookupSystem lookup, SharedTransformSystem xform)
{
_entityManager = entityManager;
_transformSystem = transformSystem;
_font = resourceCache.GetFont(TextFontPath, TextFontSize);
_entityManager = entManager;
_lookup = lookup;
_xform = xform;
_font = cache.GetFont("/Fonts/NotoSans/NotoSans-Regular.ttf", 12);
}
protected override void Draw(in OverlayDrawArgs args)
@@ -30,65 +30,52 @@ public sealed class AccessOverlay : Overlay
if (args.ViewportControl == null)
return;
var textBuffer = new StringBuilder();
var query = _entityManager.EntityQueryEnumerator<AccessReaderComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var accessReader, out var transform))
var readerQuery = _entityManager.GetEntityQuery<AccessReaderComponent>();
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var ent in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldAABB,
LookupFlags.Static | LookupFlags.Approximate))
{
textBuffer.Clear();
var entityName = _entityManager.ToPrettyString(uid);
textBuffer.AppendLine(entityName.Prototype);
textBuffer.Append("UID: ");
textBuffer.Append(entityName.Uid.Id);
textBuffer.Append(", NUID: ");
textBuffer.Append(entityName.Nuid.Id);
textBuffer.AppendLine();
if (!accessReader.Enabled)
if (!readerQuery.TryGetComponent(ent, out var reader) ||
!xformQuery.TryGetComponent(ent, out var xform))
{
textBuffer.AppendLine("-Disabled");
continue;
}
if (accessReader.AccessLists.Count > 0)
var text = new StringBuilder();
var index = 0;
var a = $"{_entityManager.ToPrettyString(ent)}";
text.Append(a);
foreach (var list in reader.AccessLists)
{
var groupNumber = 0;
foreach (var accessList in accessReader.AccessLists)
a = $"Tag {index}";
text.AppendLine(a);
foreach (var entry in list)
{
groupNumber++;
foreach (var entry in accessList)
{
textBuffer.Append("+Set ");
textBuffer.Append(groupNumber);
textBuffer.Append(": ");
textBuffer.Append(entry.Id);
textBuffer.AppendLine();
}
a = $"- {entry}";
text.AppendLine(a);
}
index++;
}
string textStr;
if (text.Length >= 2)
{
textStr = text.ToString();
textStr = textStr[..^2];
}
else
{
textBuffer.AppendLine("+Unrestricted");
textStr = "";
}
foreach (var key in accessReader.AccessKeys)
{
textBuffer.Append("+Key ");
textBuffer.Append(key.OriginStation);
textBuffer.Append(": ");
textBuffer.Append(key.Id);
textBuffer.AppendLine();
}
var screenPos = args.ViewportControl.WorldToScreen(_xform.GetWorldPosition(xform));
foreach (var tag in accessReader.DenyTags)
{
textBuffer.Append("-Tag ");
textBuffer.AppendLine(tag.Id);
}
var accessInfoText = textBuffer.ToString();
var screenPos = args.ViewportControl.WorldToScreen(_transformSystem.GetWorldPosition(transform));
args.ScreenHandle.DrawString(_font, screenPos, accessInfoText, Color.Gold);
args.ScreenHandle.DrawString(_font, screenPos, textStr, Color.Gold);
}
}
}

View File

@@ -7,16 +7,8 @@ namespace Content.Client.Access.Commands;
public sealed class ShowAccessReadersCommand : IConsoleCommand
{
public string Command => "showaccessreaders";
public string Description => "Toggles showing access reader permissions on the map";
public string Help => """
Overlay Info:
-Disabled | The access reader is disabled
+Unrestricted | The access reader has no restrictions
+Set [Index]: [Tag Name]| A tag in an access set (accessor needs all tags in the set to be allowed by the set)
+Key [StationUid]: [StationRecordKeyId] | A StationRecordKey that is allowed
-Tag [Tag Name] | A tag that is not allowed (takes priority over other allows)
""";
public string Description => "Shows all access readers in the viewport";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var collection = IoCManager.Instance;
@@ -34,9 +26,10 @@ public sealed class ShowAccessReadersCommand : IConsoleCommand
var entManager = collection.Resolve<IEntityManager>();
var cache = collection.Resolve<IResourceCache>();
var lookup = entManager.System<EntityLookupSystem>();
var xform = entManager.System<SharedTransformSystem>();
overlay.AddOverlay(new AccessOverlay(entManager, cache, xform));
overlay.AddOverlay(new AccessOverlay(entManager, cache, lookup, xform));
shell.WriteLine($"Set access reader debug overlay to true");
}
}

View File

@@ -2,4 +2,6 @@
namespace Content.Client.Access;
public sealed class IdCardSystem : SharedIdCardSystem;
public sealed class IdCardSystem : SharedIdCardSystem
{
}

View File

@@ -1,7 +1,5 @@
using Content.Shared.Access.Systems;
using Content.Shared.StatusIcon;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.Access.UI
{
@@ -42,9 +40,9 @@ namespace Content.Client.Access.UI
SendMessage(new AgentIDCardJobChangedMessage(newJob));
}
public void OnJobIconChanged(ProtoId<StatusIconPrototype> newJobIconId)
public void OnJobIconChanged(string newJobIcon)
{
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIconId));
SendMessage(new AgentIDCardJobIconChangedMessage(newJobIcon));
}
/// <summary>
@@ -59,7 +57,7 @@ namespace Content.Client.Access.UI
_window.SetCurrentName(cast.CurrentName);
_window.SetCurrentJob(cast.CurrentJob);
_window.SetAllowedIcons(cast.Icons, cast.CurrentJobIconId);
_window.SetAllowedIcons(cast.Icons);
}
protected override void Dispose(bool disposing)

View File

@@ -38,7 +38,7 @@ namespace Content.Client.Access.UI
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
}
public void SetAllowedIcons(HashSet<ProtoId<StatusIconPrototype>> icons, string currentJobIconId)
public void SetAllowedIcons(HashSet<string> icons)
{
IconGrid.DisposeAllChildren();
@@ -46,8 +46,10 @@ namespace Content.Client.Access.UI
var i = 0;
foreach (var jobIconId in icons)
{
if (!_prototypeManager.TryIndex(jobIconId, out var jobIcon))
if (!_prototypeManager.TryIndex<StatusIconPrototype>(jobIconId, out var jobIcon))
{
continue;
}
String styleBase = StyleBase.ButtonOpenBoth;
var modulo = i % JobIconColumnCount;
@@ -75,12 +77,8 @@ namespace Content.Client.Access.UI
};
jobIconButton.AddChild(jobIconTexture);
jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIconId);
jobIconButton.OnPressed += _ => _bui.OnJobIconChanged(jobIcon.ID);
IconGrid.AddChild(jobIconButton);
if (jobIconId.Equals(currentJobIconId))
jobIconButton.Pressed = true;
i++;
}
}

View File

@@ -1,5 +1,6 @@
using Content.Shared.Access;
using Content.Shared.Access.Components;
using Content.Shared.Access;
using Content.Shared.Access.Systems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.CrewManifest;

View File

@@ -27,9 +27,6 @@ namespace Content.Client.Access.UI
private string? _lastJobTitle;
private string? _lastJobProto;
// The job that will be picked if the ID doesn't have a job on the station.
private static ProtoId<JobPrototype> _defaultJob = "Passenger";
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager,
List<ProtoId<AccessLevelPrototype>> accessLevels)
{
@@ -68,6 +65,7 @@ namespace Content.Client.Access.UI
}
JobPresetOptionButton.OnItemSelected += SelectJobPreset;
_accessButtons.Populate(accessLevels, prototypeManager);
AccessLevelControlContainer.AddChild(_accessButtons);
@@ -174,15 +172,11 @@ 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.
// For example, a new ID from a box would have no job index.
if (jobIndex < 0)
if (jobIndex >= 0)
{
jobIndex = _jobPrototypeIds.IndexOf(_defaultJob);
JobPresetOptionButton.SelectId(jobIndex);
}
JobPresetOptionButton.SelectId(jobIndex);
_lastFullName = state.TargetIdFullName;
_lastJobTitle = state.TargetIdJobTitle;
_lastJobProto = state.TargetIdJobPrototype;

View File

@@ -247,10 +247,7 @@ namespace Content.Client.Actions
if (action.ClientExclusive)
{
if (instantAction.Event != null)
{
instantAction.Event.Performer = user;
instantAction.Event.Action = actionId;
}
PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime);
}

View File

@@ -3,7 +3,7 @@ using Robust.Shared.GameStates;
namespace Content.Client.Administration.Components;
[RegisterComponent]
[RegisterComponent, NetworkedComponent]
public sealed partial class HeadstandComponent : SharedHeadstandComponent
{

View File

@@ -3,5 +3,6 @@ using Robust.Shared.GameStates;
namespace Content.Client.Administration.Components;
[RegisterComponent]
public sealed partial class KillSignComponent : SharedKillSignComponent;
[NetworkedComponent, RegisterComponent]
public sealed partial class KillSignComponent : SharedKillSignComponent
{ }

View File

@@ -126,15 +126,12 @@ namespace Content.Client.Administration.Managers
public AdminData? GetAdminData(EntityUid uid, bool includeDeAdmin = false)
{
if (uid == _player.LocalEntity && (_adminData?.Active ?? includeDeAdmin))
return _adminData;
return null;
return uid == _player.LocalEntity ? _adminData : null;
}
public AdminData? GetAdminData(ICommonSession session, bool includeDeAdmin = false)
{
if (_player.LocalUser == session.UserId && (_adminData?.Active ?? includeDeAdmin))
if (_player.LocalUser == session.UserId)
return _adminData;
return null;

View File

@@ -1,7 +0,0 @@
using Content.Shared.Administration;
namespace Content.Client.Administration.Systems;
public sealed class AdminFrozenSystem : SharedAdminFrozenSystem
{
}

View File

@@ -1,6 +1,3 @@
using Content.Shared.Administration;
using Content.Shared.Administration.Managers;
using Content.Shared.Mind.Components;
using Content.Shared.Verbs;
using Robust.Client.Console;
using Robust.Shared.Utility;
@@ -14,12 +11,10 @@ namespace Content.Client.Administration.Systems
{
[Dependency] private readonly IClientConGroupController _clientConGroupController = default!;
[Dependency] private readonly IClientConsoleHost _clientConsoleHost = default!;
[Dependency] private readonly ISharedAdminManager _admin = default!;
public override void Initialize()
{
SubscribeLocalEvent<GetVerbsEvent<Verb>>(AddAdminVerbs);
}
private void AddAdminVerbs(GetVerbsEvent<Verb> args)
@@ -38,24 +33,6 @@ namespace Content.Client.Administration.Systems
};
args.Verbs.Add(verb);
}
if (!_admin.IsAdmin(args.User))
return;
if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
args.ExtraCategories.Add(VerbCategory.Admin);
if (_admin.HasAdminFlag(args.User, AdminFlags.Fun) && HasComp<MindContainerComponent>(args.Target))
args.ExtraCategories.Add(VerbCategory.Antag);
if (_admin.HasAdminFlag(args.User, AdminFlags.Debug))
args.ExtraCategories.Add(VerbCategory.Debug);
if (_admin.HasAdminFlag(args.User, AdminFlags.Fun))
args.ExtraCategories.Add(VerbCategory.Smite);
if (_admin.HasAdminFlag(args.User, AdminFlags.Admin))
args.ExtraCategories.Add(VerbCategory.Tricks);
}
}
}

View File

@@ -6,8 +6,7 @@
xmlns:tabs="clr-namespace:Content.Client.Administration.UI.Tabs"
xmlns:playerTab="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
xmlns:objectsTab="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab"
xmlns:baby="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab">
xmlns:panic="clr-namespace:Content.Client.Administration.UI.Tabs.PanicBunkerTab">
<TabContainer Name="MasterTabContainer">
<adminTab:AdminTab />
<adminbusTab:AdminbusTab />
@@ -15,7 +14,6 @@
<tabs:RoundTab />
<tabs:ServerTab />
<panic:PanicBunkerTab Name="PanicBunkerControl" Access="Public" />
<baby:BabyJailTab Name="BabyJailControl" Access="Public" />
<playerTab:PlayerTab Name="PlayerTabControl" Access="Public" />
<objectsTab:ObjectsTab Name="ObjectsTabControl" Access="Public" />
</TabContainer>

View File

@@ -21,12 +21,8 @@ namespace Content.Client.Administration.UI
MasterTabContainer.SetTabTitle(3, Loc.GetString("admin-menu-round-tab"));
MasterTabContainer.SetTabTitle(4, Loc.GetString("admin-menu-server-tab"));
MasterTabContainer.SetTabTitle(5, Loc.GetString("admin-menu-panic-bunker-tab"));
/*
* TODO: Remove baby jail code once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-baby-jail-tab"));
MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-players-tab"));
MasterTabContainer.SetTabTitle(8, Loc.GetString("admin-menu-objects-tab"));
MasterTabContainer.SetTabTitle(6, Loc.GetString("admin-menu-players-tab"));
MasterTabContainer.SetTabTitle(7, Loc.GetString("admin-menu-objects-tab"));
}
protected override void Dispose(bool disposing)

View File

@@ -3,7 +3,6 @@ using System.Net;
using System.Net.Sockets;
using Content.Client.Administration.UI.CustomControls;
using Content.Shared.Administration;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
@@ -12,7 +11,6 @@ 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.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -34,11 +32,8 @@ public sealed partial class BanPanel : DefaultWindow
// This is less efficient than just holding a reference to the root control and enumerating children, but you
// have to know how the controls are nested, which makes the code more complicated.
private readonly List<CheckBox> _roleCheckboxes = new();
private readonly ISawmill _banpanelSawmill;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private enum TabNumbers
{
@@ -70,7 +65,6 @@ public sealed partial class BanPanel : DefaultWindow
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_banpanelSawmill = _logManager.GetSawmill("admin.banpanel");
PlayerList.OnSelectionChanged += OnPlayerSelectionChanged;
PlayerNameLine.OnFocusExit += _ => OnPlayerNameChanged();
PlayerCheckbox.OnPressed += _ =>
@@ -110,11 +104,6 @@ public sealed partial class BanPanel : DefaultWindow
};
SubmitButton.OnPressed += SubmitButtonOnOnPressed;
IpCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanIpBanDefault);
HwidCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanHwidBanDefault);
LastConnCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanUseLastDetails);
EraseCheckbox.Pressed = _cfg.GetCVar(CCVars.ServerBanErasePlayer);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-none"), (int) NoteSeverity.None);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-low"), (int) NoteSeverity.Minor);
SeverityOption.AddItem(Loc.GetString("admin-note-editor-severity-medium"), (int) NoteSeverity.Medium);
@@ -147,7 +136,7 @@ public sealed partial class BanPanel : DefaultWindow
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
foreach (var proto in prototypeManager.EnumeratePrototypes<DepartmentPrototype>())
{
CreateRoleGroup(proto.ID, proto.Roles.Select(p => p.Id), proto.Color);
CreateRoleGroup(proto.ID, proto.Roles, proto.Color);
}
CreateRoleGroup("Antagonist", prototypeManager.EnumeratePrototypes<AntagPrototype>().Select(p => p.ID), Color.Red);
@@ -186,39 +175,6 @@ public sealed partial class BanPanel : DefaultWindow
c.Pressed = args.Pressed;
}
}
if (args.Pressed)
{
if (!Enum.TryParse(_cfg.GetCVar(CCVars.DepartmentBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banpanelSawmill
.Warning("Departmental role ban severity could not be parsed from config!");
return;
}
SeverityOption.SelectId((int) newSeverity);
}
else
{
foreach (var childContainer in RolesContainer.Children)
{
if (childContainer is Container)
{
foreach (var child in childContainer.Children)
{
if (child is CheckBox { Pressed: true })
return;
}
}
}
if (!Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity newSeverity))
{
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
return;
}
SeverityOption.SelectId((int) newSeverity);
}
};
outerContainer.AddChild(innerContainer);
foreach (var role in roleList)
@@ -397,35 +353,6 @@ public sealed partial class BanPanel : DefaultWindow
{
TypeOption.ModulateSelfOverride = null;
Tabs.SetTabVisible((int) TabNumbers.Roles, TypeOption.SelectedId == (int) Types.Role);
NoteSeverity? newSeverity = null;
switch (TypeOption.SelectedId)
{
case (int)Types.Server:
if (Enum.TryParse(_cfg.GetCVar(CCVars.ServerBanDefaultSeverity), true, out NoteSeverity serverSeverity))
newSeverity = serverSeverity;
else
{
_banpanelSawmill
.Warning("Server ban severity could not be parsed from config!");
}
break;
case (int) Types.Role:
if (Enum.TryParse(_cfg.GetCVar(CCVars.RoleBanDefaultSeverity), true, out NoteSeverity roleSeverity))
{
newSeverity = roleSeverity;
}
else
{
_banpanelSawmill
.Warning("Role ban severity could not be parsed from config!");
}
break;
}
if (newSeverity != null)
SeverityOption.SelectId((int) newSeverity.Value);
}
private void UpdateSubmitEnabled()

View File

@@ -75,7 +75,7 @@ namespace Content.Client.Administration.UI.Bwoink
if (info.Antag && info.ActiveThisRound)
sb.Append(new Rune(0x1F5E1)); // 🗡
if (info.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
if (info.OverallPlaytime <= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
sb.Append(new Rune(0x23F2)); // ⏲
sb.AppendFormat("\"{0}\"", text);
@@ -226,7 +226,7 @@ namespace Content.Client.Administration.UI.Bwoink
if (pl.Antag)
sb.Append(new Rune(0x1F5E1)); // 🗡
if (pl.OverallPlaytime <= TimeSpan.FromMinutes(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
if (pl.OverallPlaytime <= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.NewPlayerThreshold)))
sb.Append(new Rune(0x23F2)); // ⏲
sb.AppendFormat("\"{0}\"", pl.CharacterName);
@@ -243,9 +243,9 @@ namespace Content.Client.Administration.UI.Bwoink
{
UpdateButtons();
AHelpHelper.HideAllPanels();
if (ch != null)
{
AHelpHelper.HideAllPanels();
var panel = AHelpHelper.EnsurePanel(ch.Value);
panel.Visible = true;
}

View File

@@ -16,17 +16,14 @@ namespace Content.Client.Administration.UI.Bwoink
Bwoink.ChannelSelector.OnSelectionChanged += sel =>
{
if (sel is null)
if (sel is not null)
{
Title = Loc.GetString("bwoink-title-none-selected");
return;
}
Title = $"{sel.CharacterName} / {sel.Username}";
Title = $"{sel.CharacterName} / {sel.Username}";
if (sel.OverallPlaytime != null)
{
Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
if (sel.OverallPlaytime != null)
{
Title += $" | {Loc.GetString("generic-playtime-title")}: {sel.PlaytimeString}";
}
}
};

View File

@@ -20,7 +20,7 @@ namespace Content.Client.Administration.UI.CustomControls
private List<PlayerInfo> _playerList = new();
private readonly List<PlayerInfo> _sortedPlayerList = new();
public event Action<PlayerInfo?>? OnSelectionChanged;
public event Action<PlayerInfo>? OnSelectionChanged;
public IReadOnlyList<PlayerInfo> PlayerInfo => _playerList;
public Func<PlayerInfo, string, string>? OverrideText;
@@ -41,19 +41,12 @@ namespace Content.Client.Administration.UI.CustomControls
PlayerListContainer.ItemPressed += PlayerListItemPressed;
PlayerListContainer.ItemKeyBindDown += PlayerListItemKeyBindDown;
PlayerListContainer.GenerateItem += GenerateButton;
PlayerListContainer.NoItemSelected += PlayerListNoItemSelected;
PopulateList(_adminSystem.PlayerList);
FilterLineEdit.OnTextChanged += _ => FilterList();
_adminSystem.PlayerListChanged += PopulateList;
BackgroundPanel.PanelOverride = new StyleBoxFlat {BackgroundColor = new Color(32, 32, 40)};
}
private void PlayerListNoItemSelected()
{
_selectedPlayer = null;
OnSelectionChanged?.Invoke(null);
}
private void PlayerListItemPressed(BaseButton.ButtonEventArgs? args, ListData? data)
{
if (args == null || data is not PlayerListData {Info: var selectedPlayer})

View File

@@ -25,7 +25,7 @@ public sealed class ExplosionDebugOverlay : Overlay
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
public Matrix3x2 SpaceMatrix;
public Matrix3 SpaceMatrix;
public MapId Map;
private readonly Font _font;
@@ -78,8 +78,7 @@ public sealed class ExplosionDebugOverlay : Overlay
if (SpaceTiles == null)
return;
Matrix3x2.Invert(SpaceMatrix, out var invSpace);
gridBounds = invSpace.TransformBox(args.WorldBounds);
gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds);
DrawText(handle, gridBounds, SpaceMatrix, SpaceTiles, SpaceTileSize);
}
@@ -87,7 +86,7 @@ public sealed class ExplosionDebugOverlay : Overlay
private void DrawText(
DrawingHandleScreen handle,
Box2 gridBounds,
Matrix3x2 transform,
Matrix3 transform,
Dictionary<int, List<Vector2i>> tileSets,
ushort tileSize)
{
@@ -104,7 +103,7 @@ public sealed class ExplosionDebugOverlay : Overlay
if (!gridBounds.Contains(centre))
continue;
var worldCenter = Vector2.Transform(centre, transform);
var worldCenter = transform.Transform(centre);
var screenCenter = _eyeManager.WorldToScreen(worldCenter);
@@ -120,7 +119,7 @@ public sealed class ExplosionDebugOverlay : Overlay
if (tileSets.TryGetValue(0, out var set))
{
var epicenter = set.First();
var worldCenter = Vector2.Transform((epicenter + Vector2Helpers.Half) * tileSize, transform);
var worldCenter = transform.Transform((epicenter + Vector2Helpers.Half) * tileSize);
var screenCenter = _eyeManager.WorldToScreen(worldCenter) + new Vector2(-24, -24);
var text = $"{Intensity[0]:F2}\nΣ={TotalIntensity:F1}\nΔ={Slope:F1}";
handle.DrawString(_font, screenCenter, text);
@@ -149,12 +148,11 @@ public sealed class ExplosionDebugOverlay : Overlay
if (SpaceTiles == null)
return;
Matrix3x2.Invert(SpaceMatrix, out var invSpace);
gridBounds = invSpace.TransformBox(args.WorldBounds).Enlarged(2);
gridBounds = Matrix3.Invert(SpaceMatrix).TransformBox(args.WorldBounds).Enlarged(2);
handle.SetTransform(SpaceMatrix);
DrawTiles(handle, gridBounds, SpaceTiles, SpaceTileSize);
handle.SetTransform(Matrix3x2.Identity);
handle.SetTransform(Matrix3.Identity);
}
private void DrawTiles(

View File

@@ -3,7 +3,6 @@ using Content.Shared.Explosion;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.Player;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
@@ -23,7 +22,7 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
private readonly SharedTransformSystem _transform = default!;
private readonly SpawnExplosionEui _eui;
private List<MapId> _mapData = new();
@@ -38,7 +37,6 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_transform = _entMan.System<TransformSystem>();
_eui = eui;
ExplosionOption.OnItemSelected += ExplosionSelected;
@@ -106,7 +104,7 @@ public sealed partial class SpawnExplosionWindow : DefaultWindow
_pausePreview = true;
MapOptions.Select(_mapData.IndexOf(transform.MapID));
(MapX.Value, MapY.Value) = _transform.GetMapCoordinates(_playerManager.LocalEntity!.Value, xform: transform).Position;
(MapX.Value, MapY.Value) = transform.MapPosition.Position;
_pausePreview = false;
UpdatePreview();

View File

@@ -1,6 +0,0 @@
<controls:BabyJailStatusWindow
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab"
Title="{Loc admin-ui-baby-jail-window-title}">
<RichTextLabel Name="MessageLabel" Access="Public" />
</controls:BabyJailStatusWindow>

View File

@@ -1,21 +0,0 @@
using Content.Client.Message;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
/*
* TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
[GenerateTypedNameReferences]
public sealed partial class BabyJailStatusWindow : FancyWindow
{
public BabyJailStatusWindow()
{
RobustXamlLoader.Load(this);
MessageLabel.SetMarkup(Loc.GetString("admin-ui-baby-jail-is-enabled"));
}
}

View File

@@ -1,26 +0,0 @@
<controls:BabyJailTab
xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.Administration.UI.Tabs.BabyJailTab"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
Margin="4">
<BoxContainer Orientation="Vertical">
<cc:CommandButton Name="EnabledButton" Command="babyjail" ToggleMode="True"
Text="{Loc admin-ui-baby-jail-disabled}"
ToolTip="{Loc admin-ui-baby-jail-tooltip}" />
<cc:CommandButton Name="ShowReasonButton" Command="babyjail_show_reason"
ToggleMode="True" Text="{Loc admin-ui-baby-jail-show-reason}"
ToolTip="{Loc admin-ui-baby-jail-show-reason-tooltip}" />
<BoxContainer Orientation="Vertical" Margin="0 10 0 0">
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-baby-jail-max-account-age}" MinWidth="175" />
<LineEdit Name="MaxAccountAge" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-baby-jail-max-overall-minutes}" MinWidth="175" />
<LineEdit Name="MaxOverallMinutes" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" />
</BoxContainer>
</BoxContainer>
</BoxContainer>
</controls:BabyJailTab>

View File

@@ -1,75 +0,0 @@
using Content.Shared.Administration.Events;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Console;
/*
* TODO: Remove me once a more mature gateway process is established. This code is only being issued as a stopgap to help with potential tiding in the immediate future.
*/
namespace Content.Client.Administration.UI.Tabs.BabyJailTab;
[GenerateTypedNameReferences]
public sealed partial class BabyJailTab : Control
{
[Dependency] private readonly IConsoleHost _console = default!;
private string _maxAccountAge;
private string _maxOverallMinutes;
public BabyJailTab()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
MaxAccountAge.OnTextEntered += args => SendMaxAccountAge(args.Text);
MaxAccountAge.OnFocusExit += args => SendMaxAccountAge(args.Text);
_maxAccountAge = MaxAccountAge.Text;
MaxOverallMinutes.OnTextEntered += args => SendMaxOverallMinutes(args.Text);
MaxOverallMinutes.OnFocusExit += args => SendMaxOverallMinutes(args.Text);
_maxOverallMinutes = MaxOverallMinutes.Text;
}
private void SendMaxAccountAge(string text)
{
if (string.IsNullOrWhiteSpace(text) ||
text == _maxAccountAge ||
!int.TryParse(text, out var minutes))
{
return;
}
_console.ExecuteCommand($"babyjail_max_account_age {minutes}");
}
private void SendMaxOverallMinutes(string text)
{
if (string.IsNullOrWhiteSpace(text) ||
text == _maxOverallMinutes ||
!int.TryParse(text, out var minutes))
{
return;
}
_console.ExecuteCommand($"babyjail_max_overall_minutes {minutes}");
}
public void UpdateStatus(BabyJailStatus status)
{
EnabledButton.Pressed = status.Enabled;
EnabledButton.Text = Loc.GetString(status.Enabled
? "admin-ui-baby-jail-enabled"
: "admin-ui-baby-jail-disabled"
);
EnabledButton.ModulateSelfOverride = status.Enabled ? Color.Red : null;
ShowReasonButton.Pressed = status.ShowReason;
MaxAccountAge.Text = status.MaxAccountAgeMinutes.ToString();
_maxAccountAge = MaxAccountAge.Text;
MaxOverallMinutes.Text = status.MaxOverallMinutes.ToString();
_maxOverallMinutes = MaxOverallMinutes.Text;
}
}

View File

@@ -1,21 +1,15 @@
<Control xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:ot="clr-namespace:Content.Client.Administration.UI.Tabs.ObjectsTab"
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
Text="{Loc Object type:}" />
<LineEdit Name="SearchLineEdit" PlaceHolder="{Loc Search...}" HorizontalExpand="True" SizeFlagsStretchRatio="1"/>
<OptionButton Name="ObjectTypeOptions" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"/>
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
</BoxContainer>
<cc:HSeparator/>
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<ot:ObjectsTabHeader Name="ListHeader"/>
<cc:HSeparator/>
<co:SearchListContainer Name="SearchList" Access="Public" VerticalExpand="True"/>
</BoxContainer>
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Name="ObjectList">
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</Control>

View File

@@ -1,7 +1,5 @@
using Content.Client.Station;
using Content.Client.UserInterface.Controls;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Map.Components;
@@ -12,20 +10,20 @@ namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTab : Control
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly List<ObjectsTabEntry> _objects = new();
private readonly List<ObjectsTabSelection> _selections = new();
private bool _ascending = false; // Set to false for descending order by default
private ObjectsTabHeader.Header _headerClicked = ObjectsTabHeader.Header.ObjectName;
private readonly Color _altColor = Color.FromHex("#292B38");
private readonly Color _defaultColor = Color.FromHex("#2F2F3B");
private List<ObjectsTabSelection> _selections = new();
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
public event Action<ObjectsTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
private readonly TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
private TimeSpan _nextUpdate;
// Listen I could either have like 4 different event subscribers (for map / grid / station changes) and manage their lifetimes in AdminUIController
// OR
// I can do this.
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(2);
private TimeSpan _nextUpdate = TimeSpan.FromSeconds(2);
public ObjectsTab()
{
@@ -44,30 +42,6 @@ public sealed partial class ObjectsTab : Control
ObjectTypeOptions.AddItem(Enum.GetName((ObjectsTabSelection)type)!);
}
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
RefreshObjectList();
// Set initial selection and refresh the list to apply the initial sort order
var defaultSelection = ObjectsTabSelection.Grids;
ObjectTypeOptions.SelectId((int)defaultSelection); // Set the default selection
RefreshObjectList(defaultSelection); // Refresh the list with the default selection
// Initialize the next update time
_nextUpdate = TimeSpan.Zero;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_timing.CurTime < _nextUpdate)
return;
_nextUpdate = _timing.CurTime + _updateFrequency;
RefreshObjectList();
}
@@ -107,70 +81,32 @@ public sealed partial class ObjectsTab : Control
throw new ArgumentOutOfRangeException(nameof(selection), selection, null);
}
entities.Sort((a, b) =>
foreach (var control in _objects)
{
var valueA = GetComparableValue(a, _headerClicked);
var valueB = GetComparableValue(b, _headerClicked);
return _ascending ? Comparer<object>.Default.Compare(valueA, valueB) : Comparer<object>.Default.Compare(valueB, valueA);
});
var listData = new List<ObjectsListData>();
for (int index = 0; index < entities.Count; index++)
{
var info = entities[index];
listData.Add(new ObjectsListData(info, $"{info.Name} {info.Entity}", index % 2 == 0 ? _altColor : _defaultColor));
ObjectList.RemoveChild(control);
}
SearchList.PopulateList(listData);
_objects.Clear();
foreach (var (name, nent) in entities)
{
var ctrl = new ObjectsTabEntry(name, nent);
_objects.Add(ctrl);
ObjectList.AddChild(ctrl);
ctrl.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(ctrl, args);
}
}
private void GenerateButton(ListData data, ListContainerButton button)
protected override void FrameUpdate(FrameEventArgs args)
{
if (data is not ObjectsListData { Info: var info, BackgroundColor: var backgroundColor })
base.FrameUpdate(args);
if (_timing.CurTime < _nextUpdate)
return;
var entry = new ObjectsTabEntry(info.Name, info.Entity, new StyleBoxFlat { BackgroundColor = backgroundColor });
button.ToolTip = $"{info.Name}, {info.Entity}";
// I do not care for precision.
_nextUpdate = _timing.CurTime + _updateFrequency;
button.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(args, data);
button.AddChild(entry);
}
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not ObjectsListData { FilteringString: var filteringString })
return false;
// If the filter is empty, do not filter out any entries
if (string.IsNullOrEmpty(filter))
return true;
return filteringString.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
}
private object GetComparableValue((string Name, NetEntity Entity) entity, ObjectsTabHeader.Header header)
{
return header switch
{
ObjectsTabHeader.Header.ObjectName => entity.Name,
ObjectsTabHeader.Header.EntityID => entity.Entity.ToString(),
_ => entity.Name
};
}
private void HeaderClicked(ObjectsTabHeader.Header header)
{
if (_headerClicked == header)
{
_ascending = !_ascending;
}
else
{
_headerClicked = header;
_ascending = true;
}
ListHeader.UpdateHeaderSymbols(_headerClicked, _ascending);
RefreshObjectList();
}
@@ -182,4 +118,3 @@ public sealed partial class ObjectsTab : Control
}
}
public record ObjectsListData((string Name, NetEntity Entity) Info, string FilteringString, Color BackgroundColor) : ListData;

View File

@@ -1,6 +1,6 @@
<PanelContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Name="BackgroundColorPanel">
<ContainerButton xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
<PanelContainer Name="BackgroundColorPanel"/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
@@ -14,4 +14,4 @@
HorizontalExpand="True"
ClipText="True"/>
</BoxContainer>
</PanelContainer>
</ContainerButton>

View File

@@ -1,21 +1,19 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab;
[GenerateTypedNameReferences]
public sealed partial class ObjectsTabEntry : PanelContainer
public sealed partial class ObjectsTabEntry : ContainerButton
{
public NetEntity AssocEntity;
public ObjectsTabEntry(string name, NetEntity nent, StyleBox styleBox)
public ObjectsTabEntry(string name, NetEntity nent)
{
RobustXamlLoader.Load(this);
AssocEntity = nent;
EIDLabel.Text = nent.ToString();
NameLabel.Text = name;
BackgroundColorPanel.PanelOverride = styleBox;
}
}

View File

@@ -1,21 +0,0 @@
<Control xmlns="https://spacestation14.io"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
<PanelContainer Name="BackgroundColorPanel" Access="Public"/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
<Label Name="ObjectNameLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"
Text="{Loc object-tab-object-name}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="EntityIDLabel"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"
Text="{Loc object-tab-entity-id}"
MouseFilter="Pass"/>
</BoxContainer>
</Control>

View File

@@ -1,86 +0,0 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Input;
namespace Content.Client.Administration.UI.Tabs.ObjectsTab
{
[GenerateTypedNameReferences]
public sealed partial class ObjectsTabHeader : Control
{
public event Action<Header>? OnHeaderClicked;
private const string ArrowUp = "↑";
private const string ArrowDown = "↓";
public ObjectsTabHeader()
{
RobustXamlLoader.Load(this);
ObjectNameLabel.OnKeyBindDown += ObjectNameClicked;
EntityIDLabel.OnKeyBindDown += EntityIDClicked;
}
public Label GetHeader(Header header)
{
return header switch
{
Header.ObjectName => ObjectNameLabel,
Header.EntityID => EntityIDLabel,
_ => throw new ArgumentOutOfRangeException(nameof(header), header, null)
};
}
public void ResetHeaderText()
{
ObjectNameLabel.Text = Loc.GetString("object-tab-object-name");
EntityIDLabel.Text = Loc.GetString("object-tab-entity-id");
}
public void UpdateHeaderSymbols(Header headerClicked, bool ascending)
{
ResetHeaderText();
var arrow = ascending ? ArrowUp : ArrowDown;
GetHeader(headerClicked).Text += $" {arrow}";
}
private void HeaderClicked(GUIBoundKeyEventArgs args, Header header)
{
if (args.Function != EngineKeyFunctions.UIClick)
{
return;
}
OnHeaderClicked?.Invoke(header);
args.Handle();
}
private void ObjectNameClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.ObjectName);
}
private void EntityIDClicked(GUIBoundKeyEventArgs args)
{
HeaderClicked(args, Header.EntityID);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
ObjectNameLabel.OnKeyBindDown -= ObjectNameClicked;
EntityIDLabel.OnKeyBindDown -= EntityIDClicked;
}
}
public enum Header
{
ObjectName,
EntityID
}
}
}

View File

@@ -31,12 +31,12 @@
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-panic-bunker-min-account-age}" MinWidth="175" />
<LineEdit Name="MinAccountAge" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" />
<Label Text="{Loc generic-hours}" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" Margin="2">
<Label Text="{Loc admin-ui-panic-bunker-min-overall-minutes}" MinWidth="175" />
<LineEdit Name="MinOverallMinutes" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-minutes}" />
<Label Text="{Loc admin-ui-panic-bunker-min-overall-hours}" MinWidth="175" />
<LineEdit Name="MinOverallHours" MinWidth="50" Margin="0 0 5 0" />
<Label Text="{Loc generic-hours}" />
</BoxContainer>
</BoxContainer>
</BoxContainer>

View File

@@ -12,7 +12,7 @@ public sealed partial class PanicBunkerTab : Control
[Dependency] private readonly IConsoleHost _console = default!;
private string _minAccountAge;
private string _minOverallMinutes;
private string _minOverallHours;
public PanicBunkerTab()
{
@@ -25,9 +25,9 @@ public sealed partial class PanicBunkerTab : Control
MinAccountAge.OnFocusExit += args => SendMinAccountAge(args.Text);
_minAccountAge = MinAccountAge.Text;
MinOverallMinutes.OnTextEntered += args => SendMinOverallMinutes(args.Text);
MinOverallMinutes.OnFocusExit += args => SendMinOverallMinutes(args.Text);
_minOverallMinutes = MinOverallMinutes.Text;
MinOverallHours.OnTextEntered += args => SendMinOverallHours(args.Text);
MinOverallHours.OnFocusExit += args => SendMinOverallHours(args.Text);
_minOverallHours = MinOverallHours.Text;
}
private void SendMinAccountAge(string text)
@@ -42,16 +42,16 @@ public sealed partial class PanicBunkerTab : Control
_console.ExecuteCommand($"panicbunker_min_account_age {minutes}");
}
private void SendMinOverallMinutes(string text)
private void SendMinOverallHours(string text)
{
if (string.IsNullOrWhiteSpace(text) ||
text == _minOverallMinutes ||
!int.TryParse(text, out var minutes))
text == _minOverallHours ||
!int.TryParse(text, out var hours))
{
return;
}
_console.ExecuteCommand($"panicbunker_min_overall_minutes {minutes}");
_console.ExecuteCommand($"panicbunker_min_overall_hours {hours}");
}
public void UpdateStatus(PanicBunkerStatus status)
@@ -68,10 +68,10 @@ public sealed partial class PanicBunkerTab : Control
CountDeadminnedButton.Pressed = status.CountDeadminnedAdmins;
ShowReasonButton.Pressed = status.ShowReason;
MinAccountAge.Text = status.MinAccountAgeMinutes.ToString();
MinAccountAge.Text = status.MinAccountAgeHours.ToString();
_minAccountAge = MinAccountAge.Text;
MinOverallMinutes.Text = status.MinOverallMinutes.ToString();
_minOverallMinutes = MinOverallMinutes.Text;
MinOverallHours.Text = status.MinOverallHours.ToString();
_minOverallHours = MinOverallHours.Text;
}
}

View File

@@ -1,19 +1,21 @@
<Control xmlns="https://spacestation14.io"
xmlns:pt="clr-namespace:Content.Client.Administration.UI.Tabs.PlayerTab"
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Name="PlayerCount" HorizontalExpand="True" Text="{Loc Player Count}" />
<LineEdit Name="SearchLineEdit" HorizontalExpand="True"
PlaceHolder="{Loc player-tab-filter-line-edit-placeholder}" />
<Button Name="ShowDisconnectedButton" HorizontalExpand="True"
Text="{Loc player-tab-show-disconnected}" ToggleMode="True" />
<Button Name="OverlayButton" HorizontalExpand="True" Text="{Loc player-tab-overlay}" ToggleMode="True" />
<Label Name="PlayerCount" HorizontalExpand="True" SizeFlagsStretchRatio="0.50"
Text="{Loc Player Count}" />
<Button Name="ShowDisconnectedButton" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"
Text="{Loc player-tab-show-disconnected}" ToggleMode="True"/>
<Button Name="OverlayButton" HorizontalExpand="True" SizeFlagsStretchRatio="0.25"
Text="{Loc player-tab-overlay}" ToggleMode="True"/>
</BoxContainer>
<Control MinSize="0 5"/>
<pt:PlayerTabHeader Name="ListHeader"/>
<cc:HSeparator/>
<co:SearchListContainer Name="SearchList" Access="Public" VerticalExpand="True"/>
<Control MinSize="0 5" />
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical" Name="PlayerList">
<pt:PlayerTabHeader Name="ListHeader" />
<cc:HSeparator />
</BoxContainer>
</ScrollContainer>
</BoxContainer>
</Control>

View File

@@ -1,6 +1,5 @@
using System.Linq;
using Content.Client.Administration.Systems;
using Content.Client.UserInterface.Controls;
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
@@ -29,14 +28,15 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
private bool _ascending = true;
private bool _showDisconnected;
public event Action<GUIBoundKeyEventArgs, ListData>? OnEntryKeyBindDown;
public event Action<PlayerTabEntry, GUIBoundKeyEventArgs>? OnEntryKeyBindDown;
public PlayerTab()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_adminSystem = _entManager.System<AdminSystem>();
RobustXamlLoader.Load(this);
RefreshPlayerList(_adminSystem.PlayerList);
_adminSystem.PlayerListChanged += RefreshPlayerList;
_adminSystem.OverlayEnabled += OverlayEnabled;
_adminSystem.OverlayDisabled += OverlayDisabled;
@@ -46,18 +46,8 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
ListHeader.BackgroundColorPanel.PanelOverride = new StyleBoxFlat(_altColor);
ListHeader.OnHeaderClicked += HeaderClicked;
SearchList.SearchBar = SearchLineEdit;
SearchList.GenerateItem += GenerateButton;
SearchList.DataFilterCondition += DataFilterCondition;
SearchList.ItemKeyBindDown += (args, data) => OnEntryKeyBindDown?.Invoke(args, data);
RefreshPlayerList(_adminSystem.PlayerList);
}
#region Antag Overlay
private void OverlayEnabled()
{
OverlayButton.Pressed = true;
@@ -80,8 +70,6 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
}
}
#endregion
private void ShowDisconnectedPressed(ButtonEventArgs args)
{
_showDisconnected = args.Button.Pressed;
@@ -104,80 +92,45 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
}
}
#region ListContainer
private void RefreshPlayerList(IReadOnlyList<PlayerInfo> players)
{
foreach (var child in PlayerList.Children.ToArray())
{
if (child is PlayerTabEntry)
child.Dispose();
}
_players = players;
PlayerCount.Text = $"Players: {_playerMan.PlayerCount}";
var filteredPlayers = players.Where(info => _showDisconnected || info.Connected).ToList();
var sortedPlayers = new List<PlayerInfo>(filteredPlayers);
var sortedPlayers = new List<PlayerInfo>(players);
sortedPlayers.Sort(Compare);
UpdateHeaderSymbols();
SearchList.PopulateList(sortedPlayers.Select(info => new PlayerListData(info,
$"{info.Username} {info.CharacterName} {info.IdentityName} {info.StartingJob}"))
.ToList());
}
private void GenerateButton(ListData data, ListContainerButton button)
{
if (data is not PlayerListData { Info: var player})
return;
var entry = new PlayerTabEntry(player, new StyleBoxFlat(button.Index % 2 == 0 ? _altColor : _defaultColor));
button.AddChild(entry);
button.ToolTip = $"{player.Username}, {player.CharacterName}, {player.IdentityName}, {player.StartingJob}";
}
/// <summary>
/// Determines whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.
/// If all characters are lowercase, the comparison ignores case.
/// If there is an uppercase character, the comparison is case sensitive.
/// </summary>
/// <param name="filter"></param>
/// <param name="listData"></param>
/// <returns>Whether <paramref name="filter"/> is contained in <paramref name="listData"/>.FilteringString.</returns>
private bool DataFilterCondition(string filter, ListData listData)
{
if (listData is not PlayerListData {Info: var info, FilteringString: var playerString})
return false;
if (!_showDisconnected && !info.Connected)
return false;
if (IsAllLower(filter))
var useAltColor = false;
foreach (var player in sortedPlayers)
{
if (!playerString.Contains(filter, StringComparison.CurrentCultureIgnoreCase))
return false;
}
else
{
if (!playerString.Contains(filter))
return false;
}
if (!_showDisconnected && !player.Connected)
continue;
return true;
var entry = new PlayerTabEntry(player.Username,
player.CharacterName,
player.IdentityName,
player.StartingJob,
player.Antag ? "YES" : "NO",
new StyleBoxFlat(useAltColor ? _altColor : _defaultColor),
player.Connected,
player.PlaytimeString);
entry.PlayerEntity = player.NetEntity;
entry.OnKeyBindDown += args => OnEntryKeyBindDown?.Invoke(entry, args);
entry.ToolTip = Loc.GetString("player-tab-entry-tooltip");
PlayerList.AddChild(entry);
useAltColor ^= true;
}
}
private bool IsAllLower(string input)
{
foreach (var c in input)
{
if (char.IsLetter(c) && !char.IsLower(c))
return false;
}
return true;
}
#endregion
#region Header
private void UpdateHeaderSymbols()
{
ListHeader.ResetHeaderText();
@@ -221,9 +174,5 @@ namespace Content.Client.Administration.UI.Tabs.PlayerTab
RefreshPlayerList(_adminSystem.PlayerList);
}
#endregion
}
public record PlayerListData(PlayerInfo Info, string FilteringString) : ListData;
}

View File

@@ -1,6 +1,6 @@
<PanelContainer xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
Name="BackgroundColorPanel">
<ContainerButton xmlns="https://spacestation14.io"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls">
<PanelContainer Name="BackgroundColorPanel"/>
<BoxContainer Orientation="Horizontal"
HorizontalExpand="True"
SeparationOverride="4">
@@ -15,18 +15,17 @@
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="JobLabel"
SizeFlagsStretchRatio="2"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="AntagonistLabel"
SizeFlagsStretchRatio="1"
SizeFlagsStretchRatio="2"
HorizontalExpand="True"
ClipText="True"/>
<customControls:VSeparator/>
<Label Name="OverallPlaytimeLabel"
SizeFlagsStretchRatio="1"
SizeFlagsStretchRatio="2"
HorizontalExpand="True"
ClipText="True"/>
</BoxContainer>
</PanelContainer>
</ContainerButton>

View File

@@ -1,5 +1,4 @@
using Content.Shared.Administration;
using Robust.Client.AutoGenerated;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -7,24 +6,23 @@ using Robust.Client.UserInterface.XAML;
namespace Content.Client.Administration.UI.Tabs.PlayerTab;
[GenerateTypedNameReferences]
public sealed partial class PlayerTabEntry : PanelContainer
public sealed partial class PlayerTabEntry : ContainerButton
{
public NetEntity? PlayerEntity;
public PlayerTabEntry(PlayerInfo player, StyleBoxFlat styleBoxFlat)
public PlayerTabEntry(string username, string character, string identity, string job, string antagonist, StyleBox styleBox, bool connected, string overallPlaytime)
{
RobustXamlLoader.Load(this);
UsernameLabel.Text = player.Username;
if (!player.Connected)
UsernameLabel.Text = username;
if (!connected)
UsernameLabel.StyleClasses.Add("Disabled");
JobLabel.Text = player.StartingJob;
CharacterLabel.Text = player.CharacterName;
if (player.IdentityName != player.CharacterName)
CharacterLabel.Text += $" [{player.IdentityName}]";
AntagonistLabel.Text = Loc.GetString(player.Antag ? "player-tab-is-antag-yes" : "player-tab-is-antag-no");
BackgroundColorPanel.PanelOverride = styleBoxFlat;
OverallPlaytimeLabel.Text = player.PlaytimeString;
PlayerEntity = player.NetEntity;
JobLabel.Text = job;
CharacterLabel.Text = character;
if (identity != character)
CharacterLabel.Text += $" [{identity}]";
AntagonistLabel.Text = antagonist;
BackgroundColorPanel.PanelOverride = styleBox;
OverallPlaytimeLabel.Text = overallPlaytime;
}
}

View File

@@ -19,25 +19,23 @@
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="JobLabel"
SizeFlagsStretchRatio="2"
SizeFlagsStretchRatio="3"
HorizontalExpand="True"
ClipText="True"
Text="{Loc player-tab-job}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="AntagonistLabel"
SizeFlagsStretchRatio="1"
SizeFlagsStretchRatio="2"
HorizontalExpand="True"
ClipText="True"
Text="{Loc player-tab-antagonist}"
MouseFilter="Pass"/>
<cc:VSeparator/>
<Label Name="PlaytimeLabel"
SizeFlagsStretchRatio="1"
SizeFlagsStretchRatio="2"
HorizontalExpand="True"
ClipText="True"
Text="{Loc player-tab-playtime}"
MouseFilter="Pass"
ToolTip="{Loc player-tab-entry-tooltip}"/>
MouseFilter="Pass"/>
</BoxContainer>
</Control>

View File

@@ -91,7 +91,7 @@ public sealed class ClientAlertsSystem : AlertsSystem
ClearAlerts?.Invoke(this, EventArgs.Empty);
}
public void AlertClicked(ProtoId<AlertPrototype> alertType)
public void AlertClicked(AlertType alertType)
{
RaiseNetworkEvent(new ClickAlertEvent(alertType));
}

View File

@@ -1,17 +0,0 @@
using System.Numerics;
namespace Content.Client.Animations;
/// <summary>
/// Entities with this component tracks the user's world position every frame.
/// </summary>
[RegisterComponent]
public sealed partial class TrackUserComponent : Component
{
public EntityUid? User;
/// <summary>
/// Offset in the direction of the entity's rotation.
/// </summary>
public Vector2 Offset = Vector2.Zero;
}

View File

@@ -0,0 +1,55 @@
using Content.Shared.Antag;
using Content.Shared.Revolutionary.Components;
using Content.Shared.StatusIcon;
using Content.Shared.StatusIcon.Components;
using Content.Shared.Zombies;
using Robust.Client.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Antag;
/// <summary>
/// Used for assigning specified icons for antags.
/// </summary>
public sealed class AntagStatusIconSystem : SharedStatusIconSystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IPlayerManager _player = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RevolutionaryComponent, GetStatusIconsEvent>(GetRevIcon);
SubscribeLocalEvent<ZombieComponent, GetStatusIconsEvent>(GetIcon);
SubscribeLocalEvent<HeadRevolutionaryComponent, GetStatusIconsEvent>(GetIcon);
SubscribeLocalEvent<InitialInfectedComponent, GetStatusIconsEvent>(GetIcon);
}
/// <summary>
/// Adds a Status Icon on an entity if the player is supposed to see it.
/// </summary>
private void GetIcon<T>(EntityUid uid, T comp, ref GetStatusIconsEvent ev) where T: IAntagStatusIconComponent
{
var ent = _player.LocalSession?.AttachedEntity;
var canEv = new CanDisplayStatusIconsEvent(ent);
RaiseLocalEvent(uid, ref canEv);
if (!canEv.Cancelled)
ev.StatusIcons.Add(_prototype.Index(comp.StatusIcon));
}
/// <summary>
/// Adds the Rev Icon on an entity if the player is supposed to see it. This additional function is needed to deal
/// with a special case where if someone is a head rev we only want to display the headrev icon.
/// </summary>
private void GetRevIcon(EntityUid uid, RevolutionaryComponent comp, ref GetStatusIconsEvent ev)
{
if (HasComp<HeadRevolutionaryComponent>(uid))
return;
GetIcon(uid, comp, ref ev);
}
}

View File

@@ -32,7 +32,7 @@ public sealed partial class FireVisualsComponent : Component
public float MaxLightRadius = 4f;
[DataField("lightColor")]
public Color LightColor = Color.FromSrgb( new Color(239, 181, 20)); //CP14 fire color in art style pallette
public Color LightColor = Color.Orange;
/// <summary>
/// Client side point-light entity. We use this instead of directly adding a light to

View File

@@ -1,5 +1,4 @@
using Content.Client.Atmos.Overlays;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.EntitySystems;
using JetBrains.Annotations;
@@ -37,38 +36,28 @@ namespace Content.Client.Atmos.EntitySystems
private void OnHandleState(EntityUid gridUid, GasTileOverlayComponent comp, ref ComponentHandleState args)
{
Dictionary<Vector2i, GasOverlayChunk> modifiedChunks;
if (args.Current is not GasTileOverlayState state)
return;
switch (args.Current)
// is this a delta or full state?
if (!state.FullState)
{
// is this a delta or full state?
case GasTileOverlayDeltaState delta:
foreach (var index in comp.Chunks.Keys)
{
modifiedChunks = delta.ModifiedChunks;
foreach (var index in comp.Chunks.Keys)
{
if (!delta.AllChunks.Contains(index))
comp.Chunks.Remove(index);
}
break;
if (!state.AllChunks!.Contains(index))
comp.Chunks.Remove(index);
}
case GasTileOverlayState state:
}
else
{
foreach (var index in comp.Chunks.Keys)
{
modifiedChunks = state.Chunks;
foreach (var index in comp.Chunks.Keys)
{
if (!state.Chunks.ContainsKey(index))
comp.Chunks.Remove(index);
}
break;
if (!state.Chunks.ContainsKey(index))
comp.Chunks.Remove(index);
}
default:
return;
}
foreach (var (index, data) in modifiedChunks)
foreach (var (index, data) in state.Chunks)
{
comp.Chunks[index] = data;
}

View File

@@ -66,7 +66,7 @@ public sealed class AtmosDebugOverlay : Overlay
DrawData(msg, handle);
}
handle.SetTransform(Matrix3x2.Identity);
handle.SetTransform(Matrix3.Identity);
}
private void DrawData(DebugMessage msg,

View File

@@ -190,7 +190,7 @@ namespace Content.Client.Atmos.Overlays
var (_, _, worldMatrix, invMatrix) = gridXform.GetWorldPositionRotationMatrixWithInv();
state.drawHandle.SetTransform(worldMatrix);
var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
var floatBounds = invMatrix.TransformBox(in state.WorldBounds).Enlarged(grid.TileSize);
var localBounds = new Box2i(
(int) MathF.Floor(floatBounds.Left),
(int) MathF.Floor(floatBounds.Bottom),
@@ -249,7 +249,7 @@ namespace Content.Client.Atmos.Overlays
});
drawHandle.UseShader(null);
drawHandle.SetTransform(Matrix3x2.Identity);
drawHandle.SetTransform(Matrix3.Identity);
}
private void DrawMapOverlay(

View File

@@ -163,26 +163,6 @@ namespace Content.Client.Atmos.UI
parent.AddChild(panel);
panel.AddChild(dataContainer);
// Volume label
var volBox = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal };
volBox.AddChild(new Label
{
Text = Loc.GetString("gas-analyzer-window-volume-text")
});
volBox.AddChild(new Control
{
MinSize = new Vector2(10, 0),
HorizontalExpand = true
});
volBox.AddChild(new Label
{
Text = Loc.GetString("gas-analyzer-window-volume-val-text", ("volume", $"{gasMix.Volume:0.##}")),
Align = Label.AlignMode.Right,
HorizontalExpand = true
});
dataContainer.AddChild(volBox);
// Pressure label
var presBox = new BoxContainer { Orientation = BoxContainer.LayoutOrientation.Horizontal };

View File

@@ -1,118 +0,0 @@
using Content.Shared.Audio.Jukebox;
using Robust.Client.Audio;
using Robust.Client.Player;
using Robust.Shared.Audio.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Audio.Jukebox;
public sealed class JukeboxBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[ViewVariables]
private JukeboxMenu? _menu;
public JukeboxBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
protected override void Open()
{
base.Open();
_menu = new JukeboxMenu();
_menu.OnClose += Close;
_menu.OpenCentered();
_menu.OnPlayPressed += args =>
{
if (args)
{
SendMessage(new JukeboxPlayingMessage());
}
else
{
SendMessage(new JukeboxPauseMessage());
}
};
_menu.OnStopPressed += () =>
{
SendMessage(new JukeboxStopMessage());
};
_menu.OnSongSelected += SelectSong;
_menu.SetTime += SetTime;
PopulateMusic();
Reload();
}
/// <summary>
/// Reloads the attached menu if it exists.
/// </summary>
public void Reload()
{
if (_menu == null || !EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox))
return;
_menu.SetAudioStream(jukebox.AudioStream);
if (_protoManager.TryIndex(jukebox.SelectedSongId, out var songProto))
{
var length = EntMan.System<AudioSystem>().GetAudioLength(songProto.Path.Path.ToString());
_menu.SetSelectedSong(songProto.Name, (float) length.TotalSeconds);
}
else
{
_menu.SetSelectedSong(string.Empty, 0f);
}
}
public void PopulateMusic()
{
_menu?.Populate(_protoManager.EnumeratePrototypes<JukeboxPrototype>());
}
public void SelectSong(ProtoId<JukeboxPrototype> songid)
{
SendMessage(new JukeboxSelectedMessage(songid));
}
public void SetTime(float time)
{
var sentTime = time;
// You may be wondering, what the fuck is this
// Well we want to be able to predict the playback slider change, of which there are many ways to do it
// We can't just use SendPredictedMessage because it will reset every tick and audio updates every frame
// so it will go BRRRRT
// Using ping gets us close enough that it SHOULD, MOST OF THE TIME, fall within the 0.1 second tolerance
// that's still on engine so our playback position never gets corrected.
if (EntMan.TryGetComponent(Owner, out JukeboxComponent? jukebox) &&
EntMan.TryGetComponent(jukebox.AudioStream, out AudioComponent? audioComp))
{
audioComp.PlaybackPosition = time;
}
SendMessage(new JukeboxSetTimeMessage(sentTime));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
if (_menu == null)
return;
_menu.OnClose -= Close;
_menu.Dispose();
_menu = null;
}
}

View File

@@ -1,18 +0,0 @@
<ui:FancyWindow xmlns="https://spacestation14.io" xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
SetSize="400 500" Title="{Loc 'jukebox-menu-title'}">
<BoxContainer Margin="4 0" Orientation="Vertical">
<ItemList Name="MusicList" SelectMode="Button" Margin="3 3 3 3"
HorizontalExpand="True" VerticalExpand="True" SizeFlagsStretchRatio="8"/>
<BoxContainer Orientation="Vertical">
<Label Name="SongSelected" Text="{Loc 'jukebox-menu-selectedsong'}" />
<Label Name="SongName" Text="---" />
<Slider Name="PlaybackSlider" HorizontalExpand="True" />
</BoxContainer>
<BoxContainer Orientation="Horizontal" HorizontalExpand="True"
VerticalExpand="False" SizeFlagsStretchRatio="1">
<Button Name="PlayButton" Text="{Loc 'jukebox-menu-buttonplay'}" />
<Button Name="StopButton" Text="{Loc 'jukebox-menu-buttonstop'}" />
<Label Name="DurationLabel" Text="00:00 / 00:00" HorizontalAlignment="Right" HorizontalExpand="True"/>
</BoxContainer>
</BoxContainer>
</ui:FancyWindow>

View File

@@ -1,166 +0,0 @@
using Content.Shared.Audio.Jukebox;
using Robust.Client.Audio;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Audio.Components;
using Robust.Shared.Input;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using FancyWindow = Content.Client.UserInterface.Controls.FancyWindow;
namespace Content.Client.Audio.Jukebox;
[GenerateTypedNameReferences]
public sealed partial class JukeboxMenu : FancyWindow
{
[Dependency] private readonly IEntityManager _entManager = default!;
private AudioSystem _audioSystem;
/// <summary>
/// Are we currently 'playing' or paused for the play / pause button.
/// </summary>
private bool _playState;
/// <summary>
/// True if playing, false if paused.
/// </summary>
public event Action<bool>? OnPlayPressed;
public event Action? OnStopPressed;
public event Action<ProtoId<JukeboxPrototype>>? OnSongSelected;
public event Action<float>? SetTime;
private EntityUid? _audio;
private float _lockTimer;
public JukeboxMenu()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_audioSystem = _entManager.System<AudioSystem>();
MusicList.OnItemSelected += args =>
{
var entry = MusicList[args.ItemIndex];
if (entry.Metadata is not string juke)
return;
OnSongSelected?.Invoke(juke);
};
PlayButton.OnPressed += args =>
{
OnPlayPressed?.Invoke(!_playState);
};
StopButton.OnPressed += args =>
{
OnStopPressed?.Invoke();
};
PlaybackSlider.OnReleased += PlaybackSliderKeyUp;
SetPlayPauseButton(_audioSystem.IsPlaying(_audio), force: true);
}
public JukeboxMenu(AudioSystem audioSystem)
{
_audioSystem = audioSystem;
}
public void SetAudioStream(EntityUid? audio)
{
_audio = audio;
}
private void PlaybackSliderKeyUp(Slider args)
{
SetTime?.Invoke(PlaybackSlider.Value);
_lockTimer = 0.5f;
}
/// <summary>
/// Re-populates the list of jukebox prototypes available.
/// </summary>
public void Populate(IEnumerable<JukeboxPrototype> jukeboxProtos)
{
MusicList.Clear();
foreach (var entry in jukeboxProtos)
{
MusicList.AddItem(entry.Name, metadata: entry.ID);
}
}
public void SetPlayPauseButton(bool playing, bool force = false)
{
if (_playState == playing && !force)
return;
_playState = playing;
if (playing)
{
PlayButton.Text = Loc.GetString("jukebox-menu-buttonpause");
return;
}
PlayButton.Text = Loc.GetString("jukebox-menu-buttonplay");
}
public void SetSelectedSong(string name, float length)
{
SetSelectedSongText(name);
PlaybackSlider.MaxValue = length;
PlaybackSlider.SetValueWithoutEvent(0);
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
if (_lockTimer > 0f)
{
_lockTimer -= args.DeltaSeconds;
}
PlaybackSlider.Disabled = _lockTimer > 0f;
if (_entManager.TryGetComponent(_audio, out AudioComponent? audio))
{
DurationLabel.Text = $@"{TimeSpan.FromSeconds(audio.PlaybackPosition):mm\:ss} / {_audioSystem.GetAudioLength(audio.FileName):mm\:ss}";
}
else
{
DurationLabel.Text = $"00:00 / 00:00";
}
if (PlaybackSlider.Grabbed)
return;
if (audio != null || _entManager.TryGetComponent(_audio, out audio))
{
PlaybackSlider.SetValueWithoutEvent(audio.PlaybackPosition);
}
else
{
PlaybackSlider.SetValueWithoutEvent(0f);
}
SetPlayPauseButton(_audioSystem.IsPlaying(_audio, audio));
}
public void SetSelectedSongText(string? text)
{
if (!string.IsNullOrEmpty(text))
{
SongName.Text = text;
}
else
{
SongName.Text = "---";
}
}
}

View File

@@ -1,145 +0,0 @@
using Content.Shared.Audio.Jukebox;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.Client.Audio.Jukebox;
public sealed class JukeboxSystem : SharedJukeboxSystem
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<JukeboxComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<JukeboxComponent, AnimationCompletedEvent>(OnAnimationCompleted);
SubscribeLocalEvent<JukeboxComponent, AfterAutoHandleStateEvent>(OnJukeboxAfterState);
_protoManager.PrototypesReloaded += OnProtoReload;
}
public override void Shutdown()
{
base.Shutdown();
_protoManager.PrototypesReloaded -= OnProtoReload;
}
private void OnProtoReload(PrototypesReloadedEventArgs obj)
{
if (!obj.WasModified<JukeboxPrototype>())
return;
var query = AllEntityQuery<JukeboxComponent, UserInterfaceComponent>();
while (query.MoveNext(out var uid, out _, out var ui))
{
if (!_uiSystem.TryGetOpenUi<JukeboxBoundUserInterface>((uid, ui), JukeboxUiKey.Key, out var bui))
continue;
bui.PopulateMusic();
}
}
private void OnJukeboxAfterState(Entity<JukeboxComponent> ent, ref AfterAutoHandleStateEvent args)
{
if (!_uiSystem.TryGetOpenUi<JukeboxBoundUserInterface>(ent.Owner, JukeboxUiKey.Key, out var bui))
return;
bui.Reload();
}
private void OnAnimationCompleted(EntityUid uid, JukeboxComponent component, AnimationCompletedEvent args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
if (!TryComp<AppearanceComponent>(uid, out var appearance) ||
!_appearanceSystem.TryGetData<JukeboxVisualState>(uid, JukeboxVisuals.VisualState, out var visualState, appearance))
{
visualState = JukeboxVisualState.On;
}
UpdateAppearance(uid, visualState, component, sprite);
}
private void OnAppearanceChange(EntityUid uid, JukeboxComponent component, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (!args.AppearanceData.TryGetValue(JukeboxVisuals.VisualState, out var visualStateObject) ||
visualStateObject is not JukeboxVisualState visualState)
{
visualState = JukeboxVisualState.On;
}
UpdateAppearance(uid, visualState, component, args.Sprite);
}
private void UpdateAppearance(EntityUid uid, JukeboxVisualState visualState, JukeboxComponent component, SpriteComponent sprite)
{
SetLayerState(JukeboxVisualLayers.Base, component.OffState, sprite);
switch (visualState)
{
case JukeboxVisualState.On:
SetLayerState(JukeboxVisualLayers.Base, component.OnState, sprite);
break;
case JukeboxVisualState.Off:
SetLayerState(JukeboxVisualLayers.Base, component.OffState, sprite);
break;
case JukeboxVisualState.Select:
PlayAnimation(uid, JukeboxVisualLayers.Base, component.SelectState, 1.0f, sprite);
break;
}
}
private void PlayAnimation(EntityUid uid, JukeboxVisualLayers layer, string? state, float animationTime, SpriteComponent sprite)
{
if (string.IsNullOrEmpty(state))
return;
if (!_animationPlayer.HasRunningAnimation(uid, state))
{
var animation = GetAnimation(layer, state, animationTime);
sprite.LayerSetVisible(layer, true);
_animationPlayer.Play(uid, animation, state);
}
}
private static Animation GetAnimation(JukeboxVisualLayers layer, string state, float animationTime)
{
return new Animation
{
Length = TimeSpan.FromSeconds(animationTime),
AnimationTracks =
{
new AnimationTrackSpriteFlick
{
LayerKey = layer,
KeyFrames =
{
new AnimationTrackSpriteFlick.KeyFrame(state, 0f)
}
}
}
};
}
private void SetLayerState(JukeboxVisualLayers layer, string? state, SpriteComponent sprite)
{
if (string.IsNullOrEmpty(state))
return;
sprite.LayerSetVisible(layer, true);
sprite.LayerSetAutoAnimated(layer, true);
sprite.LayerSetState(layer, state);
}
}

View File

@@ -0,0 +1,8 @@
using Content.Server.Bed.Sleep;
namespace Content.Client.Bed;
public sealed class SleepingSystem : SharedSleepingSystem
{
}

View File

@@ -3,7 +3,6 @@ using Content.Shared.Buckle;
using Content.Shared.Buckle.Components;
using Content.Shared.Rotation;
using Robust.Client.GameObjects;
using Robust.Shared.GameStates;
namespace Content.Client.Buckle;
@@ -15,61 +14,38 @@ internal sealed class BuckleSystem : SharedBuckleSystem
{
base.Initialize();
SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<BuckleComponent, AfterAutoHandleStateEvent>(OnBuckleAfterAutoHandleState);
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
}
private void OnStrapMoveEvent(EntityUid uid, StrapComponent component, ref MoveEvent args)
private void OnBuckleAfterAutoHandleState(EntityUid uid, BuckleComponent component, ref AfterAutoHandleStateEvent args)
{
// I'm moving this to the client-side system, but for the sake of posterity let's keep this comment:
// > This is mega cursed. Please somebody save me from Mr Buckle's wild ride
ActionBlocker.UpdateCanMove(uid);
// The nice thing is its still true, this is quite cursed, though maybe not omega cursed anymore.
// This code is garbage, it doesn't work with rotated viewports. I need to finally get around to reworking
// sprite rendering for entity layers & direction dependent sorting.
if (args.NewRotation == args.OldRotation)
if (!TryComp<SpriteComponent>(uid, out var ownerSprite))
return;
if (!TryComp<SpriteComponent>(uid, out var strapSprite))
return;
var isNorth = Transform(uid).LocalRotation.GetCardinalDir() == Direction.North;
foreach (var buckledEntity in component.BuckledEntities)
// Adjust draw depth when the chair faces north so that the seat back is drawn over the player.
// Reset the draw depth when rotated in any other direction.
// TODO when ECSing, make this a visualizer
// This code was written before rotatable viewports were introduced, so hard-coding Direction.North
// and comparing it against LocalRotation now breaks this in other rotations. This is a FIXME, but
// better to get it working for most people before we look at a more permanent solution.
if (component is { Buckled: true, LastEntityBuckledTo: { } } &&
Transform(component.LastEntityBuckledTo.Value).LocalRotation.GetCardinalDir() == Direction.North &&
TryComp<SpriteComponent>(component.LastEntityBuckledTo, out var buckledSprite))
{
if (!TryComp<BuckleComponent>(buckledEntity, out var buckle))
continue;
if (!TryComp<SpriteComponent>(buckledEntity, out var buckledSprite))
continue;
if (isNorth)
{
buckle.OriginalDrawDepth ??= buckledSprite.DrawDepth;
buckledSprite.DrawDepth = strapSprite.DrawDepth - 1;
}
else if (buckle.OriginalDrawDepth.HasValue)
{
buckledSprite.DrawDepth = buckle.OriginalDrawDepth.Value;
buckle.OriginalDrawDepth = null;
}
}
}
private void OnHandleState(Entity<BuckleComponent> ent, ref ComponentHandleState args)
{
if (args.Current is not BuckleState state)
component.OriginalDrawDepth ??= ownerSprite.DrawDepth;
ownerSprite.DrawDepth = buckledSprite.DrawDepth - 1;
return;
}
ent.Comp.DontCollide = state.DontCollide;
ent.Comp.BuckleTime = state.BuckleTime;
var strapUid = EnsureEntity<BuckleComponent>(state.BuckledTo, ent);
SetBuckledTo(ent, strapUid == null ? null : new (strapUid.Value, null));
var (uid, component) = ent;
// If here, we're not turning north and should restore the saved draw depth.
if (component.OriginalDrawDepth.HasValue)
{
ownerSprite.DrawDepth = component.OriginalDrawDepth.Value;
component.OriginalDrawDepth = null;
}
}
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)

View File

@@ -0,0 +1,27 @@
using Content.Shared.Cabinet;
using Robust.Client.GameObjects;
namespace Content.Client.Cabinet;
public sealed class ItemCabinetSystem : SharedItemCabinetSystem
{
protected override void UpdateAppearance(EntityUid uid, ItemCabinetComponent? cabinet = null)
{
if (!Resolve(uid, ref cabinet))
return;
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
var state = cabinet.Opened ? cabinet.OpenState : cabinet.ClosedState;
if (state != null)
sprite.LayerSetState(ItemCabinetVisualLayers.Door, state);
sprite.LayerSetVisible(ItemCabinetVisualLayers.ContainsItem, cabinet.CabinetSlot.HasItem);
}
}
public enum ItemCabinetVisualLayers
{
Door,
ContainsItem
}

View File

@@ -1,5 +1,4 @@
using System.Numerics;
using Content.Shared.Body.Components;
using Content.Shared.CardboardBox;
using Content.Shared.CardboardBox.Components;
using Content.Shared.Examine;
@@ -14,14 +13,9 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly ExamineSystemShared _examine = default!;
private EntityQuery<BodyComponent> _bodyQuery;
public override void Initialize()
{
base.Initialize();
_bodyQuery = GetEntityQuery<BodyComponent>();
SubscribeNetworkEvent<PlayBoxEffectMessage>(OnBoxEffect);
}
@@ -65,10 +59,6 @@ public sealed class CardboardBoxSystem : SharedCardboardBoxSystem
if (!_examine.InRangeUnOccluded(sourcePos, mapPos, box.Distance, null))
continue;
// no effect for anything too exotic
if (!_bodyQuery.HasComp(mob))
continue;
var ent = Spawn(box.Effect, mapPos);
if (!xformQuery.TryGetComponent(ent, out var entTransform) || !TryComp<SpriteComponent>(ent, out var sprite))

View File

@@ -27,11 +27,6 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
SendMessage(new BountyPrintLabelMessage(id));
};
_menu.OnSkipButtonPressed += id =>
{
SendMessage(new BountySkipMessage(id));
};
_menu.OpenCentered();
}
@@ -42,7 +37,7 @@ public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
if (message is not CargoBountyConsoleState state)
return;
_menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
_menu?.UpdateEntries(state.Bounties);
}
protected override void Dispose(bool disposing)

View File

@@ -13,18 +13,7 @@
</BoxContainer>
<Control MinWidth="10"/>
<BoxContainer Orientation="Vertical" MinWidth="120">
<BoxContainer Orientation="Horizontal" MinWidth="120">
<Button Name="PrintButton"
Text="{Loc 'bounty-console-label-button-text'}"
HorizontalExpand="False"
HorizontalAlignment="Right"
StyleClasses="OpenRight"/>
<Button Name="SkipButton"
Text="{Loc 'bounty-console-skip-button-text'}"
HorizontalExpand="False"
HorizontalAlignment="Right"
StyleClasses="OpenLeft"/>
</BoxContainer>
<Button Name="PrintButton" Text="{Loc 'bounty-console-label-button-text'}" HorizontalExpand="False" HorizontalAlignment="Right"/>
<RichTextLabel Name="IdLabel" HorizontalAlignment="Right" Margin="0 0 5 0"/>
</BoxContainer>
</BoxContainer>

View File

@@ -1,13 +1,11 @@
using Content.Client.Message;
using Content.Shared.Cargo;
using Content.Shared.Cargo.Prototypes;
using Content.Shared.Random;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Serilog;
namespace Content.Client.Cargo.UI;
@@ -16,19 +14,15 @@ public sealed partial class BountyEntry : BoxContainer
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
public Action? OnLabelButtonPressed;
public Action? OnSkipButtonPressed;
public Action? OnButtonPressed;
public TimeSpan EndTime;
public TimeSpan UntilNextSkip;
public BountyEntry(CargoBountyData bounty, TimeSpan untilNextSkip)
public BountyEntry(CargoBountyData bounty)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
UntilNextSkip = untilNextSkip;
if (!_prototype.TryIndex<CargoBountyPrototype>(bounty.Bounty, out var bountyPrototype))
return;
@@ -44,27 +38,6 @@ public sealed partial class BountyEntry : BoxContainer
DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
PrintButton.OnPressed += _ => OnLabelButtonPressed?.Invoke();
SkipButton.OnPressed += _ => OnSkipButtonPressed?.Invoke();
}
private void UpdateSkipButton(float deltaSeconds)
{
UntilNextSkip -= TimeSpan.FromSeconds(deltaSeconds);
if (UntilNextSkip > TimeSpan.Zero)
{
SkipButton.Label.Text = UntilNextSkip.ToString("mm\\:ss");
SkipButton.Disabled = true;
return;
}
SkipButton.Label.Text = Loc.GetString("bounty-console-skip-button-text");
SkipButton.Disabled = false;
}
protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
UpdateSkipButton(args.DeltaSeconds);
PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
}
}

View File

@@ -10,21 +10,19 @@ namespace Content.Client.Cargo.UI;
public sealed partial class CargoBountyMenu : FancyWindow
{
public Action<string>? OnLabelButtonPressed;
public Action<string>? OnSkipButtonPressed;
public CargoBountyMenu()
{
RobustXamlLoader.Load(this);
}
public void UpdateEntries(List<CargoBountyData> bounties, TimeSpan untilNextSkip)
public void UpdateEntries(List<CargoBountyData> bounties)
{
BountyEntriesContainer.Children.Clear();
foreach (var b in bounties)
{
var entry = new BountyEntry(b, untilNextSkip);
entry.OnLabelButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
entry.OnSkipButtonPressed += () => OnSkipButtonPressed?.Invoke(b.Id);
var entry = new BountyEntry(b);
entry.OnButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
BountyEntriesContainer.AddChild(entry);
}

View File

@@ -87,12 +87,14 @@ namespace Content.Client.Changelog
if (!tab.AdminOnly || isAdmin)
{
Tabs.SetTabVisible(i, true);
tab.Visible = true;
visibleTabs++;
firstVisible ??= i;
}
else
{
Tabs.SetTabVisible(i, false);
tab.Visible = false;
}
}

View File

@@ -24,11 +24,8 @@ public sealed class ChasmFallingVisualsSystem : EntitySystem
private void OnComponentInit(EntityUid uid, ChasmFallingComponent component, ComponentInit args)
{
if (!TryComp<SpriteComponent>(uid, out var sprite) ||
TerminatingOrDeleted(uid))
{
if (!TryComp<SpriteComponent>(uid, out var sprite))
return;
}
component.OriginalScale = sprite.Scale;

View File

@@ -1,31 +0,0 @@
<ui:RadialMenu xmlns="https://spacestation14.io"
xmlns:ui="clr-namespace:Content.Client.UserInterface.Controls"
BackButtonStyleClass="RadialMenuBackButton"
CloseButtonStyleClass="RadialMenuCloseButton"
VerticalExpand="True"
HorizontalExpand="True"
MinSize="450 450">
<!-- Main -->
<ui:RadialContainer Name="Main" VerticalExpand="True" HorizontalExpand="True" Radius="64" ReserveSpaceForHiddenChildren="False">
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-general'}" TargetLayer="General" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Head/Soft/mimesoft.rsi/icon.png"/>
</ui:RadialMenuTextureButton>
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-vocal'}" TargetLayer="Vocal" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Interface/Emotes/vocal.png"/>
</ui:RadialMenuTextureButton>
<ui:RadialMenuTextureButton StyleClasses="RadialMenuButton" SetSize="64 64" ToolTip="{Loc 'emote-menu-category-hands'}" TargetLayer="Hands" Visible="False">
<TextureRect VerticalAlignment="Center" HorizontalAlignment="Center" TextureScale="2 2" TexturePath="/Textures/Clothing/Hands/Gloves/latex.rsi/icon.png"/>
</ui:RadialMenuTextureButton>
</ui:RadialContainer>
<!-- General -->
<ui:RadialContainer Name="General" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
<!-- Vocal -->
<ui:RadialContainer Name="Vocal" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
<!-- Hands -->
<ui:RadialContainer Name="Hands" VerticalExpand="True" HorizontalExpand="True" Radius="64"/>
</ui:RadialMenu>

View File

@@ -1,115 +0,0 @@
using System.Numerics;
using Content.Client.UserInterface.Controls;
using Content.Shared.Chat.Prototypes;
using Content.Shared.Speech;
using Content.Shared.Whitelist;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
namespace Content.Client.Chat.UI;
[GenerateTypedNameReferences]
public sealed partial class EmotesMenu : RadialMenu
{
[Dependency] private readonly EntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly EntityWhitelistSystem _whitelistSystem;
public event Action<ProtoId<EmotePrototype>>? OnPlayEmote;
public EmotesMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
_spriteSystem = _entManager.System<SpriteSystem>();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
var main = FindControl<RadialContainer>("Main");
var emotes = _prototypeManager.EnumeratePrototypes<EmotePrototype>();
foreach (var emote in emotes)
{
var player = _playerManager.LocalSession?.AttachedEntity;
if (emote.Category == EmoteCategory.Invalid ||
emote.ChatTriggers.Count == 0 ||
!(player.HasValue && _whitelistSystem.IsWhitelistPassOrNull(emote.Whitelist, player.Value)) ||
_whitelistSystem.IsBlacklistPass(emote.Blacklist, player.Value))
continue;
if (!emote.Available &&
_entManager.TryGetComponent<SpeechComponent>(player.Value, out var speech) &&
!speech.AllowedEmotes.Contains(emote.ID))
continue;
var parent = FindControl<RadialContainer>(emote.Category.ToString());
var button = new EmoteMenuButton
{
StyleClasses = { "RadialMenuButton" },
SetSize = new Vector2(64f, 64f),
ToolTip = Loc.GetString(emote.Name),
ProtoId = emote.ID,
};
var tex = new TextureRect
{
VerticalAlignment = VAlignment.Center,
HorizontalAlignment = HAlignment.Center,
Texture = _spriteSystem.Frame0(emote.Icon),
TextureScale = new Vector2(2f, 2f),
};
button.AddChild(tex);
parent.AddChild(button);
foreach (var child in main.Children)
{
if (child is not RadialMenuTextureButton castChild)
continue;
if (castChild.TargetLayer == emote.Category.ToString())
{
castChild.Visible = true;
break;
}
}
}
// Set up menu actions
foreach (var child in Children)
{
if (child is not RadialContainer container)
continue;
AddEmoteClickAction(container);
}
}
private void AddEmoteClickAction(RadialContainer container)
{
foreach (var child in container.Children)
{
if (child is not EmoteMenuButton castChild)
continue;
castChild.OnButtonUp += _ =>
{
OnPlayEmote?.Invoke(castChild.ProtoId);
Close();
};
}
}
}
public sealed class EmoteMenuButton : RadialMenuTextureButton
{
public ProtoId<EmotePrototype> ProtoId { get; set; }
}

View File

@@ -1,22 +0,0 @@
using Content.Client.Chemistry.EntitySystems;
using Content.Client.Chemistry.UI;
namespace Content.Client.Chemistry.Components;
/// <summary>
/// Exposes a solution container's contents via a basic item status control.
/// </summary>
/// <remarks>
/// Shows the solution volume, max volume, and transfer amount.
/// </remarks>
/// <seealso cref="SolutionItemStatusSystem"/>
/// <seealso cref="SolutionStatusControl"/>
[RegisterComponent]
public sealed partial class SolutionItemStatusComponent : Component
{
/// <summary>
/// The ID of the solution that will be shown on the item status control.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public string Solution = "default";
}

View File

@@ -1,22 +0,0 @@
using Content.Client.Chemistry.Components;
using Content.Client.Chemistry.UI;
using Content.Client.Items;
using Content.Shared.Chemistry.EntitySystems;
namespace Content.Client.Chemistry.EntitySystems;
/// <summary>
/// Wires up item status logic for <see cref="SolutionItemStatusComponent"/>.
/// </summary>
/// <seealso cref="SolutionStatusControl"/>
public sealed class SolutionItemStatusSystem : EntitySystem
{
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
public override void Initialize()
{
base.Initialize();
Subs.ItemStatus<SolutionItemStatusComponent>(
entity => new SolutionStatusControl(entity, EntityManager, _solutionContainerSystem));
}
}

View File

@@ -1,119 +0,0 @@
using System;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Chemistry.UI;
/// <summary>
/// Creates a grid of buttons given a comma-seperated list of Text
/// </summary>
public sealed class ButtonGrid : GridContainer
{
private string _buttonList = "";
/// <summary>
/// A comma-seperated list of text to use for each button. These will be inserted sequentially.
/// </summary>
public string ButtonList
{
get => _buttonList;
set
{
_buttonList = value;
Update();
}
}
public bool RadioGroup { get; set; } = false;
private string? _selected;
/// <summary>
/// Which button is currently selected. Only matters when <see cref="RadioGroup"/> is true.
/// </summary>
public string? Selected
{
get => _selected;
set
{
_selected = value;
Update();
}
}
public Action<string>? OnButtonPressed;
/// <summary>
/// <see cref="GridContainer.Columns"/>
/// </summary>
public new int Columns
{
get => base.Columns;
set
{
base.Columns = value;
Update();
}
}
/// <summary>
/// <see cref="GridContainer.Rows"/>
/// </summary>
public new int Rows
{
get => base.Rows;
set
{
base.Rows = value;
Update();
}
}
private void Update()
{
if (ButtonList == "")
return;
this.Children.Clear();
var i = 0;
var list = ButtonList.Split(",");
var group = new ButtonGroup();
foreach (var button in list)
{
var btn = new Button();
btn.Text = button;
btn.OnPressed += _ =>
{
if (RadioGroup)
btn.Pressed = true;
Selected = button;
OnButtonPressed?.Invoke(button);
};
if (button == Selected)
btn.Pressed = true;
var sep = HSeparationOverride ?? 0;
// ReSharper disable once PossibleLossOfFraction
// btn.SetWidth = (this.PixelWidth - sep * (Columns - 1)) / 3;
btn.Group = group;
var row = i / Columns;
var col = i % Columns;
var last = i == list.Length - 1;
var lastCol = i == Columns - 1;
var lastRow = row == list.Length / Columns - 1;
if (row == 0 && (lastCol || last))
btn.AddStyleClass("OpenLeft");
else if (col == 0 && lastRow)
btn.AddStyleClass("OpenRight");
else
btn.AddStyleClass("OpenBoth");
this.Children.Add(btn);
i++;
}
}
}

View File

@@ -32,7 +32,7 @@ public sealed class InjectorStatusControl : Control
{
base.FrameUpdate(args);
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.SolutionName, out _, out var solution))
if (!_solutionContainers.TryGetSolution(_parent.Owner, InjectorComponent.SolutionName, out _, out var solution))
return;
// only updates the UI if any of the details are different than they previously were

View File

@@ -1,36 +0,0 @@
<Control xmlns="https://spacestation14.io" HorizontalExpand="True">
<BoxContainer Name="MainContainer"
Orientation="Horizontal"
HorizontalExpand="True">
<PanelContainer Name="ColorPanel"
VerticalExpand="True"
SetWidth="7"
Margin="0 1 0 0" />
<Button Name="MainButton"
HorizontalExpand="True"
VerticalExpand="True"
StyleClasses="ButtonSquare"
Margin="-1 0 0 0">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<BoxContainer Orientation="Vertical"
VerticalExpand="True"
HorizontalExpand="True"
Margin="-5 0 0 0">
<Label Name="ReagentNameLabel" />
<Label Name="FillLabel"
StyleClasses="LabelSubText"
Margin="0 -5 0 0" />
</BoxContainer>
</BoxContainer>
</Button>
<Button Name="EjectButton"
StyleClasses="OpenLeft"
VerticalExpand="True"
SetWidth="20">
<Label Name="EjectButtonIcon"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="-7 -4 0 0" />
</Button>
</BoxContainer>
</Control>

View File

@@ -1,32 +0,0 @@
using Content.Shared.Chemistry;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Chemistry.UI;
[GenerateTypedNameReferences]
public sealed partial class ReagentCardControl : Control
{
public string StorageSlotId { get; }
public Action<string>? OnPressed;
public Action<string>? OnEjectButtonPressed;
public ReagentCardControl(ReagentInventoryItem item)
{
RobustXamlLoader.Load(this);
StorageSlotId = item.StorageSlotId;
ColorPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = item.ReagentColor };
ReagentNameLabel.Text = item.ReagentLabel;
FillLabel.Text = Loc.GetString("reagent-dispenser-window-quantity-label-text", ("quantity", item.Quantity));;
EjectButtonIcon.Text = Loc.GetString("reagent-dispenser-window-eject-container-button");
if (item.Quantity == 0.0)
MainButton.Disabled = true;
MainButton.OnPressed += args => OnPressed?.Invoke(StorageSlotId);
EjectButton.OnPressed += args => OnEjectButtonPressed?.Invoke(StorageSlotId);
}
}

View File

@@ -1,4 +1,3 @@
using Content.Client.Guidebook.Components;
using Content.Shared.Chemistry;
using Content.Shared.Containers.ItemSlots;
using JetBrains.Annotations;
@@ -35,7 +34,6 @@ namespace Content.Client.Chemistry.UI
_window = new()
{
Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName,
HelpGuidebookIds = EntMan.GetComponent<GuideHelpComponent>(Owner).Guides
};
_window.OpenCentered();
@@ -44,11 +42,38 @@ namespace Content.Client.Chemistry.UI
// Setup static button actions.
_window.EjectButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent(SharedReagentDispenser.OutputSlotName));
_window.ClearButton.OnPressed += _ => SendMessage(new ReagentDispenserClearContainerSolutionMessage());
_window.DispenseButton1.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U1));
_window.DispenseButton5.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U5));
_window.DispenseButton10.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U10));
_window.DispenseButton15.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U15));
_window.DispenseButton20.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U20));
_window.DispenseButton25.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U25));
_window.DispenseButton30.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U30));
_window.DispenseButton50.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U50));
_window.DispenseButton100.OnPressed += _ => SendMessage(new ReagentDispenserSetDispenseAmountMessage(ReagentDispenserDispenseAmount.U100));
_window.AmountGrid.OnButtonPressed += s => SendMessage(new ReagentDispenserSetDispenseAmountMessage(s));
// Setup reagent button actions.
_window.OnDispenseReagentButtonPressed += (args, button) => SendMessage(new ReagentDispenserDispenseReagentMessage(button.ReagentId));
_window.OnDispenseReagentButtonMouseEntered += (args, button) =>
{
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState);
};
_window.OnDispenseReagentButtonMouseExited += (args, button) =>
{
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState);
};
_window.OnDispenseReagentButtonPressed += (id) => SendMessage(new ReagentDispenserDispenseReagentMessage(id));
_window.OnEjectJugButtonPressed += (id) => SendMessage(new ItemSlotButtonPressedEvent(id));
_window.OnEjectJugButtonPressed += (args, button) => SendMessage(new ItemSlotButtonPressedEvent(button.ReagentId));
_window.OnEjectJugButtonMouseEntered += (args, button) => {
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState);
};
_window.OnEjectJugButtonMouseExited += (args, button) => {
if (_lastState is not null)
_window.UpdateContainerInfo(_lastState);
};
}
/// <summary>

View File

@@ -1,78 +1,53 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:customControls="clr-namespace:Content.Client.Administration.UI.CustomControls"
xmlns:ui="clr-namespace:Content.Client.Chemistry.UI"
Title="{Loc 'reagent-dispenser-bound-user-interface-title'}"
MinSize="600 300"
SetSize="800 500">
<BoxContainer Orientation="Horizontal">
<BoxContainer Orientation="Vertical" MinWidth="170">
<Label Text="{Loc 'reagent-dispenser-window-amount-to-dispense-label'}" HorizontalAlignment="Center" />
<ui:ButtonGrid
Name="AmountGrid"
Access="Public"
Columns="3"
HorizontalAlignment="Center"
Margin="5"
ButtonList="1,5,10,15,20,25,30,50,100"
RadioGroup="True">
</ui:ButtonGrid>
<Control VerticalExpand="True" />
<Label Name="ContainerInfoName"
Access="Public"
Text=""
HorizontalAlignment="Center" />
<Label Name="ContainerInfoFill"
Access="Public"
Text=""
HorizontalAlignment="Center"
StyleClasses="LabelSubText" />
<SpriteView Name="View"
Scale="4 4"
HorizontalAlignment="Center" />
<BoxContainer Orientation="Horizontal" HorizontalAlignment="Center">
<Button Name="ClearButton"
Access="Public"
Text="{Loc 'reagent-dispenser-window-clear-button'}"
StyleClasses="OpenRight" />
<Button Name="EjectButton"
Access="Public"
Text="{Loc 'reagent-dispenser-window-eject-button'}"
StyleClasses="OpenLeft" />
</BoxContainer>
<DefaultWindow xmlns="https://spacestation14.io"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
Title="{Loc 'reagent-dispenser-bound-user-interface-title'}"
MinSize="680 450">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'reagent-dispenser-window-amount-to-dispense-label'}"/>
<Control MinSize="20 0"></Control>
<Button Name="DispenseButton1" Access="Public" Text="1" StyleClasses="OpenRight"/>
<Button Name="DispenseButton5" Access="Public" Text="5" StyleClasses="OpenBoth"/>
<Button Name="DispenseButton10" Access="Public" Text="10" StyleClasses="OpenBoth"/>
<Button Name="DispenseButton15" Access="Public" Text="15" StyleClasses="OpenBoth"/>
<Button Name="DispenseButton20" Access="Public" Text="20" StyleClasses="OpenBoth"/>
<Button Name="DispenseButton25" Access="Public" Text="25" StyleClasses="OpenBoth"/>
<Button Name="DispenseButton30" Access="Public" Text="30" StyleClasses="OpenBoth"/>
<Button Name="DispenseButton50" Access="Public" Text="50" StyleClasses="OpenBoth"/>
<Button Name="DispenseButton100" Access="Public" Text="100" StyleClasses="OpenLeft"/>
</BoxContainer>
<SplitContainer Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True">
<ScrollContainer HScrollEnabled="False"
HorizontalExpand="True"
VerticalExpand="True"
MinHeight="50"
SizeFlagsStretchRatio="2.5">
<GridContainer Name="ReagentList"
HorizontalExpand="True"
VerticalExpand="True"
Access="Public"
Columns="3" />
</ScrollContainer>
<ScrollContainer HScrollEnabled="False"
HorizontalExpand="True"
VerticalExpand="True"
MinHeight="50">
<PanelContainer VerticalExpand="True">
<Control MinSize="0 10"/>
<GridContainer Name="ChemicalList" HorizontalExpand="True" VerticalExpand="True" Access="Public" Columns="6">
</GridContainer>
<Control MinSize="0 10"/>
<BoxContainer Orientation="Horizontal">
<Label Text="{Loc 'reagent-dispenser-window-container-label'}"/>
<Button Name="ClearButton"
Access="Public"
Text="{Loc 'reagent-dispenser-window-clear-button'}"
StyleClasses="OpenRight"/>
<Button Name="EjectButton"
Access="Public"
Text="{Loc 'reagent-dispenser-window-eject-button'}"
StyleClasses="OpenLeft"/>
</BoxContainer>
<Control MinSize="0 10"/>
<BoxContainer Orientation="Horizontal">
<SpriteView Name="View" Scale="4 4" MinSize="150 150"/>
<ScrollContainer HScrollEnabled="False" HorizontalExpand="True" VerticalExpand="True" MinSize="0 160">
<PanelContainer VerticalExpand="True"
SizeFlagsStretchRatio="6"
MinSize="0 150">
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#1b1b1e" />
</PanelContainer.PanelOverride>
<BoxContainer Name="ContainerInfo"
Orientation="Vertical"
HorizontalExpand="True"
VerticalExpand="True"
Margin="5">
<Label Text="{Loc 'reagent-dispenser-window-no-container-loaded-text'}" />
Orientation="Vertical"
HorizontalExpand="True">
<Label Text="{Loc 'reagent-dispenser-window-no-container-loaded-text'}"/>
</BoxContainer>
</PanelContainer>
</ScrollContainer>
</SplitContainer>
</BoxContainer>
</BoxContainer>
</controls:FancyWindow>
</DefaultWindow>

View File

@@ -1,9 +1,10 @@
using Content.Client.Stylesheets;
using Content.Client.UserInterface.Controls;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BoxContainer;
@@ -14,12 +15,17 @@ namespace Content.Client.Chemistry.UI
/// Client-side UI used to control a <see cref="ReagentDispenserComponent"/>.
/// </summary>
[GenerateTypedNameReferences]
public sealed partial class ReagentDispenserWindow : FancyWindow
public sealed partial class ReagentDispenserWindow : DefaultWindow
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
public event Action<string>? OnDispenseReagentButtonPressed;
public event Action<string>? OnEjectJugButtonPressed;
public event Action<BaseButton.ButtonEventArgs, DispenseReagentButton>? OnDispenseReagentButtonPressed;
public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseEntered;
public event Action<GUIMouseHoverEventArgs, DispenseReagentButton>? OnDispenseReagentButtonMouseExited;
public event Action<BaseButton.ButtonEventArgs, EjectJugButton>? OnEjectJugButtonPressed;
public event Action<GUIMouseHoverEventArgs, EjectJugButton>? OnEjectJugButtonMouseEntered;
public event Action<GUIMouseHoverEventArgs, EjectJugButton>? OnEjectJugButtonMouseExited;
/// <summary>
/// Create and initialize the dispenser UI client-side. Creates the basic layout,
@@ -29,27 +35,44 @@ namespace Content.Client.Chemistry.UI
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var dispenseAmountGroup = new ButtonGroup();
DispenseButton1.Group = dispenseAmountGroup;
DispenseButton5.Group = dispenseAmountGroup;
DispenseButton10.Group = dispenseAmountGroup;
DispenseButton15.Group = dispenseAmountGroup;
DispenseButton20.Group = dispenseAmountGroup;
DispenseButton25.Group = dispenseAmountGroup;
DispenseButton30.Group = dispenseAmountGroup;
DispenseButton50.Group = dispenseAmountGroup;
DispenseButton100.Group = dispenseAmountGroup;
}
/// <summary>
/// Update the button grid of reagents which can be dispensed.
/// </summary>
/// <param name="inventory">Reagents which can be dispensed by this dispenser</param>
public void UpdateReagentsList(List<ReagentInventoryItem> inventory)
public void UpdateReagentsList(List<KeyValuePair<string, KeyValuePair<string, string>>> inventory)
{
if (ReagentList == null)
if (ChemicalList == null)
return;
ReagentList.Children.Clear();
ChemicalList.Children.Clear();
//Sort inventory by reagentLabel
inventory.Sort((x, y) => x.ReagentLabel.CompareTo(y.ReagentLabel));
inventory.Sort((x, y) => x.Value.Key.CompareTo(y.Value.Key));
foreach (var item in inventory)
foreach (KeyValuePair<string, KeyValuePair<string, string>> entry in inventory)
{
var card = new ReagentCardControl(item);
card.OnPressed += OnDispenseReagentButtonPressed;
card.OnEjectButtonPressed += OnEjectJugButtonPressed;
ReagentList.Children.Add(card);
var button = new DispenseReagentButton(entry.Key, entry.Value.Key, entry.Value.Value);
button.OnPressed += args => OnDispenseReagentButtonPressed?.Invoke(args, button);
button.OnMouseEntered += args => OnDispenseReagentButtonMouseEntered?.Invoke(args, button);
button.OnMouseExited += args => OnDispenseReagentButtonMouseExited?.Invoke(args, button);
ChemicalList.AddChild(button);
var ejectButton = new EjectJugButton(entry.Key);
ejectButton.OnPressed += args => OnEjectJugButtonPressed?.Invoke(args, ejectButton);
ejectButton.OnMouseEntered += args => OnEjectJugButtonMouseEntered?.Invoke(args, ejectButton);
ejectButton.OnMouseExited += args => OnEjectJugButtonMouseExited?.Invoke(args, ejectButton);
ChemicalList.AddChild(ejectButton);
}
}
@@ -70,7 +93,36 @@ namespace Content.Client.Chemistry.UI
ClearButton.Disabled = castState.OutputContainer is null;
EjectButton.Disabled = castState.OutputContainer is null;
AmountGrid.Selected = ((int)castState.SelectedDispenseAmount).ToString();
switch (castState.SelectedDispenseAmount)
{
case ReagentDispenserDispenseAmount.U1:
DispenseButton1.Pressed = true;
break;
case ReagentDispenserDispenseAmount.U5:
DispenseButton5.Pressed = true;
break;
case ReagentDispenserDispenseAmount.U10:
DispenseButton10.Pressed = true;
break;
case ReagentDispenserDispenseAmount.U15:
DispenseButton15.Pressed = true;
break;
case ReagentDispenserDispenseAmount.U20:
DispenseButton20.Pressed = true;
break;
case ReagentDispenserDispenseAmount.U25:
DispenseButton25.Pressed = true;
break;
case ReagentDispenserDispenseAmount.U30:
DispenseButton30.Pressed = true;
break;
case ReagentDispenserDispenseAmount.U50:
DispenseButton50.Pressed = true;
break;
case ReagentDispenserDispenseAmount.U100:
DispenseButton100.Pressed = true;
break;
}
}
/// <summary>
@@ -85,15 +137,23 @@ namespace Content.Client.Chemistry.UI
if (state.OutputContainer is null)
{
ContainerInfoName.Text = "";
ContainerInfoFill.Text = "";
ContainerInfo.Children.Add(new Label { Text = Loc.GetString("reagent-dispenser-window-no-container-loaded-text") });
return;
}
// Set Name of the container and its fill status (Ex: 44/100u)
ContainerInfoName.Text = state.OutputContainer.DisplayName;
ContainerInfoFill.Text = state.OutputContainer.CurrentVolume + "/" + state.OutputContainer.MaxVolume;
ContainerInfo.Children.Add(new BoxContainer // Name of the container and its fill status (Ex: 44/100u)
{
Orientation = LayoutOrientation.Horizontal,
Children =
{
new Label {Text = $"{state.OutputContainer.DisplayName}: "},
new Label
{
Text = $"{state.OutputContainer.CurrentVolume}/{state.OutputContainer.MaxVolume}",
StyleClasses = {StyleNano.StyleClassLabelSecondaryColor}
}
}
});
foreach (var (reagent, quantity) in state.OutputContainer.Reagents!)
{
@@ -121,4 +181,28 @@ namespace Content.Client.Chemistry.UI
}
}
}
public sealed class DispenseReagentButton : Button
{
public string ReagentId { get; }
public DispenseReagentButton(string reagentId, string text, string amount)
{
AddStyleClass("OpenRight");
ReagentId = reagentId;
Text = text + " " + amount;
}
}
public sealed class EjectJugButton : Button
{
public string ReagentId { get; }
public EjectJugButton(string reagentId)
{
AddStyleClass("OpenLeft");
ReagentId = reagentId;
Text = "⏏";
}
}
}

View File

@@ -1,59 +0,0 @@
using Content.Client.Chemistry.Components;
using Content.Client.Chemistry.EntitySystems;
using Content.Client.Items.UI;
using Content.Client.Message;
using Content.Client.Stylesheets;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
using Robust.Client.UserInterface.Controls;
namespace Content.Client.Chemistry.UI;
/// <summary>
/// Displays basic solution information for <see cref="SolutionItemStatusComponent"/>.
/// </summary>
/// <seealso cref="SolutionItemStatusSystem"/>
public sealed class SolutionStatusControl : PollingItemStatusControl<SolutionStatusControl.Data>
{
private readonly Entity<SolutionItemStatusComponent> _parent;
private readonly IEntityManager _entityManager;
private readonly SharedSolutionContainerSystem _solutionContainers;
private readonly RichTextLabel _label;
public SolutionStatusControl(
Entity<SolutionItemStatusComponent> parent,
IEntityManager entityManager,
SharedSolutionContainerSystem solutionContainers)
{
_parent = parent;
_entityManager = entityManager;
_solutionContainers = solutionContainers;
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
AddChild(_label);
}
protected override Data PollData()
{
if (!_solutionContainers.TryGetSolution(_parent.Owner, _parent.Comp.Solution, out _, out var solution))
return default;
FixedPoint2? transferAmount = null;
if (_entityManager.TryGetComponent(_parent.Owner, out SolutionTransferComponent? transfer))
transferAmount = transfer.TransferAmount;
return new Data(solution.Volume, solution.MaxVolume, transferAmount);
}
protected override void Update(in Data data)
{
var markup = Loc.GetString("solution-status-volume",
("currentVolume", data.Volume),
("maxVolume", data.MaxVolume));
if (data.TransferVolume is { } transferVolume)
markup += "\n" + Loc.GetString("solution-status-transfer", ("volume", transferVolume));
_label.SetMarkup(markup);
}
public readonly record struct Data(FixedPoint2 Volume, FixedPoint2 MaxVolume, FixedPoint2? TransferVolume);
}

View File

@@ -3,7 +3,6 @@ using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Hands;
using Content.Shared.Item;
using Content.Shared.Rounding;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
@@ -151,9 +150,6 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
if (!TryComp(uid, out AppearanceComponent? appearance))
return;
if (!TryComp<ItemComponent>(uid, out var item))
return;
if (!AppearanceSystem.TryGetData<float>(uid, SolutionContainerVisuals.FillFraction, out var fraction, appearance))
return;
@@ -163,8 +159,7 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
{
var layer = new PrototypeLayerData();
var heldPrefix = item.HeldPrefix == null ? "inhand-" : $"{item.HeldPrefix}-inhand-";
var key = heldPrefix + args.Location.ToString().ToLowerInvariant() + component.InHandsFillBaseName + closestFillSprite;
var key = "inhand-" + args.Location.ToString().ToLowerInvariant() + component.InHandsFillBaseName + closestFillSprite;
layer.State = key;

View File

@@ -38,9 +38,9 @@ namespace Content.Client.Clickable
renderOrder = sprite.RenderOrder;
var (spritePos, spriteRot) = transform.GetWorldPositionRotation(xformQuery);
var spriteBB = sprite.CalculateRotatedBoundingBox(spritePos, spriteRot, eye.Rotation);
bottom = Matrix3Helpers.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
bottom = Matrix3.CreateRotation(eye.Rotation).TransformBox(spriteBB).Bottom;
Matrix3x2.Invert(sprite.GetLocalMatrix(), out var invSpriteMatrix);
var invSpriteMatrix = Matrix3.Invert(sprite.GetLocalMatrix());
// This should have been the rotation of the sprite relative to the screen, but this is not the case with no-rot or directional sprites.
var relativeRotation = (spriteRot + eye.Rotation).Reduced().FlipPositive();
@@ -48,8 +48,8 @@ namespace Content.Client.Clickable
Angle cardinalSnapping = sprite.SnapCardinals ? relativeRotation.GetCardinalDir().ToAngle() : Angle.Zero;
// First we get `localPos`, the clicked location in the sprite-coordinate frame.
var entityXform = Matrix3Helpers.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
var localPos = Vector2.Transform(Vector2.Transform(worldPos, entityXform), invSpriteMatrix);
var entityXform = Matrix3.CreateInverseTransform(transform.WorldPosition, sprite.NoRotation ? -eye.Rotation : spriteRot - cardinalSnapping);
var localPos = invSpriteMatrix.Transform(entityXform.Transform(worldPos));
// Check explicitly defined click-able bounds
if (CheckDirBound(sprite, relativeRotation, localPos))
@@ -79,8 +79,8 @@ namespace Content.Client.Clickable
// convert to layer-local coordinates
layer.GetLayerDrawMatrix(dir, out var matrix);
Matrix3x2.Invert(matrix, out var inverseMatrix);
var layerLocal = Vector2.Transform(localPos, inverseMatrix);
var inverseMatrix = Matrix3.Invert(matrix);
var layerLocal = inverseMatrix.Transform(localPos);
// Convert to image coordinates
var layerImagePos = (Vector2i) (layerLocal * EyeManager.PixelsPerMeter * new Vector2(1, -1) + rsiState.Size / 2f);

View File

@@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Content.Client.Inventory;
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
@@ -12,7 +11,6 @@ using Content.Shared.Item;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
using Robust.Shared.Utility;
using static Robust.Client.GameObjects.SpriteComponent;
@@ -38,24 +36,16 @@ public sealed class ClientClothingSystem : ClothingSystem
{Jumpsuit, "INNERCLOTHING"},
{"neck", "NECK"},
{"back", "BACKPACK"},
{"belt1", "BELT1"},
{"belt2", "BELT2"},
{"belt", "BELT"},
{"gloves", "HAND"},
{"shoes", "FEET"},
{"id", "IDCARD"},
{"pocket1", "POCKET1"},
{"pocket2", "POCKET2"},
{"suitstorage", "SUITSTORAGE"},
{"ring1", "RING1"},
{"ring2", "RING2"},
{"pants", "PANTS"},
{"shirt", "SHIRT"},
{"cloak", "CLOAK"},
{"keys", "KEYS"},
};
[Dependency] private readonly IResourceCache _cache = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
public override void Initialize()
@@ -121,7 +111,6 @@ public sealed class ClientClothingSystem : ClothingSystem
i++;
}
item.MappedLayer = key;
args.Layers.Add((key, layer));
}
}
@@ -162,9 +151,13 @@ public sealed class ClientClothingSystem : ClothingSystem
// species specific
if (speciesId != null && rsi.TryGetState($"{state}-{speciesId}", out _))
{
state = $"{state}-{speciesId}";
}
else if (!rsi.TryGetState(state, out _))
{
return false;
}
var layer = new PrototypeLayerData();
layer.RsiPath = rsi.Path.ToString();
@@ -272,7 +265,6 @@ public sealed class ClientClothingSystem : ClothingSystem
// temporary, until layer draw depths get added. Basically: a layer with the key "slot" is being used as a
// bookmark to determine where in the list of layers we should insert the clothing layers.
bool slotLayerExists = sprite.LayerMapTryGet(slot, out var index);
var displacementData = inventory.Displacements.GetValueOrDefault(slot);
// add the new layers
foreach (var (key, layerData) in ev.Layers)
@@ -292,8 +284,6 @@ public sealed class ClientClothingSystem : ClothingSystem
if (layerData.Color != null)
sprite.LayerSetColor(key, layerData.Color.Value);
if (layerData.Scale != null)
sprite.LayerSetScale(key, layerData.Scale.Value);
}
else
index = sprite.LayerMapReserveBlank(key);
@@ -318,28 +308,6 @@ public sealed class ClientClothingSystem : ClothingSystem
sprite.LayerSetData(index, layerData);
layer.Offset += slotDef.Offset;
if (displacementData != null)
{
if (displacementData.ShaderOverride != null)
sprite.LayerSetShader(index, displacementData.ShaderOverride);
var displacementKey = $"{key}-displacement";
if (!revealedLayers.Add(displacementKey))
{
Log.Warning($"Duplicate key for clothing visuals DISPLACEMENT: {displacementKey}.");
continue;
}
var displacementLayer = _serialization.CreateCopy(displacementData.Layer, notNullableOverride: true);
displacementLayer.CopyToShaderParameters!.LayerKey = key;
// Add before main layer for this item.
sprite.AddLayer(displacementLayer, index);
sprite.LayerMapSet(displacementKey, index);
revealedLayers.Add(displacementKey);
}
}
RaiseLocalEvent(equipment, new EquipmentVisualsUpdatedEvent(equipee, slot, revealedLayers), true);

View File

@@ -1,48 +0,0 @@
using Content.Shared.Clothing;
using Content.Shared.Clothing.Components;
using Content.Shared.Clothing.EntitySystems;
using Content.Shared.Foldable;
using Content.Shared.Item;
using Robust.Client.GameObjects;
namespace Content.Client.Clothing;
public sealed class FlippableClothingVisualizerSystem : VisualizerSystem<FlippableClothingVisualsComponent>
{
[Dependency] private readonly SharedItemSystem _itemSys = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FlippableClothingVisualsComponent, GetEquipmentVisualsEvent>(OnGetVisuals, after: [typeof(ClothingSystem)]);
SubscribeLocalEvent<FlippableClothingVisualsComponent, FoldedEvent>(OnFolded);
}
private void OnFolded(Entity<FlippableClothingVisualsComponent> ent, ref FoldedEvent args)
{
_itemSys.VisualsChanged(ent);
}
private void OnGetVisuals(Entity<FlippableClothingVisualsComponent> ent, ref GetEquipmentVisualsEvent args)
{
if (!TryComp(ent, out SpriteComponent? sprite) ||
!TryComp(ent, out ClothingComponent? clothing))
return;
if (clothing.MappedLayer == null ||
!AppearanceSystem.TryGetData<bool>(ent, FoldableSystem.FoldedVisuals.State, out var folding) ||
!sprite.LayerMapTryGet(folding ? ent.Comp.FoldingLayer : ent.Comp.UnfoldingLayer, out var idx))
return;
// add each layer to the visuals
var spriteLayer = sprite[idx];
foreach (var layer in args.Layers)
{
if (layer.Item1 != clothing.MappedLayer)
continue;
layer.Item2.Scale = spriteLayer.Scale;
}
}
}

View File

@@ -1,16 +0,0 @@
namespace Content.Client.Clothing;
/// <summary>
/// Communicates folded layers data (currently only Scale to handle flipping)
/// to the wearer clothing sprite layer
/// </summary>
[RegisterComponent]
[Access(typeof(FlippableClothingVisualizerSystem))]
public sealed partial class FlippableClothingVisualsComponent : Component
{
[DataField]
public string FoldingLayer = "foldedLayer";
[DataField]
public string UnfoldingLayer = "unfoldedLayer";
}

View File

@@ -0,0 +1,8 @@
using Content.Shared.Clothing;
namespace Content.Client.Clothing;
public sealed class MagbootsSystem : SharedMagbootsSystem
{
}

View File

@@ -1,19 +0,0 @@
using Content.Shared.Clothing.Components;
using Robust.Client.Physics;
namespace Content.Client.Clothing.Systems;
public sealed partial class PilotedByClothingSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PilotedByClothingComponent, UpdateIsPredictedEvent>(OnUpdatePredicted);
}
private void OnUpdatePredicted(Entity<PilotedByClothingComponent> entity, ref UpdateIsPredictedEvent args)
{
args.BlockPrediction = true;
}
}

View File

@@ -35,7 +35,6 @@ public sealed class ShowHealthBarsCommand : LocalizedCommands
var showHealthBarsComponent = new ShowHealthBarsComponent
{
DamageContainers = args.ToList(),
HealthStatusIcon = null,
NetSyncEnabled = false
};

View File

@@ -1,9 +1,7 @@
using Content.Client.UserInterface.Controls;
using System.Threading;
using Content.Shared.CCVar;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Configuration;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
@@ -15,8 +13,6 @@ namespace Content.Client.Communications.UI
private CommunicationsConsoleBoundUserInterface Owner { get; set; }
private readonly CancellationTokenSource _timerCancelTokenSource = new();
[Dependency] private readonly IConfigurationManager _cfg = default!;
public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
{
IoCManager.InjectDependencies(this);
@@ -27,22 +23,6 @@ namespace Content.Client.Communications.UI
var loc = IoCManager.Resolve<ILocalizationManager>();
MessageInput.Placeholder = new Rope.Leaf(loc.GetString("comms-console-menu-announcement-placeholder"));
var maxAnnounceLength = _cfg.GetCVar(CCVars.ChatMaxAnnouncementLength);
MessageInput.OnTextChanged += (args) =>
{
if (args.Control.TextLength > maxAnnounceLength)
{
AnnounceButton.Disabled = true;
AnnounceButton.ToolTip = Loc.GetString("comms-console-message-too-long");
}
else
{
AnnounceButton.Disabled = !owner.CanAnnounce;
AnnounceButton.ToolTip = null;
}
};
AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(Rope.Collapse(MessageInput.TextRope));
AnnounceButton.Disabled = !owner.CanAnnounce;

View File

@@ -27,7 +27,6 @@ namespace Content.Client.Construction
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly ExamineSystemShared _examineSystem = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
@@ -196,8 +195,9 @@ namespace Content.Client.Construction
if (GhostPresent(loc))
return false;
// This InRangeUnobstructed should probably be replaced with "is there something blocking us in that tile?"
var predicate = GetPredicate(prototype.CanBuildInImpassable, loc.ToMap(EntityManager, _transformSystem));
if (!_examineSystem.InRangeUnOccluded(user, loc, 20f, predicate: predicate))
if (!_interactionSystem.InRangeUnobstructed(user, loc, 20f, predicate: predicate))
return false;
if (!CheckConstructionConditions(prototype, loc, dir, user, showPopup: true))

View File

@@ -2,7 +2,6 @@ using System.Linq;
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
using Content.Shared.Construction.Prototypes;
using Content.Shared.Tag;
using Content.Shared.Whitelist;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Placement;
@@ -24,7 +23,6 @@ namespace Content.Client.Construction.UI
/// </summary>
internal sealed class ConstructionMenuPresenter : IDisposable
{
[Dependency] private readonly EntityManager _entManager = default!;
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
@@ -32,7 +30,6 @@ namespace Content.Client.Construction.UI
[Dependency] private readonly IPlayerManager _playerManager = default!;
private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem;
private ConstructionSystem? _constructionSystem;
private ConstructionPrototype? _selected;
@@ -81,7 +78,6 @@ namespace Content.Client.Construction.UI
// This is a lot easier than a factory
IoCManager.InjectDependencies(this);
_constructionView = new ConstructionMenu();
_whitelistSystem = _entManager.System<EntityWhitelistSystem>();
// This is required so that if we load after the system is initialized, we can bind to it immediately
if (_systemManager.TryGetEntitySystem<ConstructionSystem>(out var constructionSystem))
@@ -164,7 +160,7 @@ namespace Content.Client.Construction.UI
if (_playerManager.LocalSession == null
|| _playerManager.LocalEntity == null
|| _whitelistSystem.IsWhitelistFail(recipe.EntityWhitelist, _playerManager.LocalEntity.Value))
|| (recipe.EntityWhitelist != null && !recipe.EntityWhitelist.IsValid(_playerManager.LocalEntity.Value)))
continue;
if (!string.IsNullOrEmpty(search))

View File

@@ -8,7 +8,7 @@
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True" Margin="10">
<BoxContainer SizeFlagsStretchRatio="2" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
<BoxContainer Orientation="Vertical">
<EntityPrototypeView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
<SpriteView Name="MachineSprite" Scale="4 4" HorizontalAlignment="Center" VerticalExpand="True" MinSize="128 128"/>
<RichTextLabel Name="MachineNameLabel" HorizontalAlignment="Center" StyleClasses="LabelKeyText"/>
</BoxContainer>
<Control MinHeight="10"/>

Some files were not shown because too many files have changed in this diff Show More