forked from space-syndicate/space-station-14
Compare commits
396 Commits
posters-lo
...
april-fool
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d36345cf53 | ||
|
|
959e695315 | ||
|
|
4434b3752c | ||
|
|
8ca2182a7c | ||
|
|
bd954ca01a | ||
|
|
10ecfd4df1 | ||
|
|
bba7e96927 | ||
|
|
fdc79e4d2a | ||
|
|
cce22a32f3 | ||
|
|
0dc500bbff | ||
|
|
5e6581ce6a | ||
|
|
2c2f2e0045 | ||
|
|
a9f7ea5185 | ||
|
|
c7df69d853 | ||
|
|
b1c6fce89c | ||
|
|
b02459b4c3 | ||
|
|
69a3be39d7 | ||
|
|
f16a460ffd | ||
|
|
637fe64785 | ||
|
|
ab2ab80eb7 | ||
|
|
b73cf08e17 | ||
|
|
9b86c75fcb | ||
|
|
5d4e0f272f | ||
|
|
60839d7917 | ||
|
|
acde17d87b | ||
|
|
b6a735774b | ||
|
|
732cb8b0d6 | ||
|
|
404bbbf309 | ||
|
|
76212b877e | ||
|
|
ad02129045 | ||
|
|
8b6996cbae | ||
|
|
dfcb7b3c97 | ||
|
|
6bfd1d8f11 | ||
|
|
40deda74ab | ||
|
|
ce34252cd3 | ||
|
|
a9aa5011c1 | ||
|
|
25e7acaa9b | ||
|
|
9ab3523871 | ||
|
|
ad31749b55 | ||
|
|
e29c54d64e | ||
|
|
dbf9f71ee8 | ||
|
|
be2d3c4277 | ||
|
|
a487ed9df7 | ||
|
|
a5b06cb96f | ||
|
|
3b9fd4867e | ||
|
|
00035721e4 | ||
|
|
cf61150ebd | ||
|
|
3b46e649ee | ||
|
|
cefc37903e | ||
|
|
f8a8e7bafc | ||
|
|
59eb53d4f7 | ||
|
|
18581a01ca | ||
|
|
763089570d | ||
|
|
50fe4337da | ||
|
|
3c6e67adee | ||
|
|
ce1d616f87 | ||
|
|
b2299a2e5b | ||
|
|
c9be773c85 | ||
|
|
a6aaa6464b | ||
|
|
d6b8e5e243 | ||
|
|
7e53ef32e2 | ||
|
|
5cc78c2c75 | ||
|
|
8128759ea8 | ||
|
|
3f3c163591 | ||
|
|
6878b89a1c | ||
|
|
84f24d57ef | ||
|
|
ef9934c61d | ||
|
|
7ea9588172 | ||
|
|
f2b6dcf329 | ||
|
|
ceba6f1585 | ||
|
|
b71c8b7ec3 | ||
|
|
eeb8d0f630 | ||
|
|
979bdfcfa6 | ||
|
|
53bc25519c | ||
|
|
311a027797 | ||
|
|
7d53df5cfa | ||
|
|
c13f16fa2a | ||
|
|
c06bc32509 | ||
|
|
bfe04b191e | ||
|
|
ead0352fd1 | ||
|
|
a1f61997e1 | ||
|
|
7f524f6751 | ||
|
|
27f0b1f0ed | ||
|
|
81a2b29317 | ||
|
|
87b7f67bcf | ||
|
|
0c87f89fdd | ||
|
|
20e0209f78 | ||
|
|
72d9bd0a58 | ||
|
|
03f7514ccc | ||
|
|
91cb2e4e99 | ||
|
|
030ecc6964 | ||
|
|
a3405769a0 | ||
|
|
168299dbc4 | ||
|
|
d1a9162312 | ||
|
|
4cbd5ef1ca | ||
|
|
5314c85de8 | ||
|
|
3012e5a6e9 | ||
|
|
f6b5fbca6a | ||
|
|
b32a32dc5c | ||
|
|
54667700c7 | ||
|
|
723d84b77d | ||
|
|
91fda459c7 | ||
|
|
23c4792a13 | ||
|
|
66ff565e16 | ||
|
|
3c9a74e8a0 | ||
|
|
00c442ba98 | ||
|
|
b6c2a7ca47 | ||
|
|
f08037c571 | ||
|
|
ed45440256 | ||
|
|
f9a347be21 | ||
|
|
ac1d6afc84 | ||
|
|
5c129c49da | ||
|
|
94f68c5fe1 | ||
|
|
d2a126b82e | ||
|
|
2e7f9661a6 | ||
|
|
bfd8189607 | ||
|
|
f5a34af17e | ||
|
|
8f1cca267f | ||
|
|
62e5ef8041 | ||
|
|
4bc04f1bc5 | ||
|
|
0ed70b4f37 | ||
|
|
25c89539ba | ||
|
|
97b2829115 | ||
|
|
cfeba15e98 | ||
|
|
9f5c2d6e9c | ||
|
|
a09c4e7bd1 | ||
|
|
f078be9c48 | ||
|
|
15339faf14 | ||
|
|
a8d23e2f6b | ||
|
|
e128c0da6a | ||
|
|
a7571ac45b | ||
|
|
867da5b867 | ||
|
|
d2efee766f | ||
|
|
9afb753374 | ||
|
|
d5b789f90b | ||
|
|
65eab8c589 | ||
|
|
24d0890db1 | ||
|
|
2b2f471f96 | ||
|
|
de9a568797 | ||
|
|
57094966ce | ||
|
|
a0e7ce2005 | ||
|
|
91c5ee2bb3 | ||
|
|
36f7a5b0e6 | ||
|
|
bb8d298f9b | ||
|
|
2349e4bf75 | ||
|
|
99038eef8c | ||
|
|
6f3f37fc9e | ||
|
|
6107f2ce49 | ||
|
|
0969f264c8 | ||
|
|
512b79efe2 | ||
|
|
95939e4985 | ||
|
|
908aa1a039 | ||
|
|
d2943382e3 | ||
|
|
626dfa05cd | ||
|
|
018ba85885 | ||
|
|
87185d019c | ||
|
|
ef29436347 | ||
|
|
6476474205 | ||
|
|
4d71b1b81e | ||
|
|
b31a3450c2 | ||
|
|
c6c8fa2075 | ||
|
|
2d039500f2 | ||
|
|
83bfbffa69 | ||
|
|
e3989944c6 | ||
|
|
5523948efe | ||
|
|
edeb6c0e5a | ||
|
|
3e7ff70d31 | ||
|
|
bfc4da9377 | ||
|
|
0e5dc41fe8 | ||
|
|
d2cf1b8d5d | ||
|
|
a8130f177f | ||
|
|
930d097616 | ||
|
|
778b5b28c7 | ||
|
|
c310e0c527 | ||
|
|
5b9705bc4d | ||
|
|
492a361dd9 | ||
|
|
93fd38cf68 | ||
|
|
2dfdf73aa6 | ||
|
|
f32a922c11 | ||
|
|
81f97cf125 | ||
|
|
ea61e6b6f8 | ||
|
|
163b6c4e8f | ||
|
|
51e2fa5015 | ||
|
|
a92b67b10d | ||
|
|
2ba333cb55 | ||
|
|
1bccbf4013 | ||
|
|
dacc9a9d22 | ||
|
|
3cd30c408b | ||
|
|
27e5fe5767 | ||
|
|
e898246af1 | ||
|
|
ba3de94c73 | ||
|
|
7c21494a6a | ||
|
|
32cce0d938 | ||
|
|
9480291c4f | ||
|
|
cc43bc63ae | ||
|
|
ccbf8b5748 | ||
|
|
8b22ea15ae | ||
|
|
7738d82811 | ||
|
|
4ba9e8090d | ||
|
|
bda78dab60 | ||
|
|
735701d915 | ||
|
|
0acf552118 | ||
|
|
76b5439dc3 | ||
|
|
5de921b923 | ||
|
|
d9bacb63e7 | ||
|
|
230e058593 | ||
|
|
0377f328e8 | ||
|
|
a5f99dc949 | ||
|
|
2da11e4efd | ||
|
|
d371535db1 | ||
|
|
81159b0ff5 | ||
|
|
473a02120d | ||
|
|
51778afe6f | ||
|
|
ffe6b166d6 | ||
|
|
8c6d05b4ce | ||
|
|
dcbc094f94 | ||
|
|
64107023cf | ||
|
|
5ea213c906 | ||
|
|
0663211bd0 | ||
|
|
377f473ced | ||
|
|
c1211bae8a | ||
|
|
c94d3d8c27 | ||
|
|
ae65972548 | ||
|
|
303506fc38 | ||
|
|
b4caef5567 | ||
|
|
2c42bd5137 | ||
|
|
9aaaf5b509 | ||
|
|
31e4989271 | ||
|
|
60e88af3cf | ||
|
|
dd2c2b2b05 | ||
|
|
ca498697d0 | ||
|
|
bca82315c1 | ||
|
|
f34af1017d | ||
|
|
5caad7e434 | ||
|
|
427e5c95ca | ||
|
|
65acae15c0 | ||
|
|
c58729755f | ||
|
|
a0332c2f2e | ||
|
|
6f87b0f81b | ||
|
|
fba633a6b1 | ||
|
|
1db612f478 | ||
|
|
d0f5eb0987 | ||
|
|
56d7fe4f62 | ||
|
|
10f2443286 | ||
|
|
e2d027d15b | ||
|
|
d3ef4b7572 | ||
|
|
448165ffda | ||
|
|
9aadc77b92 | ||
|
|
517b2b4fcf | ||
|
|
e236a1facc | ||
|
|
07667ae34b | ||
|
|
b960bc7636 | ||
|
|
c8084e2f5b | ||
|
|
73b8b1e76c | ||
|
|
25e61bebcf | ||
|
|
89fdb5791e | ||
|
|
d03ca61da1 | ||
|
|
8c7e917038 | ||
|
|
53681a8b31 | ||
|
|
0f2e912302 | ||
|
|
48cefca4e4 | ||
|
|
31320a9e9a | ||
|
|
674085defb | ||
|
|
cd6b5712a5 | ||
|
|
778c302f85 | ||
|
|
76283ce647 | ||
|
|
74bffa9818 | ||
|
|
9c65bb883a | ||
|
|
4290577d5d | ||
|
|
f919361ed5 | ||
|
|
52ef1dd7f0 | ||
|
|
f4ba28d56b | ||
|
|
453ae9d958 | ||
|
|
f0de157e8f | ||
|
|
47d5ec20ce | ||
|
|
e43f154d23 | ||
|
|
8ebf650fe9 | ||
|
|
deefe7daed | ||
|
|
5fcac6cac5 | ||
|
|
04f6fef7a8 | ||
|
|
9a71e4a5db | ||
|
|
fd006f990a | ||
|
|
734082c103 | ||
|
|
620c40e087 | ||
|
|
9c3b963e8e | ||
|
|
88fb6ccebc | ||
|
|
a7bafa58f1 | ||
|
|
6a4743a2de | ||
|
|
52af530d61 | ||
|
|
21e5aea8ca | ||
|
|
35c237b9e1 | ||
|
|
b5a33ea7ab | ||
|
|
16cebe6601 | ||
|
|
86edcb960d | ||
|
|
635aa7e999 | ||
|
|
bb78c75acf | ||
|
|
e0c5813a2b | ||
|
|
c4bf9b5806 | ||
|
|
0e001229b7 | ||
|
|
912d0c7268 | ||
|
|
92dff4a630 | ||
|
|
c2a17452e7 | ||
|
|
d3d7e59175 | ||
|
|
c8cea841ca | ||
|
|
5ad059bf36 | ||
|
|
1a43dd1f50 | ||
|
|
da7d024c37 | ||
|
|
9413a829a4 | ||
|
|
86ce1258d7 | ||
|
|
6ef66a7ada | ||
|
|
d5cf2f28c0 | ||
|
|
d5d9046fb6 | ||
|
|
3a545a171e | ||
|
|
ca94c1748e | ||
|
|
a0a96da4c9 | ||
|
|
a8214c05d4 | ||
|
|
943b129f61 | ||
|
|
569f30b721 | ||
|
|
7f4bb7fe8a | ||
|
|
e4fc696e62 | ||
|
|
80efcdc231 | ||
|
|
5d76fd4c64 | ||
|
|
73f85fb280 | ||
|
|
4adeeee9b2 | ||
|
|
980c3ef799 | ||
|
|
ec71302b4f | ||
|
|
7a6fddce4f | ||
|
|
6b2558456b | ||
|
|
8cff034f52 | ||
|
|
33cf53d6cf | ||
|
|
55fb37e696 | ||
|
|
b8fe133472 | ||
|
|
f8bed49836 | ||
|
|
974144ddff | ||
|
|
9932fe5c07 | ||
|
|
837e92683e | ||
|
|
32a73acf12 | ||
|
|
34a1260b8c | ||
|
|
0fd0aea0ef | ||
|
|
59d60d4434 | ||
|
|
f3a06a0696 | ||
|
|
a26b284349 | ||
|
|
5823e47442 | ||
|
|
4151b31b31 | ||
|
|
0936f43d9e | ||
|
|
5bf968b1cd | ||
|
|
8dc3af1a2b | ||
|
|
002d65cc8c | ||
|
|
16773558c2 | ||
|
|
59cdbe5913 | ||
|
|
776d2df6bd | ||
|
|
b4502757a5 | ||
|
|
ba27027407 | ||
|
|
04271431be | ||
|
|
c1d3518c60 | ||
|
|
e81df5a24e | ||
|
|
80508175b6 | ||
|
|
439bc11348 | ||
|
|
279e031277 | ||
|
|
ca189b0db4 | ||
|
|
88d68568aa | ||
|
|
93425f0dd5 | ||
|
|
1ca6ced0cc | ||
|
|
1a4bd2754c | ||
|
|
ed4734ea06 | ||
|
|
447e66748a | ||
|
|
9f8f85ccf6 | ||
|
|
df98091580 | ||
|
|
8871078b33 | ||
|
|
854f88d69c | ||
|
|
7a247ce274 | ||
|
|
abfac96631 | ||
|
|
3bf0bd3b3b | ||
|
|
e5c0729144 | ||
|
|
3d5b5fb320 | ||
|
|
44838f2f26 | ||
|
|
7995817c80 | ||
|
|
fff00dd25f | ||
|
|
3af2e60817 | ||
|
|
084be340a8 | ||
|
|
3801a3b8e6 | ||
|
|
f01858c233 | ||
|
|
397f497a7a | ||
|
|
eed3c2a509 | ||
|
|
360a507688 | ||
|
|
a37ed23de9 | ||
|
|
6ea3a13c76 | ||
|
|
0290e20256 | ||
|
|
eb6d2c5b97 | ||
|
|
cce75f4d72 | ||
|
|
d89f4670f1 | ||
|
|
265a9aaf1e | ||
|
|
c9dfe6ee0c | ||
|
|
e0cfc42360 | ||
|
|
3b5354c2f2 | ||
|
|
e0427bbd2b |
@@ -1,7 +1,9 @@
|
||||
<BoxContainer
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Orientation="Vertical"
|
||||
HorizontalExpand="true">
|
||||
HorizontalExpand="True">
|
||||
<OutputPanel Name="TextOutput" VerticalExpand="true" />
|
||||
<HistoryLineEdit Name="SenderLineEdit" />
|
||||
<RichTextLabel Name="RelayedToDiscordLabel" Access="Public" Visible="False" />
|
||||
</BoxContainer>
|
||||
|
||||
@@ -3,7 +3,6 @@ using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Client.Administration.UI.CustomControls;
|
||||
|
||||
namespace Content.Client.Administration.UI.Bwoink
|
||||
{
|
||||
@@ -18,6 +17,13 @@ namespace Content.Client.Administration.UI.Bwoink
|
||||
public BwoinkPanel(Action<string> messageSender)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
var msg = new FormattedMessage();
|
||||
msg.PushColor(Color.LightGray);
|
||||
msg.AddText(Loc.GetString("bwoink-system-messages-being-relayed-to-discord"));
|
||||
msg.Pop();
|
||||
RelayedToDiscordLabel.SetMessage(msg);
|
||||
|
||||
_messageSender = messageSender;
|
||||
|
||||
OnVisibilityChanged += c =>
|
||||
|
||||
@@ -7,8 +7,9 @@
|
||||
<LineEdit Name="_callShuttleTime" Text="4:00" PlaceHolder="m:ss" HorizontalExpand="True" SizeFlagsStretchRatio="2"/>
|
||||
<Control HorizontalExpand="True" SizeFlagsStretchRatio="1"/>
|
||||
<cc:CommandButton Command="callshuttle 4:00" Name="_callShuttleButton" Text="{Loc 'comms-console-menu-call-shuttle'}" HorizontalExpand="True" SizeFlagsStretchRatio="2" />
|
||||
<!-- Corvax: Move button -->
|
||||
<cc:CommandButton Command="recallshuttle" Name="_recallShuttleButton" Text="{Loc 'comms-console-menu-recall-shuttle'}" HorizontalAlignment="Center" SizeFlagsStretchRatio="3" />
|
||||
</BoxContainer>
|
||||
<cc:CommandButton Command="recallshuttle" Name="_recallShuttleButton" Text="{Loc 'comms-console-menu-recall-shuttle'}" HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -15,32 +15,32 @@ public sealed class AlertLevelDisplaySystem : EntitySystem
|
||||
SubscribeLocalEvent<AlertLevelDisplayComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, AlertLevelDisplayComponent component, ref AppearanceChangeEvent args)
|
||||
private void OnAppearanceChange(EntityUid uid, AlertLevelDisplayComponent alertLevelDisplay, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var layer = args.Sprite.LayerMapReserveBlank(AlertLevelDisplay.Layer);
|
||||
|
||||
if (!args.Sprite.LayerMapTryGet(AlertLevelDisplay.Layer, out _))
|
||||
if (args.AppearanceData.TryGetValue(AlertLevelDisplay.Powered, out var poweredObject))
|
||||
{
|
||||
var layer = args.Sprite.AddLayer(new RSI.StateId(component.AlertVisuals.Values.First()));
|
||||
args.Sprite.LayerMapSet(AlertLevelDisplay.Layer, layer);
|
||||
args.Sprite.LayerSetVisible(layer, poweredObject is true);
|
||||
}
|
||||
|
||||
if (!args.AppearanceData.TryGetValue(AlertLevelDisplay.CurrentLevel, out var level))
|
||||
{
|
||||
args.Sprite.LayerSetState(AlertLevelDisplay.Layer, new RSI.StateId(component.AlertVisuals.Values.First()));
|
||||
args.Sprite.LayerSetState(layer, alertLevelDisplay.AlertVisuals.Values.First());
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.AlertVisuals.TryGetValue((string) level, out var visual))
|
||||
if (alertLevelDisplay.AlertVisuals.TryGetValue((string) level, out var visual))
|
||||
{
|
||||
args.Sprite.LayerSetState(AlertLevelDisplay.Layer, new RSI.StateId(visual));
|
||||
args.Sprite.LayerSetState(layer, visual);
|
||||
}
|
||||
else
|
||||
{
|
||||
args.Sprite.LayerSetState(AlertLevelDisplay.Layer, new RSI.StateId(component.AlertVisuals.Values.First()));
|
||||
args.Sprite.LayerSetState(layer, alertLevelDisplay.AlertVisuals.Values.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,13 @@ public sealed class FireVisualizerSystem : VisualizerSystem<FireVisualsComponent
|
||||
component.LightEntity = null;
|
||||
}
|
||||
|
||||
if (TryComp<SpriteComponent>(uid, out var sprite))
|
||||
sprite.RemoveLayer(FireVisualLayers.Fire);
|
||||
// Need LayerMapTryGet because Init fails if there's no existing sprite / appearancecomp
|
||||
// which means in some setups (most frequently no AppearanceComp) the layer never exists.
|
||||
if (TryComp<SpriteComponent>(uid, out var sprite) &&
|
||||
sprite.LayerMapTryGet(FireVisualLayers.Fire, out var layer))
|
||||
{
|
||||
sprite.RemoveLayer(layer);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, FireVisualsComponent component, ComponentInit args)
|
||||
|
||||
@@ -203,7 +203,7 @@ public sealed class BackgroundAudioSystem : EntitySystem
|
||||
_ambientParams.WithVolume(_ambientParams.Volume + _configManager.GetCVar(CCVars.AmbienceVolume)));
|
||||
}
|
||||
|
||||
private void EndAmbience()
|
||||
public void EndAmbience()
|
||||
{
|
||||
_playingCollection = null;
|
||||
_ambientStream?.Stop();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Content.Client.Cargo.UI;
|
||||
using Content.Shared.Cargo.BUI;
|
||||
using Content.Shared.Cargo.Events;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Cargo.BUI;
|
||||
|
||||
[UsedImplicitly]
|
||||
public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private CargoShuttleMenu? _menu;
|
||||
@@ -21,9 +21,7 @@ public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
|
||||
if (collection == null)
|
||||
return;
|
||||
|
||||
_menu = new CargoShuttleMenu(collection.Resolve<IGameTiming>(), collection.Resolve<IPrototypeManager>(), collection.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>());
|
||||
_menu.ShuttleCallRequested += OnShuttleCall;
|
||||
_menu.ShuttleRecallRequested += OnShuttleRecall;
|
||||
_menu = new CargoShuttleMenu(collection.Resolve<IPrototypeManager>(), collection.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>());
|
||||
_menu.OnClose += Close;
|
||||
|
||||
_menu.OpenCentered();
|
||||
@@ -38,24 +36,12 @@ public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShuttleRecall()
|
||||
{
|
||||
SendMessage(new CargoRecallShuttleMessage());
|
||||
}
|
||||
|
||||
private void OnShuttleCall()
|
||||
{
|
||||
SendMessage(new CargoCallShuttleMessage());
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
if (state is not CargoShuttleConsoleBoundUserInterfaceState cargoState) return;
|
||||
_menu?.SetAccountName(cargoState.AccountName);
|
||||
_menu?.SetShuttleName(cargoState.ShuttleName);
|
||||
_menu?.SetShuttleETA(cargoState.ShuttleETA);
|
||||
_menu?.SetOrders(cargoState.Orders);
|
||||
_menu?.SetCanRecall(cargoState.CanRecall);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,6 @@
|
||||
<Label Name="ShuttleStatusLabel"
|
||||
Text="{Loc 'cargo-console-menu-shuttle-status-away-text'}" />
|
||||
</BoxContainer>
|
||||
<Button Name="ShuttleCallButton"
|
||||
Text="Call Shuttle"/>
|
||||
<Button Name="ShuttleRecallButton"
|
||||
Text="Recall Shuttle"
|
||||
ToolTip="Needs to be out of range to recall."
|
||||
Visible="False"/>
|
||||
<Label Text="{Loc 'cargo-console-menu-orders-label'}" />
|
||||
<PanelContainer VerticalExpand="True"
|
||||
SizeFlagsStretchRatio="6">
|
||||
|
||||
@@ -3,8 +3,6 @@ using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -14,23 +12,14 @@ namespace Content.Client.Cargo.UI
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CargoShuttleMenu : FancyWindow
|
||||
{
|
||||
private readonly IGameTiming _timing;
|
||||
private readonly IPrototypeManager _protoManager;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
public Action? ShuttleCallRequested;
|
||||
public Action? ShuttleRecallRequested;
|
||||
|
||||
private TimeSpan? _shuttleEta;
|
||||
|
||||
public CargoShuttleMenu(IGameTiming timing, IPrototypeManager protoManager, SpriteSystem spriteSystem)
|
||||
public CargoShuttleMenu(IPrototypeManager protoManager, SpriteSystem spriteSystem)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_timing = timing;
|
||||
_protoManager = protoManager;
|
||||
_spriteSystem = spriteSystem;
|
||||
ShuttleCallButton.OnPressed += OnCallPressed;
|
||||
ShuttleRecallButton.OnPressed += OnRecallPressed;
|
||||
Title = Loc.GetString("cargo-shuttle-console-menu-title");
|
||||
}
|
||||
|
||||
@@ -44,33 +33,6 @@ namespace Content.Client.Cargo.UI
|
||||
ShuttleNameLabel.Text = name;
|
||||
}
|
||||
|
||||
public void SetShuttleETA(TimeSpan? eta)
|
||||
{
|
||||
_shuttleEta = eta;
|
||||
|
||||
if (eta == null)
|
||||
{
|
||||
ShuttleCallButton.Visible = false;
|
||||
ShuttleRecallButton.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShuttleRecallButton.Visible = false;
|
||||
ShuttleCallButton.Visible = true;
|
||||
ShuttleCallButton.Disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRecallPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
ShuttleRecallRequested?.Invoke();
|
||||
}
|
||||
|
||||
private void OnCallPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
ShuttleCallRequested?.Invoke();
|
||||
}
|
||||
|
||||
public void SetOrders(List<CargoOrderData> orders)
|
||||
{
|
||||
Orders.DisposeAllChildren();
|
||||
@@ -102,27 +64,5 @@ namespace Content.Client.Cargo.UI
|
||||
Orders.AddChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCanRecall(bool canRecall)
|
||||
{
|
||||
ShuttleRecallButton.Disabled = !canRecall;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
var remaining = _shuttleEta - _timing.CurTime;
|
||||
|
||||
if (remaining == null || remaining <= TimeSpan.Zero)
|
||||
{
|
||||
ShuttleStatusLabel.Text = $"Available";
|
||||
ShuttleCallButton.Disabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShuttleStatusLabel.Text = $"Available in: {remaining.Value.TotalSeconds:0.0}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ namespace Content.Client.Changelog
|
||||
NewChangelogEntriesChanged?.Invoke();
|
||||
}
|
||||
|
||||
// Corvax-MultiChangelog-Start
|
||||
public async Task<List<ChangelogEntry>> LoadChangelog()
|
||||
{
|
||||
var paths = _resource.ContentFindFiles("/Changelog/")
|
||||
@@ -85,6 +86,7 @@ namespace Content.Client.Changelog
|
||||
}
|
||||
return result.OrderBy(x => x.Time).ToList();
|
||||
}
|
||||
// Corvax-MultiChangelog-End
|
||||
|
||||
private Task<List<ChangelogEntry>> LoadChangelogFile(ResourcePath path)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Rounding;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -41,7 +42,6 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
||||
Logger.Error("Attempted to set solution container visuals volume ratio on " + ToPrettyString(uid) + " to a value greater than 1. Volume should never be greater than max volume!");
|
||||
fraction = 1f;
|
||||
}
|
||||
|
||||
if (component.Metamorphic)
|
||||
{
|
||||
if (args.Sprite.LayerMapTryGet(component.BaseLayer, out var baseLayer))
|
||||
@@ -79,7 +79,7 @@ public sealed class SolutionContainerVisualsSystem : VisualizerSystem<SolutionCo
|
||||
}
|
||||
}
|
||||
|
||||
var closestFillSprite = (int) Math.Round(fraction * component.MaxFillLevels);
|
||||
int closestFillSprite = ContentHelpers.RoundToLevels(fraction, 1, component.MaxFillLevels + 1);
|
||||
|
||||
if (closestFillSprite > 0)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Content.Client.Communications.UI
|
||||
|
||||
public CommunicationsConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'comms-console-menu-title'}"
|
||||
MinSize="350 225">
|
||||
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="5">
|
||||
<LineEdit Name="MessageInput" PlaceHolder="{Loc 'comms-console-menu-announcement-placeholder'}" HorizontalExpand="True" Margin="0 0 0 5" />
|
||||
<Button Name="AnnounceButton" Text="{Loc 'comms-console-menu-announcement-button'}" StyleClasses="OpenLeft" Access="Public" />
|
||||
|
||||
<OptionButton Name="AlertLevelButton" StyleClasses="OpenRight" Access="Public" />
|
||||
|
||||
<Control MinSize="10 10" />
|
||||
|
||||
<RichTextLabel Name="CountdownLabel" VerticalExpand="True" />
|
||||
<Button Name="EmergencyShuttleButton" Text="Placeholder Text" Access="Public" />
|
||||
</BoxContainer>
|
||||
</controls:FancyWindow>
|
||||
@@ -1,7 +1,10 @@
|
||||
using System.Threading;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using System.Threading;
|
||||
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.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -10,36 +13,22 @@ using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Client.Communications.UI
|
||||
{
|
||||
public sealed class CommunicationsConsoleMenu : DefaultWindow
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CommunicationsConsoleMenu : FancyWindow
|
||||
{
|
||||
private CommunicationsConsoleBoundUserInterface Owner { get; set; }
|
||||
private readonly CancellationTokenSource _timerCancelTokenSource = new();
|
||||
private LineEdit _messageInput { get; set; }
|
||||
public readonly Button AnnounceButton;
|
||||
public readonly Button EmergencyShuttleButton;
|
||||
private readonly RichTextLabel _countdownLabel;
|
||||
public readonly OptionButton AlertLevelButton;
|
||||
|
||||
public CommunicationsConsoleMenu(CommunicationsConsoleBoundUserInterface owner)
|
||||
{
|
||||
SetSize = MinSize = (600, 400);
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Title = Loc.GetString("comms-console-menu-title");
|
||||
Owner = owner;
|
||||
|
||||
_messageInput = new LineEdit
|
||||
{
|
||||
PlaceHolder = Loc.GetString("comms-console-menu-announcement-placeholder"),
|
||||
HorizontalExpand = true,
|
||||
SizeFlagsStretchRatio = 1
|
||||
};
|
||||
AnnounceButton = new Button();
|
||||
AnnounceButton.Text = Loc.GetString("comms-console-menu-announcement-button");
|
||||
AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(_messageInput.Text.Trim());
|
||||
AnnounceButton.OnPressed += (_) => Owner.AnnounceButtonPressed(MessageInput.Text.Trim());
|
||||
AnnounceButton.Disabled = !owner.CanAnnounce;
|
||||
|
||||
AlertLevelButton = new OptionButton();
|
||||
AlertLevelButton.OnItemSelected += args =>
|
||||
{
|
||||
var metadata = AlertLevelButton.GetItemMetadata(args.Id);
|
||||
@@ -50,37 +39,9 @@ namespace Content.Client.Communications.UI
|
||||
};
|
||||
AlertLevelButton.Disabled = !owner.AlertLevelSelectable;
|
||||
|
||||
_countdownLabel = new RichTextLabel(){MinSize = new Vector2(0, 200)};
|
||||
EmergencyShuttleButton = new Button();
|
||||
EmergencyShuttleButton.OnPressed += (_) => Owner.EmergencyShuttleButtonPressed();
|
||||
EmergencyShuttleButton.Disabled = !owner.CanCall;
|
||||
|
||||
var vbox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true
|
||||
};
|
||||
vbox.AddChild(_messageInput);
|
||||
vbox.AddChild(new Control(){MinSize = new Vector2(0,10), HorizontalExpand = true});
|
||||
vbox.AddChild(AnnounceButton);
|
||||
vbox.AddChild(AlertLevelButton);
|
||||
vbox.AddChild(new Control(){MinSize = new Vector2(0,10), HorizontalExpand = true});
|
||||
vbox.AddChild(_countdownLabel);
|
||||
vbox.AddChild(EmergencyShuttleButton);
|
||||
|
||||
var hbox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
HorizontalExpand = true,
|
||||
VerticalExpand = true
|
||||
};
|
||||
hbox.AddChild(new Control(){MinSize = new Vector2(100,0), HorizontalExpand = true});
|
||||
hbox.AddChild(vbox);
|
||||
hbox.AddChild(new Control(){MinSize = new Vector2(100,0), HorizontalExpand = true});
|
||||
|
||||
Contents.AddChild(hbox);
|
||||
|
||||
UpdateCountdown();
|
||||
Timer.SpawnRepeating(1000, UpdateCountdown, _timerCancelTokenSource.Token);
|
||||
}
|
||||
@@ -126,13 +87,13 @@ namespace Content.Client.Communications.UI
|
||||
{
|
||||
if (!Owner.CountdownStarted)
|
||||
{
|
||||
_countdownLabel.SetMessage("");
|
||||
CountdownLabel.SetMessage("");
|
||||
EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-call-shuttle");
|
||||
return;
|
||||
}
|
||||
|
||||
EmergencyShuttleButton.Text = Loc.GetString("comms-console-menu-recall-shuttle");
|
||||
_countdownLabel.SetMessage($"Time remaining\n{Owner.Countdown.ToString()}s");
|
||||
CountdownLabel.SetMessage($"Time remaining\n{Owner.Countdown.ToString()}s");
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
@@ -63,7 +63,7 @@ public sealed partial class HumanoidProfileEditor
|
||||
IoCManager.Resolve<SponsorsManager>().TryGetInfo(out var sponsor) &&
|
||||
!sponsor.AllowedMarkings.Contains(voice.ID))
|
||||
{
|
||||
_voiceButton.SetItemDisabled(i, true);
|
||||
_voiceButton.SetItemDisabled(_voiceButton.GetIdx(i), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,31 +15,24 @@ public sealed class CuffableSystem : SharedCuffableSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CuffableComponent, ComponentShutdown>(OnShutdown);
|
||||
SubscribeLocalEvent<CuffableComponent, ComponentShutdown>(OnCuffableShutdown);
|
||||
SubscribeLocalEvent<CuffableComponent, ComponentHandleState>(OnCuffableHandleState);
|
||||
SubscribeLocalEvent<HandcuffComponent, ComponentHandleState>(OnHandcuffHandleState);
|
||||
}
|
||||
|
||||
private void OnShutdown(EntityUid uid, CuffableComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryComp<SpriteComponent>(uid, out var sprite))
|
||||
sprite.LayerSetVisible(HumanoidVisualLayers.Handcuffs, false);
|
||||
}
|
||||
|
||||
private void OnHandcuffHandleState(EntityUid uid, HandcuffComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not HandcuffComponentState state)
|
||||
return;
|
||||
|
||||
component.Cuffing = state.Cuffing;
|
||||
component.OverlayIconState = state.IconState;
|
||||
}
|
||||
|
||||
if (state.IconState == string.Empty)
|
||||
return;
|
||||
|
||||
private void OnCuffableShutdown(EntityUid uid, CuffableComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (TryComp<SpriteComponent>(uid, out var sprite))
|
||||
{
|
||||
sprite.LayerSetState(HumanoidVisualLayers.Handcuffs, state.IconState);
|
||||
}
|
||||
sprite.LayerSetVisible(HumanoidVisualLayers.Handcuffs, false);
|
||||
}
|
||||
|
||||
private void OnCuffableHandleState(EntityUid uid, CuffableComponent component, ref ComponentHandleState args)
|
||||
|
||||
@@ -52,6 +52,12 @@ namespace Content.Client.Forensics
|
||||
{
|
||||
text.AppendLine(fiber);
|
||||
}
|
||||
text.AppendLine();
|
||||
text.AppendLine(Loc.GetString("forensic-scanner-interface-dnas"));
|
||||
foreach (var dna in msg.DNAs)
|
||||
{
|
||||
text.AppendLine(dna);
|
||||
}
|
||||
Diagnostics.Text = text.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace Content.Client.GameTicking.Managers
|
||||
[ViewVariables] public bool DisallowedLateJoin { get; private set; }
|
||||
[ViewVariables] public string? ServerInfoBlob { get; private set; }
|
||||
[ViewVariables] public TimeSpan StartTime { get; private set; }
|
||||
[ViewVariables] public TimeSpan PreloadTime { get; private set; }
|
||||
[ViewVariables] public TimeSpan RoundStartTimeSpan { get; private set; }
|
||||
[ViewVariables] public new bool Paused { get; private set; }
|
||||
|
||||
@@ -89,6 +90,7 @@ namespace Content.Client.GameTicking.Managers
|
||||
private void LobbyStatus(TickerLobbyStatusEvent message)
|
||||
{
|
||||
StartTime = message.StartTime;
|
||||
PreloadTime = message.PreloadTime;
|
||||
RoundStartTimeSpan = message.RoundStartTimeSpan;
|
||||
IsGameStarted = message.IsRoundStarted;
|
||||
AreWeReady = message.YouAreReady;
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Gravity;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Gravity
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class GravityGeneratorVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[DataField("spritemap")]
|
||||
private Dictionary<string, string> _rawSpriteMap
|
||||
{
|
||||
get => _spriteMap.ToDictionary(x => x.Key.ToString().ToLower(), x => x.Value);
|
||||
set
|
||||
{
|
||||
_spriteMap.Clear();
|
||||
// Get Sprites for each status
|
||||
foreach (var status in (GravityGeneratorStatus[]) Enum.GetValues(typeof(GravityGeneratorStatus)))
|
||||
{
|
||||
if (value.TryGetValue(status.ToString().ToLower(), out var sprite))
|
||||
{
|
||||
_spriteMap[status] = sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<GravityGeneratorStatus, string> _spriteMap = new();
|
||||
|
||||
[Obsolete("Subscribe to your component being initialised instead.")]
|
||||
public override void InitializeEntity(EntityUid entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
|
||||
if (!IoCManager.Resolve<IEntityManager>().TryGetComponent(entity, out SpriteComponent? sprite))
|
||||
return;
|
||||
|
||||
sprite.LayerMapReserveBlank(GravityGeneratorVisualLayers.Base);
|
||||
sprite.LayerMapReserveBlank(GravityGeneratorVisualLayers.Core);
|
||||
}
|
||||
|
||||
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<SpriteComponent>(component.Owner);
|
||||
|
||||
if (component.TryGetData(GravityGeneratorVisuals.State, out GravityGeneratorStatus state))
|
||||
{
|
||||
if (_spriteMap.TryGetValue(state, out var spriteState))
|
||||
{
|
||||
var layer = sprite.LayerMapGet(GravityGeneratorVisualLayers.Base);
|
||||
sprite.LayerSetState(layer, spriteState);
|
||||
}
|
||||
}
|
||||
|
||||
if (component.TryGetData(GravityGeneratorVisuals.Charge, out float charge))
|
||||
{
|
||||
var layer = sprite.LayerMapGet(GravityGeneratorVisualLayers.Core);
|
||||
switch (charge)
|
||||
{
|
||||
case < 0.2f:
|
||||
sprite.LayerSetVisible(layer, false);
|
||||
break;
|
||||
case >= 0.2f and < 0.4f:
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetState(layer, "startup");
|
||||
break;
|
||||
case >= 0.4f and < 0.6f:
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetState(layer, "idle");
|
||||
break;
|
||||
case >= 0.6f and < 0.8f:
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetState(layer, "activating");
|
||||
break;
|
||||
default:
|
||||
sprite.LayerSetVisible(layer, true);
|
||||
sprite.LayerSetState(layer, "activated");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GravityGeneratorVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
Core
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,66 @@
|
||||
using Content.Shared.Gravity;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Gravity;
|
||||
|
||||
public sealed partial class GravitySystem : SharedGravitySystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<SharedGravityGeneratorComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
InitializeShake();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that the visible state of gravity generators are synced with their sprites.
|
||||
/// </summary>
|
||||
private void OnAppearanceChange(EntityUid uid, SharedGravityGeneratorComponent comp, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (_appearanceSystem.TryGetData<GravityGeneratorStatus>(uid, GravityGeneratorVisuals.State, out var state, args.Component))
|
||||
{
|
||||
if (comp.SpriteMap.TryGetValue(state, out var spriteState))
|
||||
{
|
||||
var layer = args.Sprite.LayerMapGet(GravityGeneratorVisualLayers.Base);
|
||||
args.Sprite.LayerSetState(layer, spriteState);
|
||||
}
|
||||
}
|
||||
|
||||
if (_appearanceSystem.TryGetData<float>(uid, GravityGeneratorVisuals.Charge, out var charge, args.Component))
|
||||
{
|
||||
var layer = args.Sprite.LayerMapGet(GravityGeneratorVisualLayers.Core);
|
||||
switch (charge)
|
||||
{
|
||||
case < 0.2f:
|
||||
args.Sprite.LayerSetVisible(layer, false);
|
||||
break;
|
||||
case >= 0.2f and < 0.4f:
|
||||
args.Sprite.LayerSetVisible(layer, true);
|
||||
args.Sprite.LayerSetState(layer, comp.CoreStartupState);
|
||||
break;
|
||||
case >= 0.4f and < 0.6f:
|
||||
args.Sprite.LayerSetVisible(layer, true);
|
||||
args.Sprite.LayerSetState(layer, comp.CoreIdleState);
|
||||
break;
|
||||
case >= 0.6f and < 0.8f:
|
||||
args.Sprite.LayerSetVisible(layer, true);
|
||||
args.Sprite.LayerSetState(layer, comp.CoreActivatingState);
|
||||
break;
|
||||
default:
|
||||
args.Sprite.LayerSetVisible(layer, true);
|
||||
args.Sprite.LayerSetState(layer, comp.CoreActivatedState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GravityGeneratorVisualLayers : byte
|
||||
{
|
||||
Base,
|
||||
Core
|
||||
}
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
|
||||
namespace Content.Client.Guidebook;
|
||||
namespace Content.Client.Guidebook.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This component stores a reference to a guidebook that contains information relevant to this entity.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(GuidebookSystem))]
|
||||
public sealed class GuideHelpComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// What guides to include show when opening the guidebook. The first entry will be used to select the currently
|
||||
/// selected guidebook.
|
||||
/// What guides to include show when opening the guidebook. The first entry will be used to select the currently
|
||||
/// selected guidebook.
|
||||
/// </summary>
|
||||
[DataField("guides", customTypeSerializer: typeof(PrototypeIdListSerializer<GuideEntryPrototype>), required: true)]
|
||||
[ViewVariables]
|
||||
public List<string> Guides = new();
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to automatically include the children of the given guides.
|
||||
/// Whether or not to automatically include the children of the given guides.
|
||||
/// </summary>
|
||||
[DataField("includeChildren")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool IncludeChildren = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not to open the UI when interacting with the entity while on hand.
|
||||
/// Mostly intended for books
|
||||
/// </summary>
|
||||
[DataField("openOnActivation")]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool OpenOnActivation;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Content.Client.Guidebook;
|
||||
namespace Content.Client.Guidebook.Components;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for the guidebook monkey.
|
||||
|
||||
@@ -164,8 +164,17 @@ public sealed partial class GuideEntityEmbed : BoxContainer, IDocumentTag
|
||||
if (args.TryGetValue("Interactive", out var interactive))
|
||||
Interactive = bool.Parse(interactive);
|
||||
|
||||
if (args.TryGetValue("Rotation", out var rotation))
|
||||
{
|
||||
Sprite.Rotation = Angle.FromDegrees(double.Parse(rotation));
|
||||
}
|
||||
|
||||
Margin = new Thickness(4, 8);
|
||||
|
||||
// By default, we will map-initialize guidebook entities.
|
||||
if (!args.TryGetValue("Init", out var mapInit) || !bool.Parse(mapInit))
|
||||
_entityManager.RunMapInit(ent, _entityManager.GetComponent<MetaDataComponent>(ent));
|
||||
|
||||
control = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Guidebook.Controls;
|
||||
using Content.Client.Guidebook.Components;
|
||||
using Content.Client.Light;
|
||||
using Content.Client.Verbs;
|
||||
using Content.Shared.Input;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Light.Component;
|
||||
using Content.Shared.Speech;
|
||||
@@ -10,10 +9,7 @@ using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.Guidebook;
|
||||
@@ -24,25 +20,21 @@ namespace Content.Client.Guidebook;
|
||||
public sealed class GuidebookSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
|
||||
[Dependency] private readonly VerbSystem _verbSystem = default!;
|
||||
[Dependency] private readonly RgbLightControllerSystem _rgbLightControllerSystem = default!;
|
||||
[Dependency] private readonly SharedPointLightSystem _pointLightSystem = default!;
|
||||
[Dependency] private readonly TagSystem _tags = default!;
|
||||
private GuidebookWindow _guideWindow = default!;
|
||||
|
||||
public event Action<List<string>, List<string>?, string?, bool, string?>? OnGuidebookOpen;
|
||||
public const string GuideEmbedTag = "GuideEmbeded";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Initialize()
|
||||
{
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenGuidebook,
|
||||
new PointerInputCmdHandler(HandleOpenGuidebook))
|
||||
.Register<GuidebookSystem>();
|
||||
_guideWindow = new GuidebookWindow();
|
||||
|
||||
SubscribeLocalEvent<GuideHelpComponent, GetVerbsEvent<ExamineVerb>>(OnGetVerbs);
|
||||
SubscribeLocalEvent<GuideHelpComponent, ActivateInWorldEvent>(OnInteract);
|
||||
|
||||
SubscribeLocalEvent<GuidebookControlsTestComponent, InteractHandEvent>(OnGuidebookControlsTestInteractHand);
|
||||
SubscribeLocalEvent<GuidebookControlsTestComponent, ActivateInWorldEvent>(OnGuidebookControlsTestActivateInWorld);
|
||||
SubscribeLocalEvent<GuidebookControlsTestComponent, GetVerbsEvent<AlternativeVerb>>(
|
||||
@@ -58,12 +50,21 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
{
|
||||
Text = Loc.GetString("guide-help-verb"),
|
||||
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/VerbIcons/information.svg.192dpi.png")),
|
||||
Act = () => OpenGuidebook(component.Guides, includeChildren: component.IncludeChildren, selected: component.Guides[0]),
|
||||
Act = () => OnGuidebookOpen?.Invoke(component.Guides, null, null, component.IncludeChildren, component.Guides[0]),
|
||||
ClientExclusive = true,
|
||||
CloseMenu = true
|
||||
});
|
||||
}
|
||||
|
||||
private void OnInteract(EntityUid uid, GuideHelpComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
if (!component.OpenOnActivation || component.Guides.Count == 0 || _tags.HasTag(uid, GuideEmbedTag))
|
||||
return;
|
||||
|
||||
OnGuidebookOpen?.Invoke(component.Guides, null, null, component.IncludeChildren, component.Guides[0]);
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
private void OnGuidebookControlsTestGetAlternateVerbs(EntityUid uid, GuidebookControlsTestComponent component, GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
args.Verbs.Add(new AlternativeVerb()
|
||||
@@ -81,8 +82,8 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
var light = EnsureComp<PointLightComponent>(uid); // RGB demands this.
|
||||
light.Enabled = false;
|
||||
EnsureComp<PointLightComponent>(uid); // RGB demands this.
|
||||
_pointLightSystem.SetEnabled(uid, false);
|
||||
var rgb = EnsureComp<RgbLightControllerComponent>(uid);
|
||||
|
||||
var sprite = EnsureComp<SpriteComponent>(uid);
|
||||
@@ -143,103 +144,4 @@ public sealed class GuidebookSystem : EntitySystem
|
||||
var activateMsg = new InteractHandEvent(user, activated);
|
||||
RaiseLocalEvent(activated, activateMsg, true);
|
||||
}
|
||||
|
||||
private bool HandleOpenGuidebook(in PointerInputCmdHandler.PointerInputCmdArgs args)
|
||||
{
|
||||
if (args.State != BoundKeyState.Down)
|
||||
return false;
|
||||
|
||||
OpenGuidebook();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the guidebook.
|
||||
/// </summary>
|
||||
/// <param name="guides">What guides should be shown. If not specified, this will instead raise a <see
|
||||
/// cref="GetGuidesEvent"/> and automatically include all guide prototypes.</param>
|
||||
/// <param name="rootEntries">A list of guides that should form the base of the table of contents. If not specified,
|
||||
/// this will automatically simply be a list of all guides that have no parent.</param>
|
||||
/// <param name="forceRoot">This forces a singular guide to contain all other guides. This guide will
|
||||
/// contain its own children, in addition to what would normally be the root guides if this were not
|
||||
/// specified.</param>
|
||||
/// <param name="includeChildren">Whether or not to automatically include child entries. If false, this will ONLY
|
||||
/// show the specified entries</param>
|
||||
/// <param name="selected">The guide whose contents should be displayed when the guidebook is opened</param>
|
||||
public bool OpenGuidebook(
|
||||
Dictionary<string, GuideEntry>? guides = null,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
bool includeChildren = true,
|
||||
string? selected = null)
|
||||
{
|
||||
_guideWindow.OpenCenteredRight();
|
||||
|
||||
if (guides == null)
|
||||
{
|
||||
var ev = new GetGuidesEvent()
|
||||
{
|
||||
Guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>().ToDictionary(x => x.ID, x => (GuideEntry) x)
|
||||
};
|
||||
RaiseLocalEvent(ev);
|
||||
guides = ev.Guides;
|
||||
}
|
||||
else if (includeChildren)
|
||||
{
|
||||
var oldGuides = guides;
|
||||
guides = new(oldGuides);
|
||||
foreach (var guide in oldGuides.Values)
|
||||
{
|
||||
RecursivelyAddChildren(guide, guides);
|
||||
}
|
||||
}
|
||||
|
||||
_guideWindow.UpdateGuides(guides, rootEntries, forceRoot, selected);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OpenGuidebook(
|
||||
List<string> guideList,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
bool includeChildren = true,
|
||||
string? selected = null)
|
||||
{
|
||||
Dictionary<string, GuideEntry> guides = new();
|
||||
foreach (var guideId in guideList)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(guideId, out var guide))
|
||||
{
|
||||
Logger.Error($"Encountered unknown guide prototype: {guideId}");
|
||||
continue;
|
||||
}
|
||||
guides.Add(guideId, guide);
|
||||
}
|
||||
|
||||
return OpenGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
|
||||
}
|
||||
|
||||
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<string, GuideEntry> guides)
|
||||
{
|
||||
foreach (var childId in guide.Children)
|
||||
{
|
||||
if (guides.ContainsKey(childId))
|
||||
continue;
|
||||
|
||||
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(childId, out var child))
|
||||
{
|
||||
Logger.Error($"Encountered unknown guide prototype: {childId} as a child of {guide.Id}. If the child is not a prototype, it must be directly provided.");
|
||||
continue;
|
||||
}
|
||||
|
||||
guides.Add(childId, child);
|
||||
RecursivelyAddChildren(child, guides);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GetGuidesEvent : EntityEventArgs
|
||||
{
|
||||
public Dictionary<string, GuideEntry> Guides { get; init; } = new();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<Label StyleClasses="ItemStatus" Text="Blocked by" />
|
||||
<Label StyleClasses="ItemStatus" Text="{Loc 'hands-system-blocked-by'}" />
|
||||
</Control>
|
||||
|
||||
@@ -185,7 +185,8 @@ public sealed partial class MarkingPicker : Control
|
||||
? _markingManager.MarkingsByCategory(_selectedMarkingCategory)
|
||||
: _markingManager.MarkingsByCategoryAndSpecies(_selectedMarkingCategory, _currentSpecies);
|
||||
|
||||
foreach (var marking in markings.Values)
|
||||
var sortedMarkings = markings.OrderBy(p => Loc.GetString(GetMarkingName(p.Value)));
|
||||
foreach (var (_, marking) in sortedMarkings)
|
||||
{
|
||||
if (_currentMarkings.TryGetMarking(_selectedMarkingCategory, marking.ID, out _))
|
||||
{
|
||||
@@ -219,7 +220,7 @@ public sealed partial class MarkingPicker : Control
|
||||
|
||||
if (!IgnoreSpecies)
|
||||
{
|
||||
_currentMarkings.EnsureSpecies(_currentSpecies, null, _markingManager);
|
||||
_currentMarkings.EnsureSpecies(_currentSpecies, null, _markingManager);
|
||||
}
|
||||
|
||||
// walk backwards through the list for visual purposes
|
||||
@@ -438,7 +439,7 @@ public sealed partial class MarkingPicker : Control
|
||||
{
|
||||
markingSet.AddBack(MarkingCategories.Hair, HairMarking);
|
||||
}
|
||||
if (FacialHairMarking != null)
|
||||
if (FacialHairMarking != null)
|
||||
{
|
||||
markingSet.AddBack(MarkingCategories.FacialHair, FacialHairMarking);
|
||||
}
|
||||
|
||||
@@ -178,7 +178,8 @@ public sealed partial class SingleMarkingPicker : BoxContainer
|
||||
|
||||
MarkingList.Clear();
|
||||
|
||||
foreach (var (id, marking) in _markingPrototypeCache)
|
||||
var sortedMarkings = _markingPrototypeCache.OrderBy(p => Loc.GetString($"marking-{p.Key}"));
|
||||
foreach (var (id, marking) in sortedMarkings)
|
||||
{
|
||||
var item = MarkingList.AddItem(Loc.GetString($"marking-{id}"), marking.Sprites[0].Frame0());
|
||||
item.Metadata = marking.ID;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Client.Changelog;
|
||||
using Content.Client.UserInterface.Systems.EscapeMenu;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -34,6 +35,14 @@ namespace Content.Client.Info
|
||||
AddInfoButton("server-info-wiki-button", CCVars.InfoLinksWiki);
|
||||
AddInfoButton("server-info-forum-button", CCVars.InfoLinksForum);
|
||||
|
||||
var guidebookController = UserInterfaceManager.GetUIController<GuidebookUIController>();
|
||||
var guidebookButton = new Button() { Text = Loc.GetString("server-info-guidebook-button") };
|
||||
guidebookButton.OnPressed += _ =>
|
||||
{
|
||||
guidebookController.ToggleGuidebook();
|
||||
};
|
||||
buttons.AddChild(guidebookButton);
|
||||
|
||||
var changelogButton = new ChangelogButton();
|
||||
changelogButton.OnPressed += args => UserInterfaceManager.GetUIController<ChangelogUIController>().ToggleWindow();
|
||||
buttons.AddChild(changelogButton);
|
||||
|
||||
@@ -87,7 +87,7 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
if (!Resolve(uid, ref instrument) || instrument.Renderer == null)
|
||||
return;
|
||||
|
||||
instrument.Renderer.TrackingEntity = instrument.Owner;
|
||||
instrument.Renderer.TrackingEntity = uid;
|
||||
instrument.Renderer.DisablePercussionChannel = !instrument.AllowPercussion;
|
||||
instrument.Renderer.DisableProgramChangeEvent = !instrument.AllowProgramChange;
|
||||
|
||||
@@ -127,7 +127,9 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
|
||||
// We dispose of the synth two seconds from now to allow the last notes to stop from playing.
|
||||
// Don't use timers bound to the entity in case it is getting deleted.
|
||||
Timer.Spawn(2000, () => { renderer?.Dispose(); });
|
||||
if (renderer != null)
|
||||
Timer.Spawn(2000, () => { renderer.Dispose(); });
|
||||
|
||||
instrument.Renderer = null;
|
||||
instrument.MidiEventBuffer.Clear();
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Kitchen.UI"
|
||||
Title="{Loc grinder-menu-title}" MinSize="512 256" SetSize="750 256">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" VerticalAlignment="Center">
|
||||
<Button Name="GrindButton" Text="{Loc grinder-menu-grind-button}" TextAlign="Center" MinSize="64 64"/>
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc grinder-menu-title}" MinSize="768 256">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer Orientation="Vertical" VerticalAlignment="Top" Margin="8">
|
||||
<Button Name="GrindButton" Text="{Loc grinder-menu-grind-button}" TextAlign="Center" MinSize="64 48"/>
|
||||
<Control MinSize="0 16"/>
|
||||
<Button Name="JuiceButton" Text="{Loc grinder-menu-juice-button}" TextAlign="Center" MinSize="64 64"/>
|
||||
<Button Name="JuiceButton" Text="{Loc grinder-menu-juice-button}" TextAlign="Center" MinSize="64 48"/>
|
||||
</BoxContainer>
|
||||
<Control MinSize="16 0"/>
|
||||
<ui:LabelledContentBox Name="ChamberContentBox" LabelText="{Loc grinder-menu-chamber-content-box-label}" ButtonText="{Loc grinder-menu-chamber-content-box-button}" VerticalExpand="True" HorizontalExpand="True" SizeFlagsStretchRatio="2"/>
|
||||
<Control MinSize="8 0"/>
|
||||
<ui:LabelledContentBox Name="BeakerContentBox" LabelText="{Loc grinder-menu-beaker-content-box-label}" ButtonText="{Loc grinder-menu-beaker-content-box-button}" VerticalExpand="True" HorizontalExpand="True" SizeFlagsStretchRatio="2"/>
|
||||
|
||||
<ui:LabelledContentBox Name="ChamberContentBox" LabelText="{Loc grinder-menu-chamber-content-box-label}" ButtonText="{Loc grinder-menu-chamber-content-box-button}" VerticalExpand="True" HorizontalExpand="True" Margin="8" SizeFlagsStretchRatio="2"/>
|
||||
|
||||
<ui:LabelledContentBox Name="BeakerContentBox" LabelText="{Loc grinder-menu-beaker-content-box-label}" ButtonText="{Loc grinder-menu-beaker-content-box-button}" VerticalExpand="True" HorizontalExpand="True" Margin="8" SizeFlagsStretchRatio="2"/>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using Content.Shared.Kitchen;
|
||||
@@ -11,7 +12,7 @@ using Robust.Shared.Prototypes;
|
||||
namespace Content.Client.Kitchen.UI
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class GrinderMenu : DefaultWindow
|
||||
public sealed partial class GrinderMenu : FancyWindow
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IPrototypeManager _prototypeManager ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<BoxContainer Orientation="Vertical" xmlns="https://spacestation14.io">
|
||||
<SplitContainer Orientation="Horizontal">
|
||||
<SplitContainer Orientation="Horizontal" Margin="5">
|
||||
<Label Name="Label" Align="Center"/>
|
||||
<Button Name="EjectButton" Access="Public" TextAlign="Center"/>
|
||||
</SplitContainer>
|
||||
|
||||
@@ -138,6 +138,10 @@ namespace Content.Client.Lobby
|
||||
{
|
||||
text = Loc.GetString("lobby-state-paused");
|
||||
}
|
||||
else if ((_gameTicker.StartTime - _gameTicker.PreloadTime) < _gameTiming.CurTime)
|
||||
{
|
||||
text = Loc.GetString("lobby-state-preloading");
|
||||
}
|
||||
else
|
||||
{
|
||||
var difference = _gameTicker.StartTime - _gameTiming.CurTime;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<Control Name="DefaultState" VerticalExpand="True">
|
||||
<!-- Left Top Panel -->
|
||||
<PanelContainer StyleClasses="AngleRect" HorizontalAlignment="Left" Name = "LeftSideTop" VerticalAlignment="Top" >
|
||||
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center" MaxWidth="620">
|
||||
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center" MaxWidth="800">
|
||||
<info:LinkBanner Name="LinkBanner" VerticalExpand="false" HorizontalAlignment="Center" Margin="3 3 3 3"/>
|
||||
<controls:StripeBack>
|
||||
<BoxContainer Orientation="Horizontal" SeparationOverride="6" Margin="3 3 3 3">
|
||||
|
||||
@@ -1,23 +1,16 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
Title="{Loc 'crew-monitoring-user-interface-title'}"
|
||||
SetSize="775 400">
|
||||
<ScrollContainer HorizontalExpand="True"
|
||||
VerticalExpand="True">
|
||||
<GridContainer Name="SensorsTable"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
HSeparationOverride="5"
|
||||
VSeparationOverride="20"
|
||||
Columns="3">
|
||||
<!-- Table header -->
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-name'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-status'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-location'}"
|
||||
StyleClasses="LabelHeading"/>
|
||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'crew-monitoring-user-interface-title'}"
|
||||
SetSize="775 400">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" Margin="5">
|
||||
<GridContainer Name="SensorsTable" HorizontalExpand="True" VerticalExpand="True" HSeparationOverride="5" VSeparationOverride="20" Columns="4">
|
||||
<!-- Category Headers -->
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-name'}" StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-job'}" StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-status'}" StyleClasses="LabelHeading"/>
|
||||
<Label Text="{Loc 'crew-monitoring-user-interface-location'}" StyleClasses="LabelHeading"/>
|
||||
|
||||
<!-- Table rows are filled by code -->
|
||||
<!-- Additional table rows are filled by code -->
|
||||
</GridContainer>
|
||||
</ScrollContainer>
|
||||
</DefaultWindow>
|
||||
</controls:FancyWindow>
|
||||
|
||||
@@ -14,7 +14,7 @@ using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CrewMonitoringWindow : DefaultWindow
|
||||
public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
{
|
||||
private List<Control> _rowsContent = new();
|
||||
private List<(DirectionIcon Icon, Vector2 Position)> _directionIcons = new();
|
||||
@@ -41,16 +41,26 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
// add a row for each sensor
|
||||
foreach (var sensor in stSensors.OrderBy(a => a.Name))
|
||||
{
|
||||
// add users name and job
|
||||
// format: UserName (Job)
|
||||
// add users name
|
||||
// format: UserName
|
||||
var nameLabel = new Label()
|
||||
{
|
||||
Text = $"{sensor.Name} ({sensor.Job})"
|
||||
Text = sensor.Name,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
nameLabel.HorizontalExpand = true;
|
||||
SensorsTable.AddChild(nameLabel);
|
||||
_rowsContent.Add(nameLabel);
|
||||
|
||||
// add users job
|
||||
// format: JobName
|
||||
var jobLabel = new Label()
|
||||
{
|
||||
Text = sensor.Job,
|
||||
HorizontalExpand = true
|
||||
};
|
||||
SensorsTable.AddChild(jobLabel);
|
||||
_rowsContent.Add(jobLabel);
|
||||
|
||||
// add users status and damage
|
||||
// format: IsAlive (TotalDamage)
|
||||
var statusText = Loc.GetString(sensor.IsAlive ?
|
||||
@@ -122,7 +132,7 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
foreach (var child in _rowsContent)
|
||||
{
|
||||
SensorsTable.RemoveChild(child);
|
||||
SensorsTable.RemoveChild(child);
|
||||
}
|
||||
_rowsContent.Clear();
|
||||
}
|
||||
|
||||
7
Content.Client/Medical/Surgery/SurgeryRealmSystem.cs
Normal file
7
Content.Client/Medical/Surgery/SurgeryRealmSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.Medical.Surgery;
|
||||
|
||||
namespace Content.Client.Medical.Surgery;
|
||||
|
||||
public sealed class SurgeryRealmSystem : SharedSurgeryRealmSystem
|
||||
{
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
using Content.Shared.MedicalScanner;
|
||||
|
||||
namespace Content.Client.MedicalScanner
|
||||
namespace Content.Client.MedicalScanner;
|
||||
|
||||
[RegisterComponent]
|
||||
public sealed class MedicalScannerComponent : SharedMedicalScannerComponent
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class MedicalScannerComponent : SharedMedicalScannerComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent;
|
||||
using static Content.Shared.MedicalScanner.SharedMedicalScannerComponent.MedicalScannerStatus;
|
||||
|
||||
namespace Content.Client.MedicalScanner
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public sealed class MedicalScannerVisualizer : AppearanceVisualizer
|
||||
{
|
||||
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<SpriteComponent>(component.Owner);
|
||||
if (!component.TryGetData(MedicalScannerVisuals.Status, out MedicalScannerStatus status)) return;
|
||||
sprite.LayerSetState(MedicalScannerVisualLayers.Machine, StatusToMachineStateId(status));
|
||||
sprite.LayerSetState(MedicalScannerVisualLayers.Terminal, StatusToTerminalStateId(status));
|
||||
}
|
||||
|
||||
private string StatusToMachineStateId(MedicalScannerStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case Off: return "closed";
|
||||
case Open: return "open";
|
||||
case Red: return "occupied";
|
||||
case Death: return "occupied";
|
||||
case Green: return "occupied";
|
||||
case Yellow: return "occupied";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
|
||||
}
|
||||
}
|
||||
|
||||
private string StatusToTerminalStateId(MedicalScannerStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case Off: return "off_unlit";
|
||||
case Open: return "idle_unlit";
|
||||
case Red: return "red_unlit";
|
||||
case Death: return "off_unlit";
|
||||
case Green: return "idle_unlit";
|
||||
case Yellow: return "maint_unlit";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown MedicalScannerStatus");
|
||||
}
|
||||
}
|
||||
|
||||
public enum MedicalScannerVisualLayers : byte
|
||||
{
|
||||
Machine,
|
||||
Terminal,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Shared._Afterlight.ThirdDimension;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Parallax.Biomes;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -19,6 +20,7 @@ public sealed class ParallaxOverlay : Overlay
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IParallaxManager _manager = default!;
|
||||
private readonly ParallaxSystem _parallax;
|
||||
private readonly SharedZLevelSystem _zlevel = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
|
||||
|
||||
@@ -27,11 +29,12 @@ public sealed class ParallaxOverlay : Overlay
|
||||
ZIndex = ParallaxSystem.ParallaxZIndex;
|
||||
IoCManager.InjectDependencies(this);
|
||||
_parallax = _entManager.System<ParallaxSystem>();
|
||||
_zlevel = _entManager.System<SharedZLevelSystem>();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace || _entManager.HasComponent<BiomeComponent>(_mapManager.GetMapEntityId(args.MapId)))
|
||||
if (args.MapId == MapId.Nullspace || _entManager.HasComponent<BiomeComponent>(_mapManager.GetMapEntityId(args.MapId)) || _zlevel.MapBelow[(int)args.MapId] != null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Client.Parallax.Data;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Shared.Parallax;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -27,6 +28,9 @@ public sealed class ParallaxSystem : SharedParallaxSystem
|
||||
|
||||
private void OnReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.ByType.ContainsKey(typeof(ParallaxPrototype)))
|
||||
return;
|
||||
|
||||
_parallax.UnloadParallax(Fallback);
|
||||
_parallax.LoadDefaultParallax();
|
||||
|
||||
|
||||
@@ -75,8 +75,9 @@ namespace Content.Client.Physics.Controllers
|
||||
return;
|
||||
}
|
||||
|
||||
var physicsUid = player;
|
||||
PhysicsComponent? body;
|
||||
TransformComponent? xformMover = xform;
|
||||
var xformMover = xform;
|
||||
|
||||
if (mover.ToParent && HasComp<RelayInputMoverComponent>(xform.ParentUid))
|
||||
{
|
||||
@@ -85,6 +86,8 @@ namespace Content.Client.Physics.Controllers
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
physicsUid = xform.ParentUid;
|
||||
}
|
||||
else if (!TryComp(player, out body))
|
||||
{
|
||||
@@ -128,7 +131,7 @@ namespace Content.Client.Physics.Controllers
|
||||
}
|
||||
|
||||
// Server-side should just be handled on its own so we'll just do this shizznit
|
||||
HandleMobMovement(player, mover, body, xformMover, frameTime, xformQuery, moverQuery, relayTargetQuery);
|
||||
HandleMobMovement(player, mover, physicsUid, body, xformMover, frameTime, xformQuery, moverQuery, relayTargetQuery);
|
||||
}
|
||||
|
||||
protected override bool CanSound()
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Shuttles.BUIStates;
|
||||
using Content.Shared.Shuttles.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Content.Shared.Shuttles.Systems;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
@@ -32,14 +37,14 @@ public sealed class RadarControl : Control
|
||||
|
||||
private Angle? _rotation;
|
||||
|
||||
private float _radarMinRange = 64f;
|
||||
private float _radarMaxRange = 256f;
|
||||
public float RadarRange { get; private set; } = 256f;
|
||||
private float _radarMinRange = SharedRadarConsoleSystem.DefaultMinRange;
|
||||
private float _radarMaxRange = SharedRadarConsoleSystem.DefaultMaxRange;
|
||||
public float RadarRange { get; private set; } = SharedRadarConsoleSystem.DefaultMinRange;
|
||||
|
||||
/// <summary>
|
||||
/// We'll lerp between the radarrange and actual range
|
||||
/// </summary>
|
||||
private float _actualRadarRange = 256f;
|
||||
private float _actualRadarRange = SharedRadarConsoleSystem.DefaultMinRange;
|
||||
|
||||
/// <summary>
|
||||
/// Controls the maximum distance that IFF labels will display.
|
||||
@@ -69,6 +74,11 @@ public sealed class RadarControl : Control
|
||||
|
||||
public Action<float>? OnRadarRangeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Raised if the user left-clicks on the radar control with the relevant entitycoordinates.
|
||||
/// </summary>
|
||||
public Action<EntityCoordinates>? OnRadarClick;
|
||||
|
||||
public RadarControl()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -86,14 +96,11 @@ public sealed class RadarControl : Control
|
||||
{
|
||||
_radarMaxRange = ls.MaxRange;
|
||||
|
||||
if (_radarMaxRange < RadarRange)
|
||||
{
|
||||
_actualRadarRange = _radarMaxRange;
|
||||
}
|
||||
|
||||
if (_radarMaxRange < _radarMinRange)
|
||||
_radarMinRange = _radarMaxRange;
|
||||
|
||||
_actualRadarRange = Math.Clamp(_actualRadarRange, _radarMinRange, _radarMaxRange);
|
||||
|
||||
_docks.Clear();
|
||||
|
||||
foreach (var state in ls.Docks)
|
||||
@@ -104,6 +111,43 @@ public sealed class RadarControl : Control
|
||||
}
|
||||
}
|
||||
|
||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
|
||||
if (_coordinates == null || _rotation == null || args.Function != EngineKeyFunctions.UIClick ||
|
||||
OnRadarClick == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var a = InverseScalePosition(args.RelativePosition);
|
||||
var relativeWorldPos = new Vector2(a.X, -a.Y);
|
||||
relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos);
|
||||
var coords = _coordinates.Value.Offset(relativeWorldPos);
|
||||
OnRadarClick?.Invoke(coords);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the entitycoordinates of where the mouseposition is, relative to the control.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public EntityCoordinates GetMouseCoordinates(ScreenCoordinates screen)
|
||||
{
|
||||
if (_coordinates == null || _rotation == null)
|
||||
{
|
||||
return EntityCoordinates.Invalid;
|
||||
}
|
||||
|
||||
var pos = screen.Position / UIScale - GlobalPosition;
|
||||
|
||||
var a = InverseScalePosition(pos);
|
||||
var relativeWorldPos = new Vector2(a.X, -a.Y);
|
||||
relativeWorldPos = _rotation.Value.RotateVec(relativeWorldPos);
|
||||
var coords = _coordinates.Value.Offset(relativeWorldPos);
|
||||
return coords;
|
||||
}
|
||||
|
||||
protected override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
@@ -170,20 +214,18 @@ public sealed class RadarControl : Control
|
||||
var offset = _coordinates.Value.Position;
|
||||
var offsetMatrix = Matrix3.CreateInverseTransform(
|
||||
mapPosition.Position,
|
||||
xform.WorldRotation - _rotation.Value);
|
||||
xform.WorldRotation + _rotation.Value);
|
||||
|
||||
// Draw our grid in detail
|
||||
var ourGridId = _coordinates.Value.GetGridUid(_entManager);
|
||||
if (ourGridId != null)
|
||||
if (_entManager.TryGetComponent<MapGridComponent>(ourGridId, out var ourGrid) &&
|
||||
fixturesQuery.TryGetComponent(ourGridId, out var ourFixturesComp))
|
||||
{
|
||||
var ourGridMatrix = xformQuery.GetComponent(ourGridId.Value).WorldMatrix;
|
||||
var ourGridFixtures = fixturesQuery.GetComponent(ourGridId.Value);
|
||||
|
||||
Matrix3.Multiply(in ourGridMatrix, in offsetMatrix, out var matrix);
|
||||
|
||||
// Draw our grid; use non-filled boxes so it doesn't look awful.
|
||||
DrawGrid(handle, matrix, ourGridFixtures, Color.Yellow);
|
||||
|
||||
DrawGrid(handle, matrix, ourFixturesComp, ourGrid, Color.MediumSpringGreen, true);
|
||||
DrawDocks(handle, ourGridId.Value, matrix);
|
||||
}
|
||||
|
||||
@@ -200,7 +242,7 @@ public sealed class RadarControl : Control
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapPosition.MapId,
|
||||
new Box2(mapPosition.Position - MaxRadarRange, mapPosition.Position + MaxRadarRange)))
|
||||
{
|
||||
if (grid.Owner == ourGridId || !fixturesQuery.TryGetComponent(grid.Owner, out var gridFixtures))
|
||||
if (grid.Owner == ourGridId || !fixturesQuery.TryGetComponent(grid.Owner, out var fixturesComp))
|
||||
continue;
|
||||
|
||||
var gridBody = bodyQuery.GetComponent(grid.Owner);
|
||||
@@ -228,7 +270,11 @@ public sealed class RadarControl : Control
|
||||
var gridXform = xformQuery.GetComponent(grid.Owner);
|
||||
var gridMatrix = gridXform.WorldMatrix;
|
||||
Matrix3.Multiply(in gridMatrix, in offsetMatrix, out var matty);
|
||||
var color = iff?.Color ?? IFFComponent.IFFColor;
|
||||
var color = iff?.Color ?? Color.Gold;
|
||||
|
||||
// Others default:
|
||||
// Color.FromHex("#FFC000FF")
|
||||
// Hostile default: Color.Firebrick
|
||||
|
||||
if (ShowIFF &&
|
||||
(iff == null && IFFComponent.ShowIFFDefault ||
|
||||
@@ -278,7 +324,7 @@ public sealed class RadarControl : Control
|
||||
}
|
||||
|
||||
// Detailed view
|
||||
DrawGrid(handle, matty, gridFixtures, color);
|
||||
DrawGrid(handle, matty, fixturesComp, grid, color, true);
|
||||
|
||||
DrawDocks(handle, grid.Owner, matty);
|
||||
}
|
||||
@@ -309,9 +355,10 @@ public sealed class RadarControl : Control
|
||||
|
||||
private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3 matrix)
|
||||
{
|
||||
if (!ShowDocks) return;
|
||||
if (!ShowDocks)
|
||||
return;
|
||||
|
||||
const float DockScale = 1.2f;
|
||||
const float DockScale = 1f;
|
||||
|
||||
if (_docks.TryGetValue(uid, out var docks))
|
||||
{
|
||||
@@ -342,44 +389,139 @@ public sealed class RadarControl : Control
|
||||
verts[i] = ScalePosition(vert);
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color);
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color.WithAlpha(0.8f));
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, verts, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGrid(DrawingHandleScreen handle, Matrix3 matrix, FixturesComponent component, Color color)
|
||||
private void DrawGrid(DrawingHandleScreen handle, Matrix3 matrix, FixturesComponent fixturesComp, MapGridComponent grid, Color color, bool drawInterior)
|
||||
{
|
||||
foreach (var fixture in component.Fixtures.Values)
|
||||
var rator = grid.GetAllTilesEnumerator();
|
||||
var edges = new ValueList<Vector2>();
|
||||
var tileTris = new ValueList<Vector2>();
|
||||
|
||||
if (drawInterior)
|
||||
{
|
||||
// If the fixture has any points out of range we won't draw any of it.
|
||||
var invalid = false;
|
||||
var poly = (PolygonShape) fixture.Shape;
|
||||
var verts = new Vector2[poly.VertexCount + 1];
|
||||
var interiorTris = new ValueList<Vector2>();
|
||||
// TODO: Engine pr
|
||||
Span<Vector2> verts = new Vector2[8];
|
||||
|
||||
for (var i = 0; i < poly.VertexCount; i++)
|
||||
foreach (var fixture in fixturesComp.Fixtures.Values)
|
||||
{
|
||||
var vert = matrix.Transform(poly.Vertices[i]);
|
||||
var invalid = false;
|
||||
var poly = (PolygonShape) fixture.Shape;
|
||||
|
||||
if (vert.Length > RadarRange)
|
||||
for (var i = 0; i < poly.VertexCount; i++)
|
||||
{
|
||||
invalid = true;
|
||||
break;
|
||||
var vert = poly.Vertices[i];
|
||||
vert = new Vector2(MathF.Round(vert.X), MathF.Round(vert.Y));
|
||||
|
||||
vert = matrix.Transform(vert);
|
||||
|
||||
if (vert.Length > RadarRange)
|
||||
{
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
|
||||
verts[i] = vert;
|
||||
}
|
||||
|
||||
vert.Y = -vert.Y;
|
||||
verts[i] = ScalePosition(vert);
|
||||
if (invalid)
|
||||
continue;
|
||||
|
||||
Vector2 AdjustedVert(Vector2 vert)
|
||||
{
|
||||
if (vert.Length > RadarRange)
|
||||
{
|
||||
vert = vert.Normalized * RadarRange;
|
||||
}
|
||||
|
||||
vert.Y = -vert.Y;
|
||||
return ScalePosition(vert);
|
||||
}
|
||||
|
||||
interiorTris.Add(AdjustedVert(verts[0]));
|
||||
interiorTris.Add(AdjustedVert(verts[1]));
|
||||
interiorTris.Add(AdjustedVert(verts[3]));
|
||||
|
||||
interiorTris.Add(AdjustedVert(verts[1]));
|
||||
interiorTris.Add(AdjustedVert(verts[2]));
|
||||
interiorTris.Add(AdjustedVert(verts[3]));
|
||||
}
|
||||
|
||||
if (invalid) continue;
|
||||
|
||||
// Closed list
|
||||
verts[poly.VertexCount] = verts[0];
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, verts, color);
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, interiorTris.Span, color.WithAlpha(0.05f));
|
||||
}
|
||||
|
||||
while (rator.MoveNext(out var tileRef))
|
||||
{
|
||||
// TODO: Short-circuit interior chunk nodes
|
||||
// This can be optimised a lot more if required.
|
||||
Vector2? tileVec = null;
|
||||
|
||||
// Iterate edges and see which we can draw
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
var dir = (DirectionFlag) Math.Pow(2, i);
|
||||
var dirVec = dir.AsDir().ToIntVec();
|
||||
|
||||
if (!grid.GetTileRef(tileRef.Value.GridIndices + dirVec).Tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
Vector2 start;
|
||||
Vector2 end;
|
||||
tileVec ??= (Vector2) tileRef.Value.GridIndices * grid.TileSize;
|
||||
|
||||
// Draw line
|
||||
// Could probably rotate this but this might be faster?
|
||||
switch (dir)
|
||||
{
|
||||
case DirectionFlag.South:
|
||||
start = tileVec.Value;
|
||||
end = tileVec.Value + new Vector2(grid.TileSize, 0f);
|
||||
break;
|
||||
case DirectionFlag.East:
|
||||
start = tileVec.Value + new Vector2(grid.TileSize, 0f);
|
||||
end = tileVec.Value + new Vector2(grid.TileSize, grid.TileSize);
|
||||
break;
|
||||
case DirectionFlag.North:
|
||||
start = tileVec.Value + new Vector2(grid.TileSize, grid.TileSize);
|
||||
end = tileVec.Value + new Vector2(0f, grid.TileSize);
|
||||
break;
|
||||
case DirectionFlag.West:
|
||||
start = tileVec.Value + new Vector2(0f, grid.TileSize);
|
||||
end = tileVec.Value;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var adjustedStart = matrix.Transform(start);
|
||||
var adjustedEnd = matrix.Transform(end);
|
||||
|
||||
if (adjustedStart.Length > RadarRange || adjustedEnd.Length > RadarRange)
|
||||
continue;
|
||||
|
||||
start = ScalePosition(new Vector2(adjustedStart.X, -adjustedStart.Y));
|
||||
end = ScalePosition(new Vector2(adjustedEnd.X, -adjustedEnd.Y));
|
||||
|
||||
edges.Add(start);
|
||||
edges.Add(end);
|
||||
}
|
||||
}
|
||||
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleList, tileTris.Span, color.WithAlpha(0.05f));
|
||||
handle.DrawPrimitives(DrawPrimitiveTopology.LineList, edges.Span, color);
|
||||
}
|
||||
|
||||
private Vector2 ScalePosition(Vector2 value)
|
||||
{
|
||||
return value * MinimapScale + MidPoint;
|
||||
}
|
||||
|
||||
private Vector2 InverseScalePosition(Vector2 value)
|
||||
{
|
||||
return (value - MidPoint) / MinimapScale;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,11 @@ public sealed partial class GeneralStationRecordConsoleWindow : DefaultWindow
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", record.Fingerprint is null ? Loc.GetString("generic-not-available-shorthand") : record.Fingerprint))
|
||||
Text = Loc.GetString("general-station-record-console-record-fingerprint", ("fingerprint", record.Fingerprint ?? Loc.GetString("generic-not-available-shorthand")))
|
||||
},
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("general-station-record-console-record-dna", ("dna", record.DNA ?? Loc.GetString("generic-not-available-shorthand")))
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -40,16 +40,12 @@ public sealed class SubFloorHideSystem : SharedSubFloorHideSystem
|
||||
scannerRevealed &= !ShowAll; // no transparency for show-subfloor mode.
|
||||
|
||||
var revealed = !covered || ShowAll || scannerRevealed;
|
||||
var transparency = scannerRevealed ? component.ScannerTransparency : 1f;
|
||||
|
||||
// set visibility & color of each layer
|
||||
foreach (var layer in args.Sprite.AllLayers)
|
||||
{
|
||||
// pipe connection visuals are updated AFTER this, and may re-hide some layers
|
||||
layer.Visible = revealed;
|
||||
|
||||
if (layer.Visible)
|
||||
layer.Color = layer.Color.WithAlpha(transparency);
|
||||
}
|
||||
|
||||
// Is there some layer that is always visible?
|
||||
|
||||
10
Content.Client/SubFloor/TrayRevealedComponent.cs
Normal file
10
Content.Client/SubFloor/TrayRevealedComponent.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Content.Client.SubFloor;
|
||||
|
||||
/// <summary>
|
||||
/// Added clientside if an entity is revealed for TRay.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class TrayRevealedComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
144
Content.Client/SubFloor/TrayScannerSystem.cs
Normal file
144
Content.Client/SubFloor/TrayScannerSystem.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using Content.Client.Hands;
|
||||
using Content.Shared.SubFloor;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.SubFloor;
|
||||
|
||||
public sealed class TrayScannerSystem : SharedTrayScannerSystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private const string TRayAnimationKey = "trays";
|
||||
private const double AnimationLength = 0.5;
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
// TODO: Multiple viewports or w/e
|
||||
var player = _player.LocalPlayer?.ControlledEntity;
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
|
||||
if (!xformQuery.TryGetComponent(player, out var playerXform))
|
||||
return;
|
||||
|
||||
var playerPos = _transform.GetWorldPosition(playerXform, xformQuery);
|
||||
var playerMap = playerXform.MapID;
|
||||
var range = 0f;
|
||||
|
||||
if (TryComp<HandsComponent>(player, out var playerHands) &&
|
||||
TryComp<TrayScannerComponent>(playerHands.ActiveHandEntity, out var scanner) && scanner.Enabled)
|
||||
{
|
||||
range = scanner.Range;
|
||||
|
||||
foreach (var comp in _lookup.GetComponentsInRange<SubFloorHideComponent>(playerMap, playerPos, range))
|
||||
{
|
||||
var uid = comp.Owner;
|
||||
if (!comp.IsUnderCover || !comp.BlockAmbience | !comp.BlockInteractions)
|
||||
continue;
|
||||
|
||||
EnsureComp<TrayRevealedComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
var revealedQuery = AllEntityQuery<TrayRevealedComponent, SpriteComponent, TransformComponent>();
|
||||
var subfloorQuery = GetEntityQuery<SubFloorHideComponent>();
|
||||
|
||||
while (revealedQuery.MoveNext(out var uid, out _, out var sprite, out var xform))
|
||||
{
|
||||
var worldPos = _transform.GetWorldPosition(xform, xformQuery);
|
||||
|
||||
// Revealing
|
||||
// Add buffer range to avoid flickers.
|
||||
if (subfloorQuery.HasComponent(uid) &&
|
||||
xform.MapID != MapId.Nullspace &&
|
||||
xform.MapID == playerMap &&
|
||||
xform.Anchored &&
|
||||
range != 0f &&
|
||||
(playerPos - worldPos).Length <= range + 0.5f)
|
||||
{
|
||||
// Due to the fact client is predicting this server states will reset it constantly
|
||||
if ((!_appearance.TryGetData(uid, SubFloorVisuals.ScannerRevealed, out bool value) || !value) &&
|
||||
sprite.Color.A > SubfloorRevealAlpha)
|
||||
{
|
||||
sprite.Color = sprite.Color.WithAlpha(0f);
|
||||
}
|
||||
|
||||
SetRevealed(uid, true);
|
||||
|
||||
if (sprite.Color.A >= SubfloorRevealAlpha || _animation.HasRunningAnimation(uid, TRayAnimationKey))
|
||||
continue;
|
||||
|
||||
_animation.Play(uid, new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(AnimationLength),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Color),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), 0f),
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(SubfloorRevealAlpha), (float) AnimationLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, TRayAnimationKey);
|
||||
}
|
||||
// Hiding
|
||||
else
|
||||
{
|
||||
// Hidden completely so unreveal and reset the alpha.
|
||||
if (sprite.Color.A <= 0f)
|
||||
{
|
||||
SetRevealed(uid, false);
|
||||
RemCompDeferred<TrayRevealedComponent>(uid);
|
||||
sprite.Color = sprite.Color.WithAlpha(1f);
|
||||
continue;
|
||||
}
|
||||
|
||||
SetRevealed(uid, true);
|
||||
|
||||
if (_animation.HasRunningAnimation(uid, TRayAnimationKey))
|
||||
continue;
|
||||
|
||||
_animation.Play(uid, new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(AnimationLength),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Color),
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color, 0f),
|
||||
new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), (float) AnimationLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, TRayAnimationKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetRevealed(EntityUid uid, bool value)
|
||||
{
|
||||
_appearance.SetData(uid, SubFloorVisuals.ScannerRevealed, value);
|
||||
}
|
||||
}
|
||||
11
Content.Client/Tips/ClippyUI.xaml
Normal file
11
Content.Client/Tips/ClippyUI.xaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<tips:ClippyUI xmlns="https://spacestation14.io"
|
||||
xmlns:tips="clr-namespace:Content.Client.Tips"
|
||||
MinSize="64 64"
|
||||
Visible="False">
|
||||
<PanelContainer Name="LabelPanel" Access="Public" Visible="False" MaxWidth="300" MaxHeight="200">
|
||||
<ScrollContainer Name="ScrollingContents" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalExpand="True" VerticalExpand="True" HScrollEnabled="False" ReturnMeasure="True">
|
||||
<RichTextLabel Name="Label" Access="Public"/>
|
||||
</ScrollContainer>
|
||||
</PanelContainer>
|
||||
<SpriteView Name="Entity" Access="Public" MinSize="128 128"/>
|
||||
</tips:ClippyUI>
|
||||
53
Content.Client/Tips/ClippyUI.xaml.cs
Normal file
53
Content.Client/Tips/ClippyUI.xaml.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Client.Paper;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.Tips;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class ClippyUI : UIWidget
|
||||
{
|
||||
public ClippyState State = ClippyState.Hidden;
|
||||
|
||||
public ClippyUI()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void InitLabel(PaperVisualsComponent? visuals, IResourceCache resCache)
|
||||
{
|
||||
if (visuals == null)
|
||||
return;
|
||||
|
||||
Label.ModulateSelfOverride = visuals.FontAccentColor;
|
||||
|
||||
if (visuals.BackgroundImagePath == null)
|
||||
return;
|
||||
|
||||
LabelPanel.ModulateSelfOverride = visuals.BackgroundModulate;
|
||||
var backgroundImage = resCache.GetResource<TextureResource>(visuals.BackgroundImagePath);
|
||||
var backgroundImageMode = visuals.BackgroundImageTile ? StyleBoxTexture.StretchMode.Tile : StyleBoxTexture.StretchMode.Stretch;
|
||||
var backgroundPatchMargin = visuals.BackgroundPatchMargin;
|
||||
LabelPanel.PanelOverride = new StyleBoxTexture
|
||||
{
|
||||
Texture = backgroundImage,
|
||||
TextureScale = visuals.BackgroundScale,
|
||||
Mode = backgroundImageMode,
|
||||
PatchMarginLeft = backgroundPatchMargin.Left,
|
||||
PatchMarginBottom = backgroundPatchMargin.Bottom,
|
||||
PatchMarginRight = backgroundPatchMargin.Right,
|
||||
PatchMarginTop = backgroundPatchMargin.Top
|
||||
};
|
||||
}
|
||||
|
||||
public enum ClippyState : byte
|
||||
{
|
||||
Hidden,
|
||||
Revealing,
|
||||
Speaking,
|
||||
Hiding,
|
||||
}
|
||||
}
|
||||
244
Content.Client/Tips/ClippyUIController.cs
Normal file
244
Content.Client/Tips/ClippyUIController.cs
Normal file
@@ -0,0 +1,244 @@
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Paper;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Tips;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using static Content.Client.Tips.ClippyUI;
|
||||
|
||||
namespace Content.Client.Tips;
|
||||
|
||||
public sealed class ClippyUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IStateManager _state = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IResourceCache _resCache = default!;
|
||||
|
||||
[UISystemDependency] private readonly AudioSystem? _audio = default;
|
||||
|
||||
public const float Padding = 50;
|
||||
public static Angle WaddleRotation = Angle.FromDegrees(10);
|
||||
|
||||
private EntityUid _entity;
|
||||
private float _secondsUntilNextState;
|
||||
private int _previousStep = 0;
|
||||
private ClippyEvent? _currentMessage;
|
||||
private readonly Queue<ClippyEvent> _queuedMessages = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_conHost.RegisterCommand("local_clippy", ClippyCommand);
|
||||
UIManager.OnScreenChanged += OnScreenChanged;
|
||||
}
|
||||
|
||||
private void ClippyCommand(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
shell.WriteLine("usage: clippy <message> [entity prototype] [speak time] [animate time] [waddle]");
|
||||
return;
|
||||
}
|
||||
|
||||
var ev = new ClippyEvent(args[0]);
|
||||
|
||||
string proto;
|
||||
if (args.Length > 1)
|
||||
{
|
||||
ev.Proto = args[1];
|
||||
|
||||
if (!_protoMan.HasIndex<EntityPrototype>(ev.Proto))
|
||||
{
|
||||
shell.WriteError($"Unknown prototype: {ev.Proto}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (args.Length > 2)
|
||||
ev.SpeakTime = float.Parse(args[2]);
|
||||
|
||||
if (args.Length > 3)
|
||||
ev.SlideTime = float.Parse(args[3]);
|
||||
|
||||
if (args.Length > 4)
|
||||
ev.WaddleInterval = float.Parse(args[4]);
|
||||
|
||||
AddMessage(ev);
|
||||
}
|
||||
|
||||
public void AddMessage(ClippyEvent ev)
|
||||
{
|
||||
_queuedMessages.Enqueue(ev);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
var screen = UIManager.ActiveScreen;
|
||||
if (screen == null)
|
||||
{
|
||||
_queuedMessages.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var clippy = screen.GetOrAddWidget<ClippyUI>();
|
||||
_secondsUntilNextState -= args.DeltaSeconds;
|
||||
|
||||
if (_secondsUntilNextState <= 0)
|
||||
NextState(clippy);
|
||||
else
|
||||
{
|
||||
var pos = UpdatePosition(clippy, screen.Size, args); ;
|
||||
LayoutContainer.SetPosition(clippy, pos);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 UpdatePosition(ClippyUI clippy, Vector2 screenSize, FrameEventArgs args)
|
||||
{
|
||||
if (_currentMessage == null)
|
||||
return default;
|
||||
|
||||
var slideTime = _currentMessage.SlideTime;
|
||||
|
||||
var offset = clippy.State switch
|
||||
{
|
||||
ClippyState.Hidden => 0,
|
||||
ClippyState.Revealing => Math.Clamp(1 - _secondsUntilNextState / slideTime, 0, 1),
|
||||
ClippyState.Hiding => Math.Clamp(_secondsUntilNextState / slideTime, 0, 1),
|
||||
_ => 1,
|
||||
};
|
||||
|
||||
var waddle = _currentMessage.WaddleInterval;
|
||||
|
||||
if (_currentMessage == null
|
||||
|| waddle <= 0
|
||||
|| clippy.State == ClippyState.Hidden
|
||||
|| clippy.State == ClippyState.Speaking
|
||||
|| !EntityManager.TryGetComponent(_entity, out SpriteComponent? sprite))
|
||||
{
|
||||
return (screenSize.X - offset * (clippy.DesiredSize.X + Padding), (screenSize.Y - clippy.DesiredSize.Y) / 2);
|
||||
}
|
||||
|
||||
var numSteps = (int) Math.Ceiling(slideTime / waddle);
|
||||
var curStep = (int) Math.Floor(numSteps * offset);
|
||||
var stepSize = (clippy.DesiredSize.X + Padding) / numSteps;
|
||||
|
||||
if (curStep != _previousStep)
|
||||
{
|
||||
_previousStep = curStep;
|
||||
sprite.Rotation = sprite.Rotation > 0
|
||||
? -WaddleRotation
|
||||
: WaddleRotation;
|
||||
|
||||
if (EntityManager.TryGetComponent(_entity, out FootstepModifierComponent? step))
|
||||
{
|
||||
var audioParams = step.Sound.Params
|
||||
.AddVolume(-7f)
|
||||
.WithVariation(0.1f);
|
||||
_audio?.PlayGlobal(step.Sound, EntityUid.Invalid, audioParams);
|
||||
}
|
||||
}
|
||||
|
||||
return (screenSize.X - stepSize * curStep, (screenSize.Y - clippy.DesiredSize.Y) / 2);
|
||||
}
|
||||
|
||||
private void NextState(ClippyUI clippy)
|
||||
{
|
||||
SpriteComponent? sprite;
|
||||
switch (clippy.State)
|
||||
{
|
||||
case ClippyState.Hidden:
|
||||
if (!_queuedMessages.TryDequeue(out var next))
|
||||
return;
|
||||
|
||||
_entity = EntityManager.SpawnEntity(next.Proto ?? _cfg.GetCVar(CCVars.ClippyEntity), MapCoordinates.Nullspace);
|
||||
if (!EntityManager.TryGetComponent(_entity, out sprite))
|
||||
return;
|
||||
clippy.InitLabel(EntityManager.GetComponentOrNull<PaperVisualsComponent>(_entity), _resCache);
|
||||
|
||||
var scale = sprite.Scale;
|
||||
sprite.Scale = Vector2.One;
|
||||
clippy.Entity.Sprite = sprite;
|
||||
clippy.Entity.Scale = scale;
|
||||
|
||||
_currentMessage = next;
|
||||
_secondsUntilNextState = next.SlideTime;
|
||||
clippy.State = ClippyState.Revealing;
|
||||
_previousStep = 0;
|
||||
sprite.LayerSetAnimationTime("revealing", 0);
|
||||
sprite.LayerSetVisible("revealing", true);
|
||||
sprite.LayerSetVisible("speaking", false);
|
||||
sprite.LayerSetVisible("hiding", false);
|
||||
sprite.Rotation = 0;
|
||||
clippy.Label.SetMarkup(_currentMessage.Msg);
|
||||
clippy.LabelPanel.Visible = false;
|
||||
clippy.Visible = true;
|
||||
sprite.Visible = true;
|
||||
break;
|
||||
|
||||
case ClippyState.Revealing:
|
||||
clippy.State = ClippyState.Speaking;
|
||||
if (!EntityManager.TryGetComponent(_entity, out sprite))
|
||||
return;
|
||||
sprite.Rotation = 0;
|
||||
_previousStep = 0;
|
||||
sprite.LayerSetAnimationTime("speaking", 0);
|
||||
sprite.LayerSetVisible("revealing", false);
|
||||
sprite.LayerSetVisible("speaking", true);
|
||||
sprite.LayerSetVisible("hiding", false);
|
||||
clippy.LabelPanel.Visible = true;
|
||||
clippy.InvalidateArrange();
|
||||
clippy.InvalidateMeasure();
|
||||
if (_currentMessage != null)
|
||||
_secondsUntilNextState = _currentMessage.SpeakTime;
|
||||
|
||||
break;
|
||||
|
||||
case ClippyState.Speaking:
|
||||
clippy.State = ClippyState.Hiding;
|
||||
if (!EntityManager.TryGetComponent(_entity, out sprite))
|
||||
return;
|
||||
sprite.LayerSetAnimationTime("hiding", 0);
|
||||
sprite.LayerSetVisible("revealing", false);
|
||||
sprite.LayerSetVisible("speaking", false);
|
||||
sprite.LayerSetVisible("hiding", true);
|
||||
clippy.LabelPanel.Visible = false;
|
||||
if (_currentMessage != null)
|
||||
_secondsUntilNextState = _currentMessage.SlideTime;
|
||||
break;
|
||||
|
||||
default: // finished hiding
|
||||
|
||||
EntityManager.DeleteEntity(_entity);
|
||||
_entity = default;
|
||||
clippy.Visible = false;
|
||||
_currentMessage = null;
|
||||
_secondsUntilNextState = 0;
|
||||
clippy.State = ClippyState.Hidden;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScreenChanged((UIScreen? Old, UIScreen? New) ev)
|
||||
{
|
||||
ev.Old?.RemoveWidget<ClippyUI>();
|
||||
_currentMessage = null;
|
||||
EntityManager.DeleteEntity(_entity);
|
||||
}
|
||||
}
|
||||
20
Content.Client/Tips/TipsSystem.cs
Normal file
20
Content.Client/Tips/TipsSystem.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Content.Shared.Tips;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.Tips;
|
||||
|
||||
public sealed class TipsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IUserInterfaceManager _uiMan = default!;
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<ClippyEvent>(OnClippyEv);
|
||||
}
|
||||
|
||||
private void OnClippyEv(ClippyEvent ev)
|
||||
{
|
||||
_uiMan.GetUIController<ClippyUIController>().AddMessage(ev);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,14 @@
|
||||
<LayoutContainer Name="ViewportContainer" HorizontalExpand="True" VerticalExpand="True">
|
||||
<controls:MainViewport Name="MainViewport"/>
|
||||
</LayoutContainer>
|
||||
<menuBar:GameTopMenuBar Name="TopBar" Access="Protected" />
|
||||
<BoxContainer Name="TopLeft" Access="Protected" Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<menuBar:GameTopMenuBar Name="TopBar" Access="Protected" />
|
||||
<!-- Buffer so big votes don't skew it -->
|
||||
<Control/>
|
||||
</BoxContainer>
|
||||
<BoxContainer Name="VoteMenu" Access="Public" Margin="0 10 0 10" Orientation="Vertical"/>
|
||||
</BoxContainer>
|
||||
<widgets:GhostGui Name="Ghost" Access="Protected" />
|
||||
<hotbar:HotbarGui Name="Hotbar" Access="Protected" />
|
||||
<actions:ActionsBar Name="Actions" Access="Protected" />
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.UserInterface.Screens;
|
||||
@@ -17,7 +15,7 @@ public sealed partial class DefaultGameScreen : InGameScreen
|
||||
|
||||
SetAnchorPreset(MainViewport, LayoutPreset.Wide);
|
||||
SetAnchorPreset(ViewportContainer, LayoutPreset.Wide);
|
||||
SetAnchorAndMarginPreset(TopBar, LayoutPreset.TopLeft, margin: 10);
|
||||
SetAnchorAndMarginPreset(TopLeft, LayoutPreset.TopLeft, margin: 10);
|
||||
SetAnchorAndMarginPreset(Actions, LayoutPreset.BottomLeft, margin: 10);
|
||||
SetAnchorAndMarginPreset(Ghost, LayoutPreset.BottomWide, margin: 80);
|
||||
SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5);
|
||||
@@ -48,6 +46,6 @@ public sealed partial class DefaultGameScreen : InGameScreen
|
||||
{
|
||||
SetMarginBottom(Chat, size.X);
|
||||
SetMarginLeft(Chat, size.Y);
|
||||
SetMarginTop(Alerts, Size.X);
|
||||
SetMarginTop(Alerts, size.X);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<controls:RecordedSplitContainer Name="ScreenContainer" HorizontalExpand="True" VerticalExpand="True" SplitWidth="0" StretchDirection="TopLeft">
|
||||
<LayoutContainer Name="ViewportContainer" HorizontalExpand="True" VerticalExpand="True">
|
||||
<controls:MainViewport Name="MainViewport"/>
|
||||
<BoxContainer Name="VoteMenu" Access="Public" Orientation="Vertical"/>
|
||||
<widgets:GhostGui Name="Ghost" Access="Protected" />
|
||||
<hotbar:HotbarGui Name="Hotbar" Access="Protected" />
|
||||
<actions:ActionsBar Name="Actions" Access="Protected" />
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
@@ -18,6 +17,7 @@ public sealed partial class SeparatedChatGameScreen : InGameScreen
|
||||
SetAnchorPreset(ScreenContainer, LayoutPreset.Wide);
|
||||
SetAnchorPreset(ViewportContainer, LayoutPreset.Wide);
|
||||
SetAnchorPreset(MainViewport, LayoutPreset.Wide);
|
||||
SetAnchorAndMarginPreset(VoteMenu, LayoutPreset.TopLeft, margin: 10);
|
||||
SetAnchorAndMarginPreset(Actions, LayoutPreset.BottomLeft, margin: 10);
|
||||
SetAnchorAndMarginPreset(Ghost, LayoutPreset.BottomWide, margin: 80);
|
||||
SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5);
|
||||
|
||||
@@ -29,9 +29,18 @@ public sealed class AHelpUIController: UIController, IOnStateChanged<GameplaySta
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
|
||||
private BwoinkSystem? _bwoinkSystem;
|
||||
private MenuButton? AhelpButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.AHelpButton;
|
||||
public IAHelpUIHandler? UIHelper;
|
||||
private bool _discordRelayActive;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<SharedBwoinkSystem.BwoinkDiscordRelayUpdated>(DiscordRelayUpdated);
|
||||
}
|
||||
|
||||
public void OnStateEntered(GameplayState state)
|
||||
{
|
||||
@@ -71,7 +80,6 @@ public sealed class AHelpUIController: UIController, IOnStateChanged<GameplaySta
|
||||
EnsureUIHelper();
|
||||
}
|
||||
|
||||
|
||||
private void AHelpButtonPressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
EnsureUIHelper();
|
||||
@@ -129,6 +137,12 @@ public sealed class AHelpUIController: UIController, IOnStateChanged<GameplaySta
|
||||
UIHelper!.Receive(message);
|
||||
}
|
||||
|
||||
private void DiscordRelayUpdated(SharedBwoinkSystem.BwoinkDiscordRelayUpdated args, EntitySessionEventArgs session)
|
||||
{
|
||||
_discordRelayActive = args.DiscordRelayEnabled;
|
||||
UIHelper?.DiscordRelayChanged(_discordRelayActive);
|
||||
}
|
||||
|
||||
public void EnsureUIHelper()
|
||||
{
|
||||
var isAdmin = _adminManager.HasFlag(AdminFlags.Adminhelp);
|
||||
@@ -139,6 +153,7 @@ public sealed class AHelpUIController: UIController, IOnStateChanged<GameplaySta
|
||||
UIHelper?.Dispose();
|
||||
var ownerUserId = _playerManager.LocalPlayer!.UserId;
|
||||
UIHelper = isAdmin ? new AdminAHelpUIHandler(ownerUserId) : new UserAHelpUIHandler(ownerUserId);
|
||||
UIHelper.DiscordRelayChanged(_discordRelayActive);
|
||||
|
||||
UIHelper.SendMessageAction = (userId, textMessage) => _bwoinkSystem?.Send(userId, textMessage);
|
||||
UIHelper.OnClose += () => { SetAHelpPressed(false); };
|
||||
@@ -161,14 +176,15 @@ public sealed class AHelpUIController: UIController, IOnStateChanged<GameplaySta
|
||||
EnsureUIHelper();
|
||||
if (UIHelper!.IsOpen)
|
||||
return;
|
||||
UIHelper!.Open(localPlayer.UserId);
|
||||
UIHelper!.Open(localPlayer.UserId, _discordRelayActive);
|
||||
}
|
||||
|
||||
public void Open(NetUserId userId)
|
||||
{
|
||||
EnsureUIHelper();
|
||||
if (!UIHelper!.IsAdmin)
|
||||
return;
|
||||
UIHelper?.Open(userId);
|
||||
UIHelper?.Open(userId, _discordRelayActive);
|
||||
}
|
||||
|
||||
public void ToggleWindow()
|
||||
@@ -177,7 +193,6 @@ public sealed class AHelpUIController: UIController, IOnStateChanged<GameplaySta
|
||||
UIHelper?.ToggleWindow();
|
||||
}
|
||||
|
||||
|
||||
public void PopOut()
|
||||
{
|
||||
EnsureUIHelper();
|
||||
@@ -223,8 +238,9 @@ public interface IAHelpUIHandler : IDisposable
|
||||
public bool IsOpen { get; }
|
||||
public void Receive(SharedBwoinkSystem.BwoinkTextMessage message);
|
||||
public void Close();
|
||||
public void Open(NetUserId netUserId);
|
||||
public void Open(NetUserId netUserId, bool relayActive);
|
||||
public void ToggleWindow();
|
||||
public void DiscordRelayChanged(bool active);
|
||||
public event Action OnClose;
|
||||
public event Action OnOpen;
|
||||
public Action<NetUserId, string>? SendMessageAction { get; set; }
|
||||
@@ -298,11 +314,15 @@ public sealed class AdminAHelpUIHandler : IAHelpUIHandler
|
||||
OpenWindow();
|
||||
}
|
||||
|
||||
public void DiscordRelayChanged(bool active)
|
||||
{
|
||||
}
|
||||
|
||||
public event Action? OnClose;
|
||||
public event Action? OnOpen;
|
||||
public Action<NetUserId, string>? SendMessageAction { get; set; }
|
||||
|
||||
public void Open(NetUserId channelId)
|
||||
public void Open(NetUserId channelId, bool relayActive)
|
||||
{
|
||||
SelectChannel(channelId);
|
||||
OpenWindow();
|
||||
@@ -381,11 +401,12 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
|
||||
public bool IsOpen => _window is { Disposed: false, IsOpen: true };
|
||||
private DefaultWindow? _window;
|
||||
private BwoinkPanel? _chatPanel;
|
||||
private bool _discordRelayActive;
|
||||
|
||||
public void Receive(SharedBwoinkSystem.BwoinkTextMessage message)
|
||||
{
|
||||
DebugTools.Assert(message.UserId == _ownerId);
|
||||
EnsureInit();
|
||||
EnsureInit(_discordRelayActive);
|
||||
_chatPanel!.ReceiveLine(message);
|
||||
_window!.OpenCentered();
|
||||
}
|
||||
@@ -397,7 +418,7 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
|
||||
|
||||
public void ToggleWindow()
|
||||
{
|
||||
EnsureInit();
|
||||
EnsureInit(_discordRelayActive);
|
||||
if (_window!.IsOpen)
|
||||
{
|
||||
_window.Close();
|
||||
@@ -413,27 +434,38 @@ public sealed class UserAHelpUIHandler : IAHelpUIHandler
|
||||
{
|
||||
}
|
||||
|
||||
public void DiscordRelayChanged(bool active)
|
||||
{
|
||||
_discordRelayActive = active;
|
||||
|
||||
if (_chatPanel != null)
|
||||
{
|
||||
_chatPanel.RelayedToDiscordLabel.Visible = active;
|
||||
}
|
||||
}
|
||||
|
||||
public event Action? OnClose;
|
||||
public event Action? OnOpen;
|
||||
public Action<NetUserId, string>? SendMessageAction { get; set; }
|
||||
|
||||
public void Open(NetUserId channelId)
|
||||
public void Open(NetUserId channelId, bool relayActive)
|
||||
{
|
||||
EnsureInit();
|
||||
EnsureInit(relayActive);
|
||||
_window!.OpenCentered();
|
||||
}
|
||||
|
||||
private void EnsureInit()
|
||||
private void EnsureInit(bool relayActive)
|
||||
{
|
||||
if (_window is { Disposed: false })
|
||||
return;
|
||||
_chatPanel = new BwoinkPanel(text => SendMessageAction?.Invoke(_ownerId, text));
|
||||
_chatPanel.RelayedToDiscordLabel.Visible = relayActive;
|
||||
_window = new DefaultWindow()
|
||||
{
|
||||
TitleClass="windowTitleAlert",
|
||||
HeaderClass="windowHeaderAlert",
|
||||
Title=Loc.GetString("bwoink-user-title"),
|
||||
SetSize=(400, 200),
|
||||
MinSize=(500, 200),
|
||||
};
|
||||
_window.OnClose += () => { OnClose?.Invoke(); };
|
||||
_window.OnOpen += () => { OnOpen?.Invoke(); };
|
||||
|
||||
@@ -496,6 +496,7 @@ public sealed class ChatUIController : UIController
|
||||
if (_admin.HasFlag(AdminFlags.Admin))
|
||||
{
|
||||
FilterableChannels |= ChatChannel.Admin;
|
||||
FilterableChannels |= ChatChannel.AdminAlert;
|
||||
FilterableChannels |= ChatChannel.AdminChat;
|
||||
CanSendChannels |= ChatSelectChannel.Admin;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public sealed partial class ChannelFilterPopup : Popup
|
||||
ChatChannel.OOC,
|
||||
ChatChannel.Dead,
|
||||
ChatChannel.Admin,
|
||||
ChatChannel.AdminAlert,
|
||||
ChatChannel.AdminChat,
|
||||
ChatChannel.Server
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</PanelContainer.PanelOverride>
|
||||
|
||||
<BoxContainer Orientation="Vertical" SeparationOverride="4" HorizontalExpand="True" VerticalExpand="True">
|
||||
<OutputPanel Name="Contents" HorizontalExpand="True" VerticalExpand="True" />
|
||||
<OutputPanel Name="Contents" HorizontalExpand="True" VerticalExpand="True" Margin="8 8 8 4" />
|
||||
<controls:ChatInputBox HorizontalExpand="True" Name="ChatInput" Access="Public" Margin="2"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Info;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
using Content.Client.UserInterface.Systems.Info;
|
||||
using Content.Shared.CCVar;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -26,7 +24,7 @@ public sealed class EscapeUIController : UIController, IOnStateEntered<GameplayS
|
||||
[Dependency] private readonly ChangelogUIController _changelog = default!;
|
||||
[Dependency] private readonly InfoUIController _info = default!;
|
||||
[Dependency] private readonly OptionsUIController _options = default!;
|
||||
[UISystemDependency] private readonly GuidebookSystem? _guidebook = default!;
|
||||
[Dependency] private readonly GuidebookUIController _guidebook = default!;
|
||||
|
||||
private Options.UI.EscapeMenu? _escapeWindow;
|
||||
|
||||
@@ -102,7 +100,7 @@ public sealed class EscapeUIController : UIController, IOnStateEntered<GameplayS
|
||||
|
||||
_escapeWindow.GuidebookButton.OnPressed += _ =>
|
||||
{
|
||||
_guidebook?.OpenGuidebook();
|
||||
_guidebook.ToggleGuidebook();
|
||||
};
|
||||
|
||||
// Hide wiki button if we don't have a link for it.
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
using System.Linq;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Guidebook;
|
||||
using Content.Client.Guidebook.Controls;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Guidebook;
|
||||
|
||||
public sealed class GuidebookUIController : UIController, IOnStateEntered<LobbyState>, IOnStateEntered<GameplayState>, IOnStateExited<LobbyState>, IOnStateExited<GameplayState>, IOnSystemChanged<GuidebookSystem>
|
||||
{
|
||||
[UISystemDependency] private readonly GuidebookSystem _guidebookSystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
private GuidebookWindow? _guideWindow;
|
||||
private MenuButton? GuidebookButton => UIManager.GetActiveUIWidgetOrNull<MenuBar.Widgets.GameTopMenuBar>()?.GuidebookButton;
|
||||
|
||||
public void OnStateEntered(LobbyState state)
|
||||
{
|
||||
HandleStateEntered();
|
||||
}
|
||||
|
||||
public void OnStateEntered(GameplayState state)
|
||||
{
|
||||
HandleStateEntered();
|
||||
}
|
||||
|
||||
private void HandleStateEntered()
|
||||
{
|
||||
DebugTools.Assert(_guideWindow == null);
|
||||
|
||||
// setup window
|
||||
_guideWindow = UIManager.CreateWindow<GuidebookWindow>();
|
||||
_guideWindow.OnClose += OnWindowClosed;
|
||||
_guideWindow.OnOpen += OnWindowOpen;
|
||||
|
||||
// setup keybinding
|
||||
CommandBinds.Builder
|
||||
.Bind(ContentKeyFunctions.OpenGuidebook,
|
||||
InputCmdHandler.FromDelegate(_ => ToggleGuidebook()))
|
||||
.Register<GuidebookUIController>();
|
||||
}
|
||||
|
||||
public void OnStateExited(LobbyState state)
|
||||
{
|
||||
HandleStateExited();
|
||||
}
|
||||
|
||||
public void OnStateExited(GameplayState state)
|
||||
{
|
||||
HandleStateExited();
|
||||
}
|
||||
|
||||
private void HandleStateExited()
|
||||
{
|
||||
if (_guideWindow == null)
|
||||
return;
|
||||
|
||||
_guideWindow.OnClose -= OnWindowClosed;
|
||||
_guideWindow.OnOpen -= OnWindowOpen;
|
||||
|
||||
// shutdown
|
||||
_guideWindow.Dispose();
|
||||
_guideWindow = null;
|
||||
CommandBinds.Unregister<GuidebookUIController>();
|
||||
}
|
||||
|
||||
public void OnSystemLoaded(GuidebookSystem system)
|
||||
{
|
||||
_guidebookSystem.OnGuidebookOpen += ToggleGuidebook;
|
||||
}
|
||||
|
||||
public void OnSystemUnloaded(GuidebookSystem system)
|
||||
{
|
||||
_guidebookSystem.OnGuidebookOpen -= ToggleGuidebook;
|
||||
}
|
||||
|
||||
internal void UnloadButton()
|
||||
{
|
||||
if (GuidebookButton == null)
|
||||
return;
|
||||
|
||||
GuidebookButton.OnPressed -= GuidebookButtonOnPressed;
|
||||
}
|
||||
|
||||
internal void LoadButton()
|
||||
{
|
||||
if (GuidebookButton == null)
|
||||
return;
|
||||
|
||||
GuidebookButton.OnPressed += GuidebookButtonOnPressed;
|
||||
}
|
||||
|
||||
private void GuidebookButtonOnPressed(ButtonEventArgs obj)
|
||||
{
|
||||
ToggleGuidebook();
|
||||
}
|
||||
|
||||
private void OnWindowClosed()
|
||||
{
|
||||
if (GuidebookButton != null)
|
||||
GuidebookButton.Pressed = false;
|
||||
}
|
||||
|
||||
private void OnWindowOpen()
|
||||
{
|
||||
if (GuidebookButton != null)
|
||||
GuidebookButton.Pressed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the guidebook.
|
||||
/// </summary>
|
||||
/// <param name="guides">What guides should be shown. If not specified, this will instead list all the entries</param>
|
||||
/// <param name="rootEntries">A list of guides that should form the base of the table of contents. If not specified,
|
||||
/// this will automatically simply be a list of all guides that have no parent.</param>
|
||||
/// <param name="forceRoot">This forces a singular guide to contain all other guides. This guide will
|
||||
/// contain its own children, in addition to what would normally be the root guides if this were not
|
||||
/// specified.</param>
|
||||
/// <param name="includeChildren">Whether or not to automatically include child entries. If false, this will ONLY
|
||||
/// show the specified entries</param>
|
||||
/// <param name="selected">The guide whose contents should be displayed when the guidebook is opened</param>
|
||||
public void ToggleGuidebook(
|
||||
Dictionary<string, GuideEntry>? guides = null,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
bool includeChildren = true,
|
||||
string? selected = null)
|
||||
{
|
||||
if (_guideWindow == null)
|
||||
return;
|
||||
|
||||
if (_guideWindow.IsOpen)
|
||||
{
|
||||
_guideWindow.Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (GuidebookButton != null)
|
||||
GuidebookButton.Pressed = !_guideWindow.IsOpen;
|
||||
|
||||
if (guides == null)
|
||||
{
|
||||
guides = _prototypeManager.EnumeratePrototypes<GuideEntryPrototype>()
|
||||
.ToDictionary(x => x.ID, x => (GuideEntry) x);
|
||||
}
|
||||
else if (includeChildren)
|
||||
{
|
||||
var oldGuides = guides;
|
||||
guides = new(oldGuides);
|
||||
foreach (var guide in oldGuides.Values)
|
||||
{
|
||||
RecursivelyAddChildren(guide, guides);
|
||||
}
|
||||
}
|
||||
|
||||
_guideWindow.UpdateGuides(guides, rootEntries, forceRoot, selected);
|
||||
_guideWindow.OpenCenteredRight();
|
||||
}
|
||||
|
||||
public void ToggleGuidebook(
|
||||
List<string> guideList,
|
||||
List<string>? rootEntries = null,
|
||||
string? forceRoot = null,
|
||||
bool includeChildren = true,
|
||||
string? selected = null)
|
||||
{
|
||||
Dictionary<string, GuideEntry> guides = new();
|
||||
foreach (var guideId in guideList)
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(guideId, out var guide))
|
||||
{
|
||||
Logger.Error($"Encountered unknown guide prototype: {guideId}");
|
||||
continue;
|
||||
}
|
||||
guides.Add(guideId, guide);
|
||||
}
|
||||
|
||||
ToggleGuidebook(guides, rootEntries, forceRoot, includeChildren, selected);
|
||||
}
|
||||
|
||||
private void RecursivelyAddChildren(GuideEntry guide, Dictionary<string, GuideEntry> guides)
|
||||
{
|
||||
foreach (var childId in guide.Children)
|
||||
{
|
||||
if (guides.ContainsKey(childId))
|
||||
continue;
|
||||
|
||||
if (!_prototypeManager.TryIndex<GuideEntryPrototype>(childId, out var child))
|
||||
{
|
||||
Logger.Error($"Encountered unknown guide prototype: {childId} as a child of {guide.Id}. If the child is not a prototype, it must be directly provided.");
|
||||
continue;
|
||||
}
|
||||
|
||||
guides.Add(childId, child);
|
||||
RecursivelyAddChildren(child, guides);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.UserInterface.Systems.Actions;
|
||||
using Content.Client.UserInterface.Systems.Admin;
|
||||
using Content.Client.UserInterface.Systems.Bwoink;
|
||||
@@ -6,6 +5,7 @@ using Content.Client.UserInterface.Systems.Character;
|
||||
using Content.Client.UserInterface.Systems.Crafting;
|
||||
using Content.Client.UserInterface.Systems.EscapeMenu;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Client.UserInterface.Systems.Guidebook;
|
||||
using Content.Client.UserInterface.Systems.Inventory;
|
||||
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
|
||||
using Content.Client.UserInterface.Systems.Sandbox;
|
||||
@@ -23,6 +23,7 @@ public sealed class GameTopMenuBarUIController : UIController
|
||||
[Dependency] private readonly AHelpUIController _ahelp = default!;
|
||||
[Dependency] private readonly ActionUIController _action = default!;
|
||||
[Dependency] private readonly SandboxUIController _sandbox = default!;
|
||||
[Dependency] private readonly GuidebookUIController _guidebook = default!;
|
||||
|
||||
private GameTopMenuBar? GameTopMenuBar => UIManager.GetActiveUIWidgetOrNull<GameTopMenuBar>();
|
||||
|
||||
@@ -38,6 +39,7 @@ public sealed class GameTopMenuBarUIController : UIController
|
||||
public void UnloadButtons()
|
||||
{
|
||||
_escape.UnloadButton();
|
||||
_guidebook.UnloadButton();
|
||||
_inventory.UnloadButton();
|
||||
_admin.UnloadButton();
|
||||
_character.UnloadButton();
|
||||
@@ -50,6 +52,7 @@ public sealed class GameTopMenuBarUIController : UIController
|
||||
public void LoadButtons()
|
||||
{
|
||||
_escape.LoadButton();
|
||||
_guidebook.LoadButton();
|
||||
_inventory.LoadButton();
|
||||
_admin.LoadButton();
|
||||
_character.LoadButton();
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
Name = "MenuButtons"
|
||||
VerticalExpand="False"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Left"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
SeparationOverride="5"
|
||||
>
|
||||
@@ -23,6 +23,16 @@
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonOpenRight}"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="GuidebookButton"
|
||||
Access="Internal"
|
||||
Icon="{xe:Tex '/Textures/Interface/VerbIcons/information.svg.192dpi.png'}"
|
||||
ToolTip="{Loc 'game-hud-open-guide-menu-button-tooltip'}"
|
||||
BoundKey = "{x:Static is:ContentKeyFunctions.OpenGuidebook}"
|
||||
MinSize="42 64"
|
||||
HorizontalExpand="True"
|
||||
AppendStyleClass="{x:Static style:StyleBase.ButtonSquare}"
|
||||
/>
|
||||
<ui:MenuButton
|
||||
Name="CharacterButton"
|
||||
Access="Internal"
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="You feel like you are going to have a bad time"
|
||||
MinWidth="500">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<Label Text="Begin surgery on yourself to heal all injuries?" HorizontalExpand="True" />
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Button Name="AcceptButton" Access="Public" Text="Accept" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,14 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Surgery;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class SelfRequestWindow : DefaultWindow
|
||||
{
|
||||
public SelfRequestWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Content.Shared.Medical.Surgery;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Surgery;
|
||||
|
||||
public sealed class SurgeryRealmOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly Font _font;
|
||||
|
||||
public SurgeryRealmOverlay(IEntityManager entityManager, IEyeManager eyeManager, IResourceCache resourceCache)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
ZIndex = 200;
|
||||
_font = new VectorFont(resourceCache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.ScreenSpace;
|
||||
|
||||
protected override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (!EntitySystem.TryGet(out EntityLookupSystem? entityLookup))
|
||||
return;
|
||||
|
||||
var viewport = args.WorldAABB;
|
||||
|
||||
foreach (var heart in _entityManager.EntityQuery<SurgeryRealmHeartComponent>())
|
||||
{
|
||||
// if not on the same map, continue
|
||||
if (_entityManager.GetComponent<TransformComponent>(heart.Owner).MapID != _eyeManager.CurrentMap)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var aabb = entityLookup.GetWorldAABB(heart.Owner);
|
||||
|
||||
// if not on screen, continue
|
||||
if (!aabb.Intersects(in viewport))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var lineoffset = new Vector2(0f, 11f);
|
||||
var screenCoordinates = _eyeManager.WorldToScreen(aabb.Center +
|
||||
new Angle(-_eyeManager.CurrentEye.Rotation).RotateVec(
|
||||
aabb.TopRight - aabb.Center)) + new Vector2(1f, 7f);
|
||||
args.ScreenHandle.DrawString(_font, screenCoordinates + lineoffset * 2, $"Health: {heart.Health}", Color.OrangeRed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Client.Audio;
|
||||
using Content.Client.Instruments;
|
||||
using Content.Shared.Medical.Surgery;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Surgery;
|
||||
|
||||
public sealed class SurgeryRealmUIController : UIController
|
||||
{
|
||||
[UISystemDependency] private readonly BackgroundAudioSystem? _backgroundAudio = default!;
|
||||
|
||||
private SelfRequestWindow _selfRequestWindow = default!;
|
||||
private SurgeryRealmOverlay _overlay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_overlay = new SurgeryRealmOverlay(EntityManager, IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IResourceCache>());
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(_overlay);
|
||||
|
||||
SubscribeNetworkEvent<SurgeryRealmRequestSelfEvent>(OnSurgeryRequestSelf);
|
||||
|
||||
// pjb dont look
|
||||
SubscribeNetworkEvent<SurgeryRealmStartEvent>((msg, args) => _ = OnSurgeryRealmStart(msg, args));
|
||||
}
|
||||
|
||||
private void OnSurgeryRequestSelf(SurgeryRealmRequestSelfEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
_selfRequestWindow?.Dispose();
|
||||
_selfRequestWindow = new SelfRequestWindow();
|
||||
_selfRequestWindow.OpenCentered();
|
||||
_selfRequestWindow.AcceptButton.OnPressed += buttonArgs =>
|
||||
{
|
||||
_selfRequestWindow.Dispose();
|
||||
IoCManager.Resolve<IEntityNetworkManager>().SendSystemNetworkMessage(new SurgeryRealmAcceptSelfEvent());
|
||||
};
|
||||
}
|
||||
|
||||
private async Task OnSurgeryRealmStart(SurgeryRealmStartEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
for (var i = 500; i < 10000; i += 500)
|
||||
{
|
||||
// I LOVE ambience code
|
||||
Timer.Spawn(i, () => _backgroundAudio?.EndAmbience());
|
||||
}
|
||||
|
||||
var file = IoCManager.Resolve<IResourceManager>()
|
||||
.ContentFileRead(new ResourcePath("/Audio/Surgery/midilovania.mid"));
|
||||
await using var memStream = new MemoryStream((int) file.Length);
|
||||
// 100ms delay is due to a race condition or something idk.
|
||||
// While we're waiting, load it into memory.
|
||||
await Task.WhenAll(Timer.Delay(100), file.CopyToAsync(memStream));
|
||||
|
||||
EntitySystem.Get<InstrumentSystem>()
|
||||
.OpenMidi(msg.Camera, memStream.GetBuffer().AsSpan(0, (int)memStream.Length));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Shared._Afterlight.ThirdDimension;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -77,16 +79,28 @@ public sealed class ViewportUIController : UIController
|
||||
|
||||
base.FrameUpdate(e);
|
||||
|
||||
|
||||
Viewport.Viewport.Eye = _eyeManager.CurrentEye;
|
||||
|
||||
// verify that the current eye is not "null". Fuck IEyeManager.
|
||||
|
||||
var ent = _playerMan.LocalPlayer?.ControlledEntity;
|
||||
if (_entMan.TryGetComponent(ent, out ZViewComponent? view))
|
||||
{
|
||||
Viewport.Viewport.LowerEyes = view.DownViewEnts.Select(x =>
|
||||
{
|
||||
var eye = _entMan.GetComponent<EyeComponent>(x);
|
||||
eye.Rotation = _eyeManager.CurrentEye.Rotation;
|
||||
eye.DrawFov = false; // We're z leveling, no FoV.
|
||||
return eye.Eye!;
|
||||
}).ToArray();
|
||||
}
|
||||
if (_eyeManager.CurrentEye.Position != default || ent == null)
|
||||
return;
|
||||
|
||||
_entMan.TryGetComponent(ent, out EyeComponent? eye);
|
||||
|
||||
|
||||
if (eye?.Eye == _eyeManager.CurrentEye
|
||||
&& _entMan.GetComponent<TransformComponent>(ent.Value).WorldPosition == default)
|
||||
return; // nothing to worry about, the player is just in null space... actually that is probably a problem?
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
using Content.Client.UserInterface.Screens;
|
||||
using Content.Client.UserInterface.Systems.Gameplay;
|
||||
using Content.Client.Voting;
|
||||
using Robust.Client.UserInterface.Controllers;
|
||||
|
||||
namespace Content.Client.UserInterface.Systems.Vote;
|
||||
|
||||
public sealed class VoteUIController : UIController
|
||||
{
|
||||
[Dependency] private readonly IVoteManager _votes = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
var gameplayStateLoad = UIManager.GetUIController<GameplayStateLoadController>();
|
||||
gameplayStateLoad.OnScreenLoad += OnScreenLoad;
|
||||
gameplayStateLoad.OnScreenUnload += OnScreenUnload;
|
||||
}
|
||||
|
||||
private void OnScreenLoad()
|
||||
{
|
||||
switch (UIManager.ActiveScreen)
|
||||
{
|
||||
case DefaultGameScreen game:
|
||||
_votes.SetPopupContainer(game.VoteMenu);
|
||||
break;
|
||||
case SeparatedChatGameScreen separated:
|
||||
_votes.SetPopupContainer(separated.VoteMenu);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScreenUnload()
|
||||
{
|
||||
_votes.ClearPopupContainer();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -24,7 +25,9 @@ namespace Content.Client.Viewport
|
||||
|
||||
// Internal viewport creation is deferred.
|
||||
private IClydeViewport? _viewport;
|
||||
private List<IClydeViewport> _lowerPorts = new();
|
||||
private IEye? _eye;
|
||||
private IEye[] _lowerEyes = new IEye[] {};
|
||||
private Vector2i _viewportSize;
|
||||
private int _curRenderScale;
|
||||
private ScalingViewportStretchMode _stretchMode = ScalingViewportStretchMode.Bilinear;
|
||||
@@ -50,6 +53,27 @@ namespace Content.Client.Viewport
|
||||
}
|
||||
}
|
||||
|
||||
public IEye[] LowerEyes
|
||||
{
|
||||
get => _lowerEyes;
|
||||
set
|
||||
{
|
||||
var old = value;
|
||||
_lowerEyes = value;
|
||||
if (old.Length != value.Length)
|
||||
{
|
||||
InvalidateViewport();
|
||||
Logger.Debug("Eyes updated..");
|
||||
}
|
||||
|
||||
|
||||
foreach (var (eye, port) in _lowerEyes.Zip(_lowerPorts))
|
||||
{
|
||||
port.Eye = eye;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The size, in unscaled pixels, of the internal viewport.
|
||||
/// </summary>
|
||||
@@ -137,6 +161,11 @@ namespace Content.Client.Viewport
|
||||
|
||||
_viewport!.Render();
|
||||
|
||||
foreach (var viewport in _lowerPorts)
|
||||
{
|
||||
viewport.Render();
|
||||
}
|
||||
|
||||
if (_queuedScreenshots.Count != 0)
|
||||
{
|
||||
var callbacks = _queuedScreenshots.ToArray();
|
||||
@@ -155,6 +184,10 @@ namespace Content.Client.Viewport
|
||||
var drawBox = GetDrawBox();
|
||||
var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition);
|
||||
_viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal);
|
||||
foreach (var viewport in _lowerPorts.AsEnumerable().Reverse())
|
||||
{
|
||||
handle.DrawTextureRect(viewport.RenderTarget.Texture, drawBox);
|
||||
}
|
||||
handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox);
|
||||
_viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal);
|
||||
}
|
||||
@@ -224,6 +257,23 @@ namespace Content.Client.Viewport
|
||||
{
|
||||
Filter = StretchMode == ScalingViewportStretchMode.Bilinear,
|
||||
});
|
||||
_viewport.ClearColor = Color.Blue.WithAlpha(0.02f);
|
||||
|
||||
_lowerPorts.Clear();
|
||||
for (var i = 0; i < _lowerEyes.Length; i++)
|
||||
{
|
||||
_lowerPorts.Add(_clyde.CreateViewport(
|
||||
ViewportSize * renderScale,
|
||||
new TextureSampleParameters
|
||||
{
|
||||
Filter = StretchMode == ScalingViewportStretchMode.Bilinear,
|
||||
}));
|
||||
_lowerPorts[i].RenderScale = (renderScale, renderScale);
|
||||
_lowerPorts[i].ClearColor = Color.Blue.WithAlpha(0.02f);
|
||||
|
||||
_lowerPorts[i].Eye = _lowerEyes[i];
|
||||
_lowerPorts[i].Eye!.Zoom = _lowerPorts[i].Eye!.Zoom * (1.02f + i * 0.02f);
|
||||
}
|
||||
|
||||
_viewport.RenderScale = (renderScale, renderScale);
|
||||
|
||||
@@ -241,6 +291,11 @@ namespace Content.Client.Viewport
|
||||
{
|
||||
_viewport?.Dispose();
|
||||
_viewport = null;
|
||||
foreach (var port in _lowerPorts)
|
||||
{
|
||||
port.Dispose();
|
||||
}
|
||||
_lowerPorts = new();
|
||||
}
|
||||
|
||||
public MapCoordinates ScreenToMap(Vector2 coords)
|
||||
@@ -291,7 +346,7 @@ namespace Content.Client.Viewport
|
||||
|
||||
private void EnsureViewportCreated()
|
||||
{
|
||||
if (_viewport == null)
|
||||
if (_viewport == null || _lowerPorts.Count != _lowerEyes.Length)
|
||||
{
|
||||
RegenerateViewport();
|
||||
}
|
||||
|
||||
@@ -95,6 +95,13 @@ namespace Content.Client.Voting
|
||||
}
|
||||
|
||||
_popupContainer = container;
|
||||
SetVoteData();
|
||||
}
|
||||
|
||||
private void SetVoteData()
|
||||
{
|
||||
if (_popupContainer == null)
|
||||
return;
|
||||
|
||||
foreach (var (vId, vote) in _votes)
|
||||
{
|
||||
@@ -121,9 +128,13 @@ namespace Content.Client.Voting
|
||||
@new = true;
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>()
|
||||
.PlayGlobal("/Audio/Effects/voteding.ogg", Filter.Local(), false);
|
||||
// TODO: It would be better if this used a per-state container, i.e. a container
|
||||
// for the lobby and each HUD layout.
|
||||
SetPopupContainer(_userInterfaceManager.WindowRoot);
|
||||
|
||||
// Refresh
|
||||
var container = _popupContainer;
|
||||
ClearPopupContainer();
|
||||
|
||||
if (container != null)
|
||||
SetPopupContainer(container);
|
||||
|
||||
// New vote from the server.
|
||||
var vote = new ActiveVote(voteId)
|
||||
@@ -142,6 +153,7 @@ namespace Content.Client.Voting
|
||||
_votes.Remove(voteId);
|
||||
if (_votePopups.TryGetValue(voteId, out var toRemove))
|
||||
{
|
||||
|
||||
toRemove.Orphan();
|
||||
_votePopups.Remove(voteId);
|
||||
}
|
||||
|
||||
@@ -153,10 +153,13 @@ public sealed partial class MeleeWeaponSystem
|
||||
_animation.Play(animationUid, GetFadeAnimation(sprite, 0.05f, 0.15f), FadeAnimationKey);
|
||||
break;
|
||||
case WeaponArcAnimation.None:
|
||||
var (mapPos, mapRot) = _transform.GetWorldPositionRotation(userXform, GetEntityQuery<TransformComponent>());
|
||||
var xform = Transform(animationUid);
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var (mapPos, mapRot) = _transform.GetWorldPositionRotation(userXform, xformQuery);
|
||||
var xform = xformQuery.GetComponent(animationUid);
|
||||
xform.AttachToGridOrMap();
|
||||
_transform.SetWorldPosition(xform, mapPos + (mapRot - userXform.LocalRotation).RotateVec(localPos));
|
||||
var worldPos = mapPos + (mapRot - userXform.LocalRotation).RotateVec(localPos);
|
||||
var newLocalPos = _transform.GetInvWorldMatrix(xform.ParentUid, xformQuery).Transform(worldPos);
|
||||
_transform.SetLocalPositionNoLerp(xform, newLocalPos);
|
||||
if (arcComponent.Fadeout)
|
||||
_animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey);
|
||||
break;
|
||||
|
||||
@@ -169,7 +169,8 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
});
|
||||
}
|
||||
|
||||
public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? Entity, IShootable Shootable)> ammo, EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid? user = null)
|
||||
public override void Shoot(EntityUid gunUid, GunComponent gun, List<(EntityUid? Entity, IShootable Shootable)> ammo,
|
||||
EntityCoordinates fromCoordinates, EntityCoordinates toCoordinates, EntityUid? user = null, bool throwItems = false)
|
||||
{
|
||||
// Rather than splitting client / server for every ammo provider it's easier
|
||||
// to just delete the spawned entities. This is for programmer sanity despite the wasted perf.
|
||||
@@ -178,6 +179,16 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
|
||||
foreach (var (ent, shootable) in ammo)
|
||||
{
|
||||
if (throwItems)
|
||||
{
|
||||
Recoil(user, direction);
|
||||
if (ent!.Value.IsClientSide())
|
||||
Del(ent.Value);
|
||||
else
|
||||
RemComp<AmmoComponent>(ent.Value);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (shootable)
|
||||
{
|
||||
case CartridgeAmmoComponent cartridge:
|
||||
@@ -247,6 +258,9 @@ public sealed partial class GunSystem : SharedGunSystem
|
||||
else
|
||||
return;
|
||||
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return;
|
||||
|
||||
var ent = Spawn(message.Prototype, coordinates);
|
||||
|
||||
var effectXform = Transform(ent);
|
||||
|
||||
7
Content.Client/Wires/WiresSystem.cs
Normal file
7
Content.Client/Wires/WiresSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Client.Wires;
|
||||
|
||||
public sealed class WiresSystem : SharedWiresSystem
|
||||
{
|
||||
}
|
||||
17
Content.Client/zlevels/ZViewSystem.cs
Normal file
17
Content.Client/zlevels/ZViewSystem.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Content.Shared._Afterlight.ThirdDimension;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.zlevels;
|
||||
|
||||
public sealed class ZViewSystem : SharedZViewSystem
|
||||
{
|
||||
public override EntityUid SpawnViewEnt(EntityUid source, MapCoordinates loc)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool CanSetup(EntityUid source)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ public static class PoolManager
|
||||
(CVars.ThreadParallelCount.Name, "1"),
|
||||
(CCVars.GameRoleTimers.Name, "false"),
|
||||
(CCVars.CargoShuttles.Name, "false"),
|
||||
(CCVars.ArrivalsShuttles.Name, "false"),
|
||||
(CCVars.EmergencyShuttleEnabled.Name, "false"),
|
||||
(CCVars.ProcgenPreload.Name, "false"),
|
||||
// @formatter:on
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Content.IntegrationTests;
|
||||
public sealed class PoolManagerTestEventHandler
|
||||
{
|
||||
// This value is completely arbitrary.
|
||||
private static TimeSpan MaximumTotalTestingTimeLimit => TimeSpan.FromMinutes(15);
|
||||
private static TimeSpan MaximumTotalTestingTimeLimit => TimeSpan.FromMinutes(20);
|
||||
private static TimeSpan HardStopTimeLimit => MaximumTotalTestingTimeLimit.Add(TimeSpan.FromMinutes(1));
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Stacks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
@@ -82,13 +83,57 @@ public sealed class CargoTest
|
||||
if (entManager.TryGetComponent<StaticPriceComponent>(ent, out var staticpricecomp))
|
||||
{
|
||||
Assert.That(staticpricecomp.Price, Is.EqualTo(0),
|
||||
$"The prototype {proto} have a StackPriceComponent and StaticPriceComponent whose values are not compatible with each other.");
|
||||
$"The prototype {proto} has a StackPriceComponent and StaticPriceComponent whose values are not compatible with each other.");
|
||||
}
|
||||
}
|
||||
|
||||
if (entManager.HasComponent<StackComponent>(ent))
|
||||
{
|
||||
if (entManager.TryGetComponent<StaticPriceComponent>(ent, out var staticpricecomp))
|
||||
{
|
||||
Assert.That(staticpricecomp.Price, Is.EqualTo(0),
|
||||
$"The prototype {proto} has a StackComponent and StaticPriceComponent whose values are not compatible with each other.");
|
||||
}
|
||||
}
|
||||
|
||||
entManager.DeleteEntity(ent);
|
||||
}
|
||||
mapManager.DeleteMap(mapId);
|
||||
});
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task StackPrice()
|
||||
{
|
||||
const string StackProto = @"
|
||||
- type: entity
|
||||
id: A
|
||||
|
||||
- type: stack
|
||||
id: StackProto
|
||||
spawn: A
|
||||
|
||||
- type: entity
|
||||
id: StackEnt
|
||||
components:
|
||||
- type: StackPrice
|
||||
price: 20
|
||||
- type: Stack
|
||||
stackType: StackProto
|
||||
count: 5
|
||||
";
|
||||
|
||||
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, ExtraPrototypes = StackProto});
|
||||
var server = pairTracker.Pair.Server;
|
||||
|
||||
var entManager = server.ResolveDependency<IEntityManager>();
|
||||
var priceSystem = entManager.System<PricingSystem>();
|
||||
|
||||
var ent = entManager.SpawnEntity("StackEnt", MapCoordinates.Nullspace);
|
||||
var price = priceSystem.GetPrice(ent);
|
||||
Assert.That(price, Is.EqualTo(100.0));
|
||||
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace Content.IntegrationTests.Tests
|
||||
Assert.IsNotNull(stationConfig, $"{entManager.ToPrettyString(station)} had null StationConfig.");
|
||||
var shuttlePath = stationConfig.EmergencyShuttlePath.ToString();
|
||||
var shuttle = mapLoader.LoadGrid(shuttleMap, shuttlePath);
|
||||
Assert.That(shuttle != null && shuttleSystem.TryFTLDock(entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}");
|
||||
Assert.That(shuttle != null && shuttleSystem.TryFTLDock(shuttle.Value, entManager.GetComponent<ShuttleComponent>(shuttle.Value), targetGrid.Value), $"Unable to dock {shuttlePath} to {mapProto}");
|
||||
|
||||
mapManager.DeleteMap(shuttleMap);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Content.IntegrationTests.Tests
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
config.SetCVar(CCVars.GameLobbyEnabled, true);
|
||||
config.SetCVar(CCVars.EmergencyShuttleTransitTime, 1f);
|
||||
config.SetCVar(CCVars.EmergencyShuttleMinTransitTime, 1f);
|
||||
config.SetCVar(CCVars.EmergencyShuttleDockTime, 1f);
|
||||
|
||||
roundEndSystem.DefaultCooldownDuration = TimeSpan.FromMilliseconds(100);
|
||||
@@ -116,7 +116,7 @@ namespace Content.IntegrationTests.Tests
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
config.SetCVar(CCVars.GameLobbyEnabled, false);
|
||||
config.SetCVar(CCVars.EmergencyShuttleTransitTime, CCVars.EmergencyShuttleTransitTime.DefaultValue);
|
||||
config.SetCVar(CCVars.EmergencyShuttleMinTransitTime, CCVars.EmergencyShuttleMinTransitTime.DefaultValue);
|
||||
config.SetCVar(CCVars.EmergencyShuttleDockTime, CCVars.EmergencyShuttleDockTime.DefaultValue);
|
||||
|
||||
roundEndSystem.DefaultCooldownDuration = TimeSpan.FromSeconds(30);
|
||||
|
||||
@@ -78,6 +78,8 @@ namespace Content.IntegrationTests.Tests
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
|
||||
const string TestMap = "Maps/bagel.yml";
|
||||
|
||||
/// <summary>
|
||||
/// Loads the default map, runs it for 5 ticks, then assert that it did not change.
|
||||
/// </summary>
|
||||
@@ -91,13 +93,13 @@ namespace Content.IntegrationTests.Tests
|
||||
|
||||
MapId mapId = default;
|
||||
|
||||
// Load saltern.yml as uninitialized map, and save it to ensure it's up to date.
|
||||
// Load bagel.yml as uninitialized map, and save it to ensure it's up to date.
|
||||
server.Post(() =>
|
||||
{
|
||||
mapId = mapManager.CreateMap();
|
||||
mapManager.AddUninitializedMap(mapId);
|
||||
mapManager.SetMapPaused(mapId, true);
|
||||
mapLoader.LoadMap(mapId, "Maps/bagel.yml");
|
||||
mapLoader.LoadMap(mapId, TestMap);
|
||||
mapLoader.SaveMap(mapId, "load save ticks save 1.yml");
|
||||
});
|
||||
|
||||
@@ -145,6 +147,78 @@ namespace Content.IntegrationTests.Tests
|
||||
TestContext.Error.WriteLine(twoTmp);
|
||||
}
|
||||
});
|
||||
|
||||
await server.WaitPost(() => mapManager.DeleteMap(mapId));
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the same uninitialized map at slightly different times, and then checks that they are the same
|
||||
/// when getting saved.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should ensure that entities do not perform randomization prior to initialization and should prevents
|
||||
/// bugs like the one discussed in github.com/space-wizards/RobustToolbox/issues/3870. This test is somewhat
|
||||
/// similar to <see cref="LoadSaveTicksSaveBagel"/> and <see cref="SaveLoadSave"/>, but neither of these
|
||||
/// caught the mentioned bug.
|
||||
/// </remarks>
|
||||
[Test]
|
||||
public async Task LoadTickLoadBagel()
|
||||
{
|
||||
await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true});
|
||||
var server = pairTracker.Pair.Server;
|
||||
|
||||
var mapLoader = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<MapLoaderSystem>();
|
||||
var mapManager = server.ResolveDependency<IMapManager>();
|
||||
var userData = server.ResolveDependency<IResourceManager>().UserData;
|
||||
|
||||
MapId mapId = default;
|
||||
const string fileA = "/load tick load a.yml";
|
||||
const string fileB = "/load tick load b.yml";
|
||||
string yamlA;
|
||||
string yamlB;
|
||||
|
||||
// Load & save the first map
|
||||
server.Post(() =>
|
||||
{
|
||||
mapId = mapManager.CreateMap();
|
||||
mapManager.AddUninitializedMap(mapId);
|
||||
mapManager.SetMapPaused(mapId, true);
|
||||
mapLoader.LoadMap(mapId, TestMap);
|
||||
mapLoader.SaveMap(mapId, fileA);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
await using (var stream = userData.Open(new ResourcePath(fileA), FileMode.Open))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
yamlA = await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
server.RunTicks(5);
|
||||
|
||||
// Load & save the second map
|
||||
server.Post(() =>
|
||||
{
|
||||
mapManager.DeleteMap(mapId);
|
||||
mapManager.CreateMap(mapId);
|
||||
mapManager.AddUninitializedMap(mapId);
|
||||
mapManager.SetMapPaused(mapId, true);
|
||||
mapLoader.LoadMap(mapId, TestMap);
|
||||
mapLoader.SaveMap(mapId, fileB);
|
||||
});
|
||||
|
||||
await server.WaitIdleAsync();
|
||||
|
||||
await using (var stream = userData.Open(new ResourcePath(fileB), FileMode.Open))
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
yamlB = await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
Assert.That(yamlA, Is.EqualTo(yamlB));
|
||||
|
||||
await server.WaitPost(() => mapManager.DeleteMap(mapId));
|
||||
await pairTracker.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@ using Robust.Shared.Prototypes;
|
||||
using Content.Server.Storage.Components;
|
||||
using Content.Server.VendingMachines;
|
||||
using Content.Server.VendingMachines.Restock;
|
||||
using Content.Server.Wires;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Content.Shared.Wires;
|
||||
using Content.Server.Wires;
|
||||
|
||||
namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
@@ -188,7 +189,7 @@ namespace Content.IntegrationTests.Tests
|
||||
VendingMachineComponent machineComponent;
|
||||
VendingMachineRestockComponent restockRightComponent;
|
||||
VendingMachineRestockComponent restockWrongComponent;
|
||||
WiresComponent machineWires;
|
||||
WiresPanelComponent machineWiresPanel;
|
||||
|
||||
var testMap = await PoolManager.CreateTestMap(pairTracker);
|
||||
|
||||
@@ -206,7 +207,7 @@ namespace Content.IntegrationTests.Tests
|
||||
Assert.True(entityManager.TryGetComponent(machine, out machineComponent!), $"Machine has no {nameof(VendingMachineComponent)}");
|
||||
Assert.True(entityManager.TryGetComponent(packageRight, out restockRightComponent!), $"Correct package has no {nameof(VendingMachineRestockComponent)}");
|
||||
Assert.True(entityManager.TryGetComponent(packageWrong, out restockWrongComponent!), $"Wrong package has no {nameof(VendingMachineRestockComponent)}");
|
||||
Assert.True(entityManager.TryGetComponent(machine, out machineWires!), $"Machine has no {nameof(WiresComponent)}");
|
||||
Assert.True(entityManager.TryGetComponent(machine, out machineWiresPanel!), $"Machine has no {nameof(WiresPanelComponent)}");
|
||||
|
||||
var systemRestock = entitySystemManager.GetEntitySystem<VendingMachineRestockSystem>();
|
||||
var systemMachine = entitySystemManager.GetEntitySystem<VendingMachineSystem>();
|
||||
@@ -215,8 +216,9 @@ namespace Content.IntegrationTests.Tests
|
||||
Assert.That(systemRestock.TryAccessMachine(packageRight, restockRightComponent, machineComponent, user, machine), Is.False, "Right package is able to restock without opened access panel");
|
||||
Assert.That(systemRestock.TryAccessMachine(packageWrong, restockWrongComponent, machineComponent, user, machine), Is.False, "Wrong package is able to restock without opened access panel");
|
||||
|
||||
var systemWires = entitySystemManager.GetEntitySystem<WiresSystem>();
|
||||
// Open the panel.
|
||||
machineWires.IsPanelOpen = true;
|
||||
systemWires.TogglePanel(machine, machineWiresPanel, true);
|
||||
|
||||
// Test that the right package works for the right machine.
|
||||
Assert.That(systemRestock.TryAccessMachine(packageRight, restockRightComponent, machineComponent, user, machine), Is.True, "Correct package is unable to restock with access panel opened");
|
||||
|
||||
@@ -418,8 +418,31 @@ namespace Content.Server.Database
|
||||
public string? Name { get; set; } = default!;
|
||||
}
|
||||
|
||||
// Used by SS14.Admin
|
||||
public interface IBanCommon<TUnban> where TUnban : IUnbanCommon
|
||||
{
|
||||
int Id { get; set; }
|
||||
Guid? UserId { get; set; }
|
||||
(IPAddress, int)? Address { get; set; }
|
||||
byte[]? HWId { get; set; }
|
||||
DateTime BanTime { get; set; }
|
||||
DateTime? ExpirationTime { get; set; }
|
||||
string Reason { get; set; }
|
||||
Guid? BanningAdmin { get; set; }
|
||||
TUnban? Unban { get; set; }
|
||||
}
|
||||
|
||||
// Used by SS14.Admin
|
||||
public interface IUnbanCommon
|
||||
{
|
||||
int Id { get; set; }
|
||||
int BanId { get; set; }
|
||||
Guid? UnbanningAdmin { get; set; }
|
||||
DateTime UnbanTime { get; set; }
|
||||
}
|
||||
|
||||
[Table("server_ban")]
|
||||
public class ServerBan
|
||||
public class ServerBan : IBanCommon<ServerUnban>
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
@@ -439,7 +462,7 @@ namespace Content.Server.Database
|
||||
}
|
||||
|
||||
[Table("server_unban")]
|
||||
public class ServerUnban
|
||||
public class ServerUnban : IUnbanCommon
|
||||
{
|
||||
[Column("unban_id")] public int Id { get; set; }
|
||||
|
||||
@@ -489,7 +512,7 @@ namespace Content.Server.Database
|
||||
}
|
||||
|
||||
[Table("server_role_ban")]
|
||||
public sealed class ServerRoleBan
|
||||
public sealed class ServerRoleBan : IBanCommon<ServerRoleUnban>
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
@@ -509,7 +532,7 @@ namespace Content.Server.Database
|
||||
}
|
||||
|
||||
[Table("server_role_unban")]
|
||||
public sealed class ServerRoleUnban
|
||||
public sealed class ServerRoleUnban : IUnbanCommon
|
||||
{
|
||||
[Column("role_unban_id")] public int Id { get; set; }
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.AME.Components;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Explosion.EntitySystems;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
@@ -27,6 +29,8 @@ namespace Content.Server.AME
|
||||
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
|
||||
public AMEControllerComponent? MasterController => _masterController;
|
||||
|
||||
private readonly List<AMEShieldComponent> _cores = new();
|
||||
@@ -133,10 +137,21 @@ namespace Content.Server.AME
|
||||
if (instability != 0)
|
||||
{
|
||||
overloading = true;
|
||||
var integrityCheck = 100;
|
||||
foreach(AMEShieldComponent core in _cores)
|
||||
{
|
||||
var oldIntegrity = core.CoreIntegrity;
|
||||
core.CoreIntegrity -= instability;
|
||||
|
||||
if (oldIntegrity > 95
|
||||
&& core.CoreIntegrity <= 95
|
||||
&& core.CoreIntegrity < integrityCheck)
|
||||
integrityCheck = core.CoreIntegrity;
|
||||
}
|
||||
|
||||
// Admin alert
|
||||
if (integrityCheck != 100 && _masterController != null)
|
||||
_chat.SendAdminAlert($"AME overloading: {_entMan.ToPrettyString(_masterController.Owner)}");
|
||||
}
|
||||
}
|
||||
// Note the float conversions. The maths will completely fail if not done using floats.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Logs;
|
||||
using Content.Server.Administration.Systems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Mind.Components;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.Power.Components;
|
||||
@@ -20,6 +22,7 @@ namespace Content.Server.AME.Components
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _sysMan = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
|
||||
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(AMEControllerUiKey.Key);
|
||||
private bool _injecting;
|
||||
@@ -183,6 +186,10 @@ namespace Content.Server.AME.Components
|
||||
|
||||
if (msg.Button == UiButton.ToggleInjection)
|
||||
_adminLogger.Add(LogType.Action, LogImpact.Extreme, $"{_entities.ToPrettyString(mindComponent.Owner):player} has set the AME to {humanReadableState}");
|
||||
|
||||
// Admin alert
|
||||
if (GetCoreCount() * 2 == InjectionAmount - 2 && msg.Button == UiButton.IncreaseFuel)
|
||||
_chat.SendAdminAlert(player, $"increased AME over safe limit to {InjectionAmount}", mindComponent);
|
||||
}
|
||||
|
||||
GetAMENodeGroup()?.UpdateCoreVisuals();
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.Abilities.Boxer
|
||||
{
|
||||
/// <summary>
|
||||
/// Added to the boxer on spawn.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class BoxerComponent : Component
|
||||
{
|
||||
[DataField("modifiers", required: true)]
|
||||
public DamageModifierSet UnarmedModifiers = default!;
|
||||
|
||||
[DataField("rangeBonus")]
|
||||
public float RangeBonus = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Damage modifier with boxing glove stam damage.
|
||||
/// </summary>
|
||||
[DataField("boxingGlovesModifier")]
|
||||
public float BoxingGlovesModifier = 1.75f;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Content.Shared.Damage;
|
||||
|
||||
namespace Content.Server.Abilities.Boxer
|
||||
{
|
||||
/// <summary>
|
||||
/// Boxer gets a bonus for these, and their fists, but not other unarmed weapons.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class BoxingGlovesComponent : Component
|
||||
{}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
using Content.Shared.Damage.Events;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
namespace Content.Server.Abilities.Boxer
|
||||
{
|
||||
public sealed class BoxingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedContainerSystem _containerSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<BoxerComponent, ComponentInit>(OnInit);
|
||||
SubscribeLocalEvent<BoxerComponent, MeleeHitEvent>(OnMeleeHit);
|
||||
SubscribeLocalEvent<BoxingGlovesComponent, StaminaMeleeHitEvent>(OnStamHit);
|
||||
}
|
||||
|
||||
private void OnInit(EntityUid uid, BoxerComponent component, ComponentInit args)
|
||||
{
|
||||
if (TryComp<MeleeWeaponComponent>(uid, out var meleeComp))
|
||||
meleeComp.Range *= component.RangeBonus;
|
||||
}
|
||||
private void OnMeleeHit(EntityUid uid, BoxerComponent component, MeleeHitEvent args)
|
||||
{
|
||||
args.ModifiersList.Add(component.UnarmedModifiers);
|
||||
}
|
||||
|
||||
private void OnStamHit(EntityUid uid, BoxingGlovesComponent component, StaminaMeleeHitEvent args)
|
||||
{
|
||||
if (!_containerSystem.TryGetContainingContainer(uid, out var equipee))
|
||||
return;
|
||||
|
||||
if (TryComp<BoxerComponent>(equipee.Owner, out var boxer))
|
||||
args.Multiplier *= boxer.BoxingGlovesModifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,19 +5,20 @@ using System.Text;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
|
||||
namespace Content.Server.Administration.Commands
|
||||
{
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
public sealed class BanCommand : IConsoleCommand
|
||||
public sealed class BanCommand : LocalizedCommands
|
||||
{
|
||||
public string Command => "ban";
|
||||
public string Description => Loc.GetString("cmd-ban-desc");
|
||||
public string Help => Loc.GetString("cmd-ban-help", ("Command", Command));
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
public override string Command => "ban";
|
||||
|
||||
public override async void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = shell.Player as IPlayerSession;
|
||||
var plyMgr = IoCManager.Resolve<IPlayerManager>();
|
||||
@@ -54,7 +55,7 @@ namespace Content.Server.Administration.Commands
|
||||
var located = await locator.LookupIdByNameOrIdAsync(target);
|
||||
if (located == null)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-ban-player"));
|
||||
shell.WriteError(LocalizationManager.GetString("cmd-ban-player"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ namespace Content.Server.Administration.Commands
|
||||
|
||||
if (player != null && player.UserId == targetUid)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-ban-self"));
|
||||
shell.WriteLine(LocalizationManager.GetString("cmd-ban-self"));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,43 +101,42 @@ namespace Content.Server.Administration.Commands
|
||||
|
||||
var response = new StringBuilder($"Banned {target} with reason \"{reason}\"");
|
||||
|
||||
response.Append(expires == null ?
|
||||
" permanently."
|
||||
: $" until {expires}");
|
||||
response.Append(expires == null ? " permanently." : $" until {expires}");
|
||||
|
||||
shell.WriteLine(response.ToString());
|
||||
|
||||
if (plyMgr.TryGetSessionById(targetUid, out var targetPlayer))
|
||||
{
|
||||
targetPlayer.ConnectedClient.Disconnect(banDef.DisconnectMessage);
|
||||
var message = banDef.FormatBanMessage(_cfg, LocalizationManager);
|
||||
targetPlayer.ConnectedClient.Disconnect(message);
|
||||
}
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var playerMgr = IoCManager.Resolve<IPlayerManager>();
|
||||
var options = playerMgr.ServerSessions.Select(c => c.Name).OrderBy(c => c).ToArray();
|
||||
return CompletionResult.FromHintOptions(options, Loc.GetString("cmd-ban-hint"));
|
||||
return CompletionResult.FromHintOptions(options, LocalizationManager.GetString("cmd-ban-hint"));
|
||||
}
|
||||
|
||||
if (args.Length == 2)
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-ban-hint-reason"));
|
||||
return CompletionResult.FromHint(LocalizationManager.GetString("cmd-ban-hint-reason"));
|
||||
|
||||
if (args.Length == 3)
|
||||
{
|
||||
var durations = new CompletionOption[]
|
||||
{
|
||||
new("0", Loc.GetString("cmd-ban-hint-duration-1")),
|
||||
new("1440", Loc.GetString("cmd-ban-hint-duration-2")),
|
||||
new("4320", Loc.GetString("cmd-ban-hint-duration-3")),
|
||||
new("10080", Loc.GetString("cmd-ban-hint-duration-4")),
|
||||
new("20160", Loc.GetString("cmd-ban-hint-duration-5")),
|
||||
new("43800", Loc.GetString("cmd-ban-hint-duration-6")),
|
||||
new("0", LocalizationManager.GetString("cmd-ban-hint-duration-1")),
|
||||
new("1440", LocalizationManager.GetString("cmd-ban-hint-duration-2")),
|
||||
new("4320", LocalizationManager.GetString("cmd-ban-hint-duration-3")),
|
||||
new("10080", LocalizationManager.GetString("cmd-ban-hint-duration-4")),
|
||||
new("20160", LocalizationManager.GetString("cmd-ban-hint-duration-5")),
|
||||
new("43800", LocalizationManager.GetString("cmd-ban-hint-duration-6")),
|
||||
};
|
||||
|
||||
return CompletionResult.FromHintOptions(durations, Loc.GetString("cmd-ban-hint-duration"));
|
||||
return CompletionResult.FromHintOptions(durations, LocalizationManager.GetString("cmd-ban-hint-duration"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class GamePrototypeLoadManager : IGamePrototypeLoadManager
|
||||
{
|
||||
_netManager.RegisterNetMessage<GamePrototypeLoadMessage>(ClientLoadsPrototype);
|
||||
_netManager.Connected += NetManagerOnConnected;
|
||||
//_replay.OnRecordingStarted += OnStartReplayRecording;
|
||||
_replay.OnRecordingStarted += OnStartReplayRecording;
|
||||
}
|
||||
|
||||
private void OnStartReplayRecording((MappingDataNode, List<object>) initReplayData)
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class NetworkResourceManager : SharedNetworkResourceManager
|
||||
_cfgManager.OnValueChanged(CCVars.ResourceUploadingStoreEnabled, value => StoreUploaded = value, true);
|
||||
|
||||
AutoDelete(_cfgManager.GetCVar(CCVars.ResourceUploadingStoreDeletionDays));
|
||||
//_replay.OnRecordingStarted += OnStartReplayRecording;
|
||||
_replay.OnRecordingStarted += OnStartReplayRecording;
|
||||
}
|
||||
|
||||
private void OnStartReplayRecording((MappingDataNode, List<object>) initReplayData)
|
||||
|
||||
@@ -83,6 +83,15 @@ namespace Content.Server.Administration.Systems
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerInfo? GetCachedPlayerInfo(NetUserId? netUserId)
|
||||
{
|
||||
if (netUserId == null)
|
||||
return null;
|
||||
|
||||
_playerList.TryGetValue(netUserId.Value, out var value);
|
||||
return value ?? null;
|
||||
}
|
||||
|
||||
private void OnIdentityChanged(IdentityChangedEvent ev)
|
||||
{
|
||||
if (!TryComp<ActorComponent>(ev.CharacterEntity, out var actor))
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed class AdminTestArenaSystem : EntitySystem
|
||||
}
|
||||
|
||||
ArenaMap[admin.UserId] = _mapManager.GetMapEntityId(_mapManager.CreateMap());
|
||||
var grids = _map.LoadMap(Comp<MapComponent>(ArenaMap[admin.UserId]).WorldMap, ArenaMapPath);
|
||||
var grids = _map.LoadMap(Comp<MapComponent>(ArenaMap[admin.UserId]).MapId, ArenaMapPath);
|
||||
ArenaGrid[admin.UserId] = grids.Count == 0 ? null : grids[0];
|
||||
|
||||
return (ArenaMap[admin.UserId], ArenaGrid[admin.UserId]);
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Cargo.Components;
|
||||
using Content.Server.Cargo.Systems;
|
||||
using Content.Server.Damage.Components;
|
||||
using Content.Server.Doors.Components;
|
||||
using Content.Server.Doors.Systems;
|
||||
using Content.Server.Hands.Components;
|
||||
@@ -123,7 +124,7 @@ public sealed partial class AdminVerbSystem
|
||||
args.Verbs.Add(rejuvenate);
|
||||
}
|
||||
|
||||
if (!_godmodeSystem.HasGodmode(args.Target))
|
||||
if (!HasComp<GodmodeComponent>(args.Target))
|
||||
{
|
||||
Verb makeIndestructible = new()
|
||||
{
|
||||
@@ -695,7 +696,7 @@ public sealed partial class AdminVerbSystem
|
||||
{
|
||||
if (_adminManager.HasAdminFlag(player, AdminFlags.Mapping))
|
||||
{
|
||||
if (_mapManager.IsMapPaused(map.WorldMap))
|
||||
if (_mapManager.IsMapPaused(map.MapId))
|
||||
{
|
||||
Verb unpauseMap = new()
|
||||
{
|
||||
@@ -704,7 +705,7 @@ public sealed partial class AdminVerbSystem
|
||||
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/play.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_mapManager.SetMapPaused(map.WorldMap, false);
|
||||
_mapManager.SetMapPaused(map.MapId, false);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-unpause-map-description"),
|
||||
@@ -721,7 +722,7 @@ public sealed partial class AdminVerbSystem
|
||||
Icon = new SpriteSpecifier.Texture(new ResourcePath("/Textures/Interface/AdminActions/pause.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_mapManager.SetMapPaused(map.WorldMap, true);
|
||||
_mapManager.SetMapPaused(map.MapId, true);
|
||||
},
|
||||
Impact = LogImpact.Extreme,
|
||||
Message = Loc.GetString("admin-trick-pause-map-description"),
|
||||
|
||||
@@ -109,6 +109,8 @@ namespace Content.Server.Administration.Systems
|
||||
{
|
||||
_webhookUrl = url;
|
||||
|
||||
RaiseNetworkEvent(new BwoinkDiscordRelayUpdated(!string.IsNullOrWhiteSpace(url)));
|
||||
|
||||
if (url == string.Empty)
|
||||
return;
|
||||
|
||||
@@ -395,13 +397,11 @@ namespace Content.Server.Administration.Systems
|
||||
_messageQueues[msg.UserId].Enqueue(GenerateAHelpMessage(senderSession.Name, str, !personalChannel, admins.Count == 0));
|
||||
}
|
||||
|
||||
if (admins.Count != 0)
|
||||
if (admins.Count != 0 || sendsWebhook)
|
||||
return;
|
||||
|
||||
// No admin online, let the player know
|
||||
var systemText = sendsWebhook ?
|
||||
Loc.GetString("bwoink-system-starmute-message-no-other-users-webhook") :
|
||||
Loc.GetString("bwoink-system-starmute-message-no-other-users");
|
||||
var systemText = Loc.GetString("bwoink-system-starmute-message-no-other-users");
|
||||
var starMuteMsg = new BwoinkTextMessage(message.UserId, SystemUserId, systemText);
|
||||
RaiseNetworkEvent(starMuteMsg, senderSession.ConnectedClient);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Content.Server.Alert.Click
|
||||
if (entManager.TryGetComponent(player, out PilotComponent? pilotComponent) &&
|
||||
pilotComponent.Console != null)
|
||||
{
|
||||
entManager.System<ShuttleConsoleSystem>().RemovePilot(pilotComponent);
|
||||
entManager.System<ShuttleConsoleSystem>().RemovePilot(player, pilotComponent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.AlertLevel;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -13,6 +14,7 @@ public sealed class AlertLevelDisplaySystem : EntitySystem
|
||||
{
|
||||
SubscribeLocalEvent<AlertLevelChangedEvent>(OnAlertChanged);
|
||||
SubscribeLocalEvent<AlertLevelDisplayComponent, ComponentInit>(OnDisplayInit);
|
||||
SubscribeLocalEvent<AlertLevelDisplayComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
}
|
||||
|
||||
private void OnAlertChanged(AlertLevelChangedEvent args)
|
||||
@@ -23,7 +25,7 @@ public sealed class AlertLevelDisplaySystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisplayInit(EntityUid uid, AlertLevelDisplayComponent component, ComponentInit args)
|
||||
private void OnDisplayInit(EntityUid uid, AlertLevelDisplayComponent alertLevelDisplay, ComponentInit args)
|
||||
{
|
||||
if (TryComp(uid, out AppearanceComponent? appearance))
|
||||
{
|
||||
@@ -34,4 +36,11 @@ public sealed class AlertLevelDisplaySystem : EntitySystem
|
||||
}
|
||||
}
|
||||
}
|
||||
private void OnPowerChanged(EntityUid uid, AlertLevelDisplayComponent alertLevelDisplay, ref PowerChangedEvent args)
|
||||
{
|
||||
if (!TryComp(uid, out AppearanceComponent? appearance))
|
||||
return;
|
||||
|
||||
_appearance.SetData(uid, AlertLevelDisplay.Powered, args.Powered, appearance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,15 +157,14 @@ public sealed partial class AnomalySystem
|
||||
Audio.PlayPvs(component.GeneratingFinishedSound, uid);
|
||||
|
||||
var message = Loc.GetString("anomaly-generator-announcement");
|
||||
_radio.SendRadioMessage(uid, message, _prototype.Index<RadioChannelPrototype>(component.ScienceChannel));
|
||||
_radio.SendRadioMessage(uid, message, _prototype.Index<RadioChannelPrototype>(component.ScienceChannel), uid);
|
||||
}
|
||||
|
||||
private void UpdateGenerator()
|
||||
{
|
||||
foreach (var (active, gen) in EntityQuery<GeneratingAnomalyGeneratorComponent, AnomalyGeneratorComponent>())
|
||||
var query = EntityQueryEnumerator<GeneratingAnomalyGeneratorComponent, AnomalyGeneratorComponent>();
|
||||
while (query.MoveNext(out var ent, out var active, out var gen))
|
||||
{
|
||||
var ent = active.Owner;
|
||||
|
||||
if (Timing.CurTime < active.EndTime)
|
||||
continue;
|
||||
active.AudioStream?.Stop();
|
||||
|
||||
@@ -27,41 +27,45 @@ public sealed partial class AnomalySystem
|
||||
|
||||
private void OnScannerAnomalyShutdown(ref AnomalyShutdownEvent args)
|
||||
{
|
||||
foreach (var component in EntityQuery<AnomalyScannerComponent>())
|
||||
var query = EntityQueryEnumerator<AnomalyScannerComponent>();
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (component.ScannedAnomaly != args.Anomaly)
|
||||
continue;
|
||||
_ui.TryCloseAll(component.Owner, AnomalyScannerUiKey.Key);
|
||||
_ui.TryCloseAll(uid, AnomalyScannerUiKey.Key);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScannerAnomalySeverityChanged(ref AnomalySeverityChangedEvent args)
|
||||
{
|
||||
foreach (var component in EntityQuery<AnomalyScannerComponent>())
|
||||
var query = EntityQueryEnumerator<AnomalyScannerComponent>();
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (component.ScannedAnomaly != args.Anomaly)
|
||||
continue;
|
||||
UpdateScannerUi(component.Owner, component);
|
||||
UpdateScannerUi(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScannerAnomalyStabilityChanged(ref AnomalyStabilityChangedEvent args)
|
||||
{
|
||||
foreach (var component in EntityQuery<AnomalyScannerComponent>())
|
||||
var query = EntityQueryEnumerator<AnomalyScannerComponent>();
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (component.ScannedAnomaly != args.Anomaly)
|
||||
continue;
|
||||
UpdateScannerUi(component.Owner, component);
|
||||
UpdateScannerUi(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnScannerAnomalyHealthChanged(ref AnomalyHealthChangedEvent args)
|
||||
{
|
||||
foreach (var component in EntityQuery<AnomalyScannerComponent>())
|
||||
var query = EntityQueryEnumerator<AnomalyScannerComponent>();
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (component.ScannedAnomaly != args.Anomaly)
|
||||
continue;
|
||||
UpdateScannerUi(component.Owner, component);
|
||||
UpdateScannerUi(uid, component);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,10 +100,9 @@ public sealed partial class AnomalySystem
|
||||
|
||||
private void OnVesselAnomalyShutdown(ref AnomalyShutdownEvent args)
|
||||
{
|
||||
foreach (var component in EntityQuery<AnomalyVesselComponent>())
|
||||
var query = EntityQueryEnumerator<AnomalyVesselComponent>();
|
||||
while (query.MoveNext(out var ent, out var component))
|
||||
{
|
||||
var ent = component.Owner;
|
||||
|
||||
if (args.Anomaly != component.Anomaly)
|
||||
continue;
|
||||
|
||||
@@ -118,9 +117,9 @@ public sealed partial class AnomalySystem
|
||||
|
||||
private void OnVesselAnomalyStabilityChanged(ref AnomalyStabilityChangedEvent args)
|
||||
{
|
||||
foreach (var component in EntityQuery<AnomalyVesselComponent>())
|
||||
var query = EntityQueryEnumerator<AnomalyVesselComponent>();
|
||||
while (query.MoveNext(out var ent, out var component))
|
||||
{
|
||||
var ent = component.Owner;
|
||||
if (args.Anomaly != component.Anomaly)
|
||||
continue;
|
||||
|
||||
@@ -171,9 +170,9 @@ public sealed partial class AnomalySystem
|
||||
|
||||
private void UpdateVessels()
|
||||
{
|
||||
foreach (var vessel in EntityQuery<AnomalyVesselComponent>())
|
||||
var query = EntityQueryEnumerator<AnomalyVesselComponent>();
|
||||
while (query.MoveNext(out var vesselEnt, out var vessel))
|
||||
{
|
||||
var vesselEnt = vessel.Owner;
|
||||
if (vessel.Anomaly is not { } anomUid)
|
||||
continue;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user