Skip to content

Commit 78189bb

Browse files
Stable release for 2025-12-20 (#41934)
2 parents c867adb + 0444896 commit 78189bb

File tree

639 files changed

+17947
-8209
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

639 files changed

+17947
-8209
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@
3737
/Content.Server/Power/Pow3r/ @PJB3005
3838

3939
/Content.Server/Discord/ @Simyon264
40+
41+
/Content.*/Atmos/** @ArtisticRoomba

CONTRIBUTING.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Space Station 14 Contributing Guidelines
2+
3+
Thanks for contributing to Space Station 14.
4+
When contributing, be sure to follow our [codebase conventions](https://docs.spacestation14.com/en/general-development/codebase-info/codebase-organization.html) and [PR guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
5+
6+
Following these guidelines helps us increase review turnaround time, so be sure to review the linked documents in full.
7+
8+
The last major guidelines update was on **December 6th, 2025**.
9+
10+
### Why is this here?
11+
We put this here so that GitHub will notify you when submitting a pull request that the PR guidelines have changed, if you haven't read the latest version.

Content.Benchmarks/DestructibleBenchmark.cs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ public class DestructibleBenchmark
5151
private readonly List<Entity<DamageableComponent>> _damageables = new();
5252
private readonly List<Entity<DamageableComponent, DestructibleComponent>> _destructbiles = new();
5353

54+
private TestMapData _currentMapData = default!;
55+
5456
private DamageSpecifier _damage;
5557

5658
private TestPair _pair = default!;
@@ -70,8 +72,6 @@ public async Task SetupAsync()
7072
_pair = await PoolManager.GetServerClient();
7173
var server = _pair.Server;
7274

73-
var mapdata = await _pair.CreateTestMap();
74-
7575
_entMan = server.ResolveDependency<IEntityManager>();
7676
_protoMan = server.ResolveDependency<IPrototypeManager>();
7777
_random = server.ResolveDependency<IRobustRandom>();
@@ -86,19 +86,25 @@ public async Task SetupAsync()
8686
_damage = new DamageSpecifier(type, DamageAmount);
8787

8888
_random.SetSeed(69420); // Randomness needs to be deterministic for benchmarking.
89+
}
8990

91+
[IterationSetup]
92+
public void IterationSetup()
93+
{
9094
var plating = _tileDefMan[TileRef].TileId;
95+
var server = _pair.Server;
96+
_currentMapData = _pair.CreateTestMap().GetAwaiter().GetResult();
9197

9298
// We make a rectangular grid of destructible entities, and then damage them all simultaneously to stress test the system.
9399
// Needed for managing the performance of destructive effects and damage application.
94-
await server.WaitPost(() =>
100+
server.WaitPost(() =>
95101
{
96102
// Set up a thin line of tiles to place our objects on. They should be anchored for a "realistic" scenario...
97103
for (var x = 0; x < EntityCount; x++)
98104
{
99105
for (var y = 0; y < _prototypes.Length; y++)
100106
{
101-
_map.SetTile(mapdata.Grid, mapdata.Grid, new Vector2i(x, y), new Tile(plating));
107+
_map.SetTile(_currentMapData.Grid, _currentMapData.Grid, new Vector2i(x, y), new Tile(plating));
102108
}
103109
}
104110

@@ -107,20 +113,25 @@ await server.WaitPost(() =>
107113
var y = 0;
108114
foreach (var protoId in _prototypes)
109115
{
110-
var coords = new EntityCoordinates(mapdata.Grid, x + 0.5f, y + 0.5f);
116+
var coords = new EntityCoordinates(_currentMapData.Grid, x + 0.5f, y + 0.5f);
111117
_entMan.SpawnEntity(protoId, coords);
112118
y++;
113119
}
114120
}
115121

116122
var query = _entMan.EntityQueryEnumerator<DamageableComponent, DestructibleComponent>();
117123

124+
_destructbiles.EnsureCapacity(EntityCount);
125+
_damageables.EnsureCapacity(EntityCount);
126+
118127
while (query.MoveNext(out var uid, out var damageable, out var destructible))
119128
{
120129
_damageables.Add((uid, damageable));
121130
_destructbiles.Add((uid, damageable, destructible));
122131
}
123-
});
132+
})
133+
.GetAwaiter()
134+
.GetResult();
124135
}
125136

126137
[Benchmark]
@@ -150,6 +161,26 @@ await _pair.Server.WaitPost(() =>
150161
});
151162
}
152163

164+
[IterationCleanup]
165+
public void IterationCleanupAsync()
166+
{
167+
// We need to nuke the entire map and respawn everything as some destructible effects
168+
// spawn entities and whatnot.
169+
_pair.Server.WaitPost(() =>
170+
{
171+
_map.QueueDeleteMap(_currentMapData.MapId);
172+
})
173+
.Wait();
174+
175+
// Deletion of entities is often queued (QueueDel) which must be processed by running ticks
176+
// or else it will grow infinitely and leak memory.
177+
_pair.Server.WaitRunTicks(2)
178+
.GetAwaiter()
179+
.GetResult();
180+
181+
_destructbiles.Clear();
182+
_damageables.Clear();
183+
}
153184

154185
[GlobalCleanup]
155186
public async Task CleanupAsync()

Content.Benchmarks/MapLoadBenchmark.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using BenchmarkDotNet.Attributes;
66
using Content.IntegrationTests;
77
using Content.IntegrationTests.Pair;
8-
using Content.Server.Maps;
8+
using Content.Shared.Maps;
99
using Robust.Shared;
1010
using Robust.Shared.Analyzers;
1111
using Robust.Shared.EntitySerialization.Systems;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<GridContainer xmlns="https://spacestation14.io"
2-
Columns="5"
2+
Columns="4"
33
HorizontalAlignment="Center">
44
</GridContainer>

Content.Client/Access/UI/AccessOverriderWindow.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
MinSize="650 290">
33
<BoxContainer Orientation="Vertical">
44
<GridContainer Columns="2">
5-
<GridContainer Columns="3" HorizontalExpand="True">
5+
<GridContainer Name="PrivilegedIdGrid" Columns="3" HorizontalExpand="True">
66
<Label Text="{Loc 'access-overrider-window-privileged-id'}" />
77
<Button Name="PrivilegedIdButton" Access="Public"/>
88
<Label Name="PrivilegedIdLabel" />

Content.Client/Access/UI/AccessOverriderWindow.xaml.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public void SetAccessLevels(IPrototypeManager protoManager, List<ProtoId<AccessL
5353

5454
public void UpdateState(IPrototypeManager protoManager, AccessOverriderBoundUserInterfaceState state)
5555
{
56+
PrivilegedIdGrid.Visible = state.ShowPrivilegedIdGrid;
57+
5658
PrivilegedIdLabel.Text = state.PrivilegedIdName;
5759
PrivilegedIdButton.Text = state.IsPrivilegedIdPresent
5860
? Loc.GetString("access-overrider-window-eject-button")
@@ -77,7 +79,9 @@ public void UpdateState(IPrototypeManager protoManager, AccessOverriderBoundUser
7779
missingPrivileges.Add(privilege);
7880
}
7981

80-
MissingPrivilegesLabel.Text = Loc.GetString("access-overrider-window-missing-privileges");
82+
MissingPrivilegesLabel.Text = state.ShowPrivilegedIdGrid ?
83+
Loc.GetString("access-overrider-window-missing-privileges") :
84+
Loc.GetString("access-overrider-window-missing-privileges-no-id");
8185
MissingPrivilegesText.Text = string.Join(", ", missingPrivileges);
8286
}
8387

Content.Client/Administration/Systems/KillSignSystem.cs

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,77 @@
11
using System.Numerics;
22
using Content.Shared.Administration.Components;
33
using Robust.Client.GameObjects;
4-
using Robust.Shared.Utility;
4+
using Robust.Client.Player;
55

66
namespace Content.Client.Administration.Systems;
77

88
public sealed class KillSignSystem : EntitySystem
99
{
1010
[Dependency] private readonly SpriteSystem _sprite = default!;
11+
[Dependency] private readonly IPlayerManager _player = default!;
1112

1213
public override void Initialize()
1314
{
1415
SubscribeLocalEvent<KillSignComponent, ComponentStartup>(KillSignAdded);
1516
SubscribeLocalEvent<KillSignComponent, ComponentShutdown>(KillSignRemoved);
17+
SubscribeLocalEvent<KillSignComponent, AfterAutoHandleStateEvent>(AfterAutoHandleState);
1618
}
1719

18-
private void KillSignRemoved(EntityUid uid, KillSignComponent component, ComponentShutdown args)
20+
private void KillSignRemoved(Entity<KillSignComponent> ent, ref ComponentShutdown args)
1921
{
20-
if (!TryComp<SpriteComponent>(uid, out var sprite))
21-
return;
22+
RemoveKillsign(ent);
23+
}
2224

23-
if (!_sprite.LayerMapTryGet((uid, sprite), KillSignKey.Key, out var layer, false))
24-
return;
25+
private void KillSignAdded(Entity<KillSignComponent> ent, ref ComponentStartup args)
26+
{
27+
AddKillsign(ent);
28+
}
2529

26-
_sprite.RemoveLayer((uid, sprite), layer);
30+
private void AfterAutoHandleState(Entity<KillSignComponent> ent, ref AfterAutoHandleStateEvent args)
31+
{
32+
// After receiving a new state for the component, we remove the old killsign and build a new one.
33+
// This is so changes to the sprite can be displayed live and allowing them to be edited via ViewVariables.
34+
// This could just update an existing sprite, but this is both easier and runs rarely anyway.
35+
RemoveKillsign(ent);
36+
AddKillsign(ent);
2737
}
2838

29-
private void KillSignAdded(EntityUid uid, KillSignComponent component, ComponentStartup args)
39+
private void AddKillsign(Entity<KillSignComponent> ent)
3040
{
31-
if (!TryComp<SpriteComponent>(uid, out var sprite))
41+
// If we hide from owner and we ARE the owner, don't add a killsign.
42+
// This could use session specific networking to FULLY hide it, but I am too lazy right now.
43+
if (ent.Comp.HideFromOwner && _player.LocalEntity == ent)
44+
return;
45+
46+
if (!TryComp<SpriteComponent>(ent, out var sprite))
3247
return;
3348

34-
if (_sprite.LayerMapTryGet((uid, sprite), KillSignKey.Key, out var _, false))
49+
if (_sprite.LayerMapTryGet((ent, sprite), KillSignKey.Key, out var _, false))
3550
return;
3651

37-
var adj = _sprite.GetLocalBounds((uid, sprite)).Height / 2 + ((1.0f / 32) * 6.0f);
52+
if (ent.Comp.Sprite == null)
53+
return;
3854

39-
var layer = _sprite.AddLayer((uid, sprite), new SpriteSpecifier.Rsi(new ResPath("Objects/Misc/killsign.rsi"), "sign"));
40-
_sprite.LayerMapSet((uid, sprite), KillSignKey.Key, layer);
55+
var adj = _sprite.GetLocalBounds((ent, sprite)).Height / 2 + ((1.0f / 32) * 6.0f);
56+
57+
var layer = _sprite.AddLayer((ent, sprite), ent.Comp.Sprite);
58+
_sprite.LayerMapSet((ent, sprite), KillSignKey.Key, layer);
59+
_sprite.LayerSetScale((ent, sprite), layer, ent.Comp.Scale);
60+
_sprite.LayerSetOffset((ent, sprite), layer, ent.Comp.DoOffset ? new Vector2(0.0f, adj) : new Vector2(0.0f, 0.0f));
61+
62+
if (ent.Comp.ForceUnshaded)
63+
sprite.LayerSetShader(layer, "unshaded");
64+
}
65+
66+
private void RemoveKillsign(Entity<KillSignComponent> ent)
67+
{
68+
if (!TryComp<SpriteComponent>(ent, out var sprite))
69+
return;
70+
71+
if (!_sprite.LayerMapTryGet((ent, sprite), KillSignKey.Key, out var layer, false))
72+
return;
4173

42-
_sprite.LayerSetOffset((uid, sprite), layer, new Vector2(0.0f, adj));
43-
sprite.LayerSetShader(layer, "unshaded");
74+
_sprite.RemoveLayer((ent, sprite), layer);
4475
}
4576

4677
private enum KillSignKey

Content.Client/Administration/UI/CustomControls/AdminLogLabel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public AdminLogLabel(ref SharedAdminLog log, HSeparator separator)
1515
OnVisibilityChanged += VisibilityChanged;
1616
}
1717

18-
public SharedAdminLog Log { get; }
18+
public new SharedAdminLog Log { get; }
1919

2020
public HSeparator Separator { get; }
2121

Content.Client/CartridgeLoader/Cartridges/NewsReaderUiFragment.xaml.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
using Content.Client.Message;
2+
using Content.Client.RichText;
3+
using Content.Client.UserInterface.RichText;
24
using Content.Shared.MassMedia.Systems;
35
using Robust.Client.AutoGenerated;
46
using Robust.Client.UserInterface.Controls;
7+
using Robust.Client.UserInterface.RichText;
58
using Robust.Client.UserInterface.XAML;
9+
using Robust.Shared.Utility;
610

711
namespace Content.Client.CartridgeLoader.Cartridges;
812

@@ -31,16 +35,17 @@ public void UpdateState(NewsArticle article, int targetNum, int totalNum, bool n
3135
Author.Visible = true;
3236

3337
PageName.Text = article.Title;
34-
PageText.SetMarkupPermissive(article.Content);
38+
PageText.SetMessage(FormattedMessage.FromMarkupPermissive(article.Content), UserFormattableTags.BaseAllowedTags);
3539

3640
PageNum.Text = $"{targetNum}/{totalNum}";
3741

3842
NotificationSwitch.Text = Loc.GetString(notificationOn ? "news-read-ui-notification-on" : "news-read-ui-notification-off");
3943

40-
string shareTime = article.ShareTime.ToString(@"hh\:mm\:ss");
44+
var shareTime = article.ShareTime.ToString(@"hh\:mm\:ss");
4145
ShareTime.SetMarkup(Loc.GetString("news-read-ui-time-prefix-text") + " " + shareTime);
4246

43-
Author.SetMarkup(Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author != null ? article.Author : Loc.GetString("news-read-ui-no-author")));
47+
var author = Loc.GetString("news-read-ui-author-prefix") + " " + (article.Author ?? Loc.GetString("news-read-ui-no-author"));
48+
Author.SetMessage(FormattedMessage.FromMarkupPermissive(author), UserFormattableTags.BaseAllowedTags);
4449

4550
Prev.Disabled = targetNum <= 1;
4651
Next.Disabled = targetNum >= totalNum;

0 commit comments

Comments
 (0)