Compare commits
371 Commits
ed-20-09-2
...
ed-18-10-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
237716b353 | ||
|
|
f14177bf86 | ||
|
|
9a8d30022a | ||
|
|
ebca84d28f | ||
|
|
5b4a7f6edc | ||
|
|
a83d6f0d87 | ||
|
|
5d89812abc | ||
|
|
31d39b36ae | ||
|
|
dc47bb2283 | ||
|
|
d259191d3d | ||
|
|
519a6b2474 | ||
|
|
4425b77c90 | ||
|
|
796764d755 | ||
|
|
e5ad32fe93 | ||
|
|
648505dc15 | ||
|
|
af72f2e17c | ||
|
|
40959cf422 | ||
|
|
a6fc5e6ace | ||
|
|
e98af125dc | ||
|
|
bc64676747 | ||
|
|
e7b7e2270d | ||
|
|
740288eb96 | ||
|
|
93c7bdc134 | ||
|
|
3e078ab3e0 | ||
|
|
ac16a05fef | ||
|
|
327466a6e2 | ||
|
|
6d99597349 | ||
|
|
844b4d4616 | ||
|
|
fc1c709d44 | ||
|
|
5a41cc81b3 | ||
|
|
d450b613d6 | ||
|
|
b657aba2e1 | ||
|
|
ddaa0e83c6 | ||
|
|
1dbbf315c7 | ||
|
|
b9df2a495a | ||
|
|
357e998cbb | ||
|
|
384ff7e8f6 | ||
|
|
1ca7456363 | ||
|
|
dc2899c274 | ||
|
|
6f9b4f4226 | ||
|
|
28f576dae8 | ||
|
|
1366f6b405 | ||
|
|
0e0887bd49 | ||
|
|
1f04117edf | ||
|
|
922dd0bce6 | ||
|
|
33042b00d0 | ||
|
|
38fd54a1bf | ||
|
|
34df781668 | ||
|
|
f22f9e39c5 | ||
|
|
eecbfb63a0 | ||
|
|
6b10e00da4 | ||
|
|
d2c5aa74b4 | ||
|
|
a6532a2801 | ||
|
|
a6c468b697 | ||
|
|
46a2eb545e | ||
|
|
35fc1b4037 | ||
|
|
867efe8b5b | ||
|
|
126c1786de | ||
|
|
386e59b462 | ||
|
|
eb4e422cb0 | ||
|
|
2287df13bd | ||
|
|
db64ad9c4d | ||
|
|
644d7ab682 | ||
|
|
98c921aa94 | ||
|
|
21ea4290a5 | ||
|
|
c55241355e | ||
|
|
35baaacbce | ||
|
|
ec4acdebbf | ||
|
|
94c8018ff3 | ||
|
|
fbb6f17add | ||
|
|
88f060d51a | ||
|
|
8b65186bfa | ||
|
|
9e054c1f9d | ||
|
|
423d394af1 | ||
|
|
295e63193c | ||
|
|
570c166517 | ||
|
|
7edd1c6d52 | ||
|
|
c0d67429ab | ||
|
|
41cf2b22ff | ||
|
|
ab1e071fdf | ||
|
|
1c41d3381f | ||
|
|
a8982b88af | ||
|
|
568fb235fa | ||
|
|
bc76cd876f | ||
|
|
3e85d28e9d | ||
|
|
c68e5a667c | ||
|
|
b603233480 | ||
|
|
052d7c4d2e | ||
|
|
2d644095e3 | ||
|
|
440742f8f7 | ||
|
|
719f78956b | ||
|
|
4ad4fd42b1 | ||
|
|
58c8a07d2c | ||
|
|
8961dd35a5 | ||
|
|
9b4df5b452 | ||
|
|
8b14e2534d | ||
|
|
c55e311c7e | ||
|
|
612732fb84 | ||
|
|
edc83b2fab | ||
|
|
7b39d146d3 | ||
|
|
ea3b14faac | ||
|
|
5a229e7a2b | ||
|
|
dd12ac1f8a | ||
|
|
c4c786f08d | ||
|
|
542817807d | ||
|
|
b364894228 | ||
|
|
936917cd05 | ||
|
|
2df61e4e71 | ||
|
|
95f20f13dc | ||
|
|
2a07e4666a | ||
|
|
cfc723e3f8 | ||
|
|
4ca2a2bb8f | ||
|
|
b050e8332e | ||
|
|
360585276d | ||
|
|
c0d8d58629 | ||
|
|
42a1e02261 | ||
|
|
20aa14e4f7 | ||
|
|
8833cf624a | ||
|
|
ec8b7b238a | ||
|
|
24f2d4c317 | ||
|
|
f3fe5af240 | ||
|
|
bfb2f60090 | ||
|
|
23f4bc1d7a | ||
|
|
d82c666e80 | ||
|
|
f79de12509 | ||
|
|
6c08ca60f3 | ||
|
|
6a1815aeaf | ||
|
|
ba68fb2ddb | ||
|
|
a73780bc57 | ||
|
|
3b682d4d68 | ||
|
|
edc110907e | ||
|
|
5681db31a9 | ||
|
|
64e637905a | ||
|
|
bad7359aeb | ||
|
|
2f286afd95 | ||
|
|
55ae02f320 | ||
|
|
db198d2813 | ||
|
|
38f59d2038 | ||
|
|
717f82f9da | ||
|
|
bc059f0b6d | ||
|
|
c419e58300 | ||
|
|
7cf04dcb20 | ||
|
|
dd15a6862c | ||
|
|
5c0b127456 | ||
|
|
16325007d5 | ||
|
|
f6ceaa7d10 | ||
|
|
4aed728207 | ||
|
|
3f94c73bdb | ||
|
|
d0c4d5a93a | ||
|
|
cc9202bbb2 | ||
|
|
1dc9541aed | ||
|
|
6bc383c0ef | ||
|
|
f1f1fc1dc3 | ||
|
|
6b49a510d1 | ||
|
|
d806db264a | ||
|
|
4cd9e41469 | ||
|
|
4d9e657f6f | ||
|
|
a5840b925b | ||
|
|
1b9d77a760 | ||
|
|
9f93931057 | ||
|
|
0dc7982109 | ||
|
|
0a7b23cd4d | ||
|
|
8b692d118d | ||
|
|
bed968465c | ||
|
|
9f5b6af82d | ||
|
|
a371de16d3 | ||
|
|
dd376e4e84 | ||
|
|
caac678fa6 | ||
|
|
7485411ab2 | ||
|
|
dca61c009d | ||
|
|
b5a7326160 | ||
|
|
7d4ea967e0 | ||
|
|
4e860952d7 | ||
|
|
bbf84d0346 | ||
|
|
560c7acc52 | ||
|
|
34adcd5e2a | ||
|
|
558b38e51d | ||
|
|
b9e90f50ca | ||
|
|
88ccc191b9 | ||
|
|
4bcf3c3c0e | ||
|
|
1df3ed177b | ||
|
|
0d26bb0320 | ||
|
|
bc461b91a5 | ||
|
|
2e03787b6c | ||
|
|
f46f5e2835 | ||
|
|
bee66eddef | ||
|
|
b1e69ee84d | ||
|
|
b8d7733640 | ||
|
|
b9b0c10361 | ||
|
|
af3ea5253a | ||
|
|
c2704165d3 | ||
|
|
bf80f3abb8 | ||
|
|
fe2145d3b9 | ||
|
|
c2bc821e55 | ||
|
|
eeadc75b0a | ||
|
|
038201bfde | ||
|
|
622f1a23f3 | ||
|
|
d8263f0298 | ||
|
|
4491550a5e | ||
|
|
fd1f4a3929 | ||
|
|
a92c623672 | ||
|
|
0f729bca0d | ||
|
|
9c2fe85d47 | ||
|
|
548973bc1f | ||
|
|
5a585f70e5 | ||
|
|
b2dad9ff90 | ||
|
|
92c5c873d4 | ||
|
|
6459f7156b | ||
|
|
d2ed93ed62 | ||
|
|
e0a3d3d91a | ||
|
|
7168959929 | ||
|
|
0dedc9d91a | ||
|
|
693ba93e44 | ||
|
|
31c45d6169 | ||
|
|
d1812c11fd | ||
|
|
52e85fe726 | ||
|
|
55366dc594 | ||
|
|
95a159f950 | ||
|
|
6b6a96e8d6 | ||
|
|
89dbef7461 | ||
|
|
e36d735064 | ||
|
|
75bc6d4eac | ||
|
|
4bca634ff6 | ||
|
|
241be37185 | ||
|
|
f3e185c063 | ||
|
|
042f0d3f47 | ||
|
|
057de0cb10 | ||
|
|
87932bce57 | ||
|
|
b883f79660 | ||
|
|
efd989435e | ||
|
|
49d4e4c465 | ||
|
|
50395e25cf | ||
|
|
07637bd691 | ||
|
|
f3e9209af5 | ||
|
|
9d75a228c0 | ||
|
|
dc4d638ce9 | ||
|
|
f1bd0be772 | ||
|
|
1450d76337 | ||
|
|
594aad0fa9 | ||
|
|
d0bb408678 | ||
|
|
95104de117 | ||
|
|
20bb9ede11 | ||
|
|
5e9a0c9d9d | ||
|
|
468e3c9f69 | ||
|
|
ecdb02650b | ||
|
|
31db37e826 | ||
|
|
24140d8c8a | ||
|
|
8e27ef22ed | ||
|
|
0456d40bef | ||
|
|
16c46af6c5 | ||
|
|
aab423667e | ||
|
|
0a3273a436 | ||
|
|
a7e29f2878 | ||
|
|
b6845defa0 | ||
|
|
caf34be616 | ||
|
|
c4b8260f23 | ||
|
|
3d2aadde1f | ||
|
|
ad6c5a1ce9 | ||
|
|
b2cb813f54 | ||
|
|
dd7884ed40 | ||
|
|
5e162e776d | ||
|
|
8a2c69d18d | ||
|
|
ee393a1cd8 | ||
|
|
d3ff4d5401 | ||
|
|
f8514e7815 | ||
|
|
1e466579fa | ||
|
|
932af69c31 | ||
|
|
b78156ac08 | ||
|
|
1aea1ae76f | ||
|
|
8ed779bc02 | ||
|
|
8f06155028 | ||
|
|
72acce5200 | ||
|
|
4f5255bdcb | ||
|
|
49f177299e | ||
|
|
1b81ce4b53 | ||
|
|
cd761eae69 | ||
|
|
6a11dd04d4 | ||
|
|
2ea54b1913 | ||
|
|
3cc7fd405b | ||
|
|
5f5bed8fa7 | ||
|
|
3501d49029 | ||
|
|
d32c42f754 | ||
|
|
29e56becb8 | ||
|
|
24ed1f71f6 | ||
|
|
c5d62ce751 | ||
|
|
3e92eb1910 | ||
|
|
2955dd5009 | ||
|
|
c4d12df04c | ||
|
|
e964e9c98e | ||
|
|
6d5ac1e9b6 | ||
|
|
1567af6f07 | ||
|
|
a9b5e39fb0 | ||
|
|
00002fa07f | ||
|
|
99101f2e2c | ||
|
|
b81c7a478e | ||
|
|
fba67979c8 | ||
|
|
2d42a6bf98 | ||
|
|
7aae8c0099 | ||
|
|
1a601c49a5 | ||
|
|
d74d44acff | ||
|
|
24c412f16e | ||
|
|
937940bcc7 | ||
|
|
3e1c067c41 | ||
|
|
b32bdbf8e9 | ||
|
|
d5d6fb51aa | ||
|
|
30ac40f088 | ||
|
|
0093fce5b4 | ||
|
|
90d19367f8 | ||
|
|
fdfbd74bcb | ||
|
|
e490b69b4e | ||
|
|
bac7093f84 | ||
|
|
9b9853439c | ||
|
|
cd8ba1d6b3 | ||
|
|
37a8c23f79 | ||
|
|
2e37b1fb2f | ||
|
|
67b34cf8cf | ||
|
|
3c0be539ec | ||
|
|
eb9047982a | ||
|
|
854bfd27cb | ||
|
|
ccadcc9781 | ||
|
|
1468cbdb8a | ||
|
|
75ff65d108 | ||
|
|
59a8f4445d | ||
|
|
94ad76fd07 | ||
|
|
3acf6b93a1 | ||
|
|
3fc9f96b75 | ||
|
|
bdd0561254 | ||
|
|
550c423181 | ||
|
|
b129629405 | ||
|
|
9c905cd58f | ||
|
|
c2f0626e02 | ||
|
|
1c3cfeeb35 | ||
|
|
0c5a053ae4 | ||
|
|
c2a201d998 | ||
|
|
cc7e5e0150 | ||
|
|
6958789f37 | ||
|
|
4f77709eed | ||
|
|
1c839da604 | ||
|
|
84062da128 | ||
|
|
e6e166405d | ||
|
|
7ceb2d2507 | ||
|
|
d4a5bc8d6b | ||
|
|
a8686b3597 | ||
|
|
2c9a3020ab | ||
|
|
8a924c84ae | ||
|
|
ba24ebfb4f | ||
|
|
64aab9e41d | ||
|
|
58119bc3f7 | ||
|
|
55b7e3ce0e | ||
|
|
6d4177567e | ||
|
|
9fad1ab14e | ||
|
|
697f4f5ef9 | ||
|
|
ed83593948 | ||
|
|
489938cdb1 | ||
|
|
b55366cf9b | ||
|
|
8322b1c2d1 | ||
|
|
a1237910c5 | ||
|
|
54e4cfecfe | ||
|
|
2bceaad785 | ||
|
|
974c08596b | ||
|
|
675a9197f2 | ||
|
|
eca63a4603 | ||
|
|
61089355f0 | ||
|
|
870bacbcac | ||
|
|
6fc4e9682c | ||
|
|
60887dd2e5 | ||
|
|
337af483ca | ||
|
|
9afc786573 | ||
|
|
dce537df07 | ||
|
|
21bd9df477 | ||
|
|
92be69a5ab |
20
.github/workflows/CP14Publish.yml
vendored
Normal file
20
.github/workflows/CP14Publish.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Publish
|
||||
|
||||
concurrency:
|
||||
group: publish
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Send POST-request
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.BUILD_HOST }}
|
||||
username: ${{ secrets.BUILD_USER }}
|
||||
password: ${{ secrets.BUILD_PASS }}
|
||||
port: 22
|
||||
script: sh update.sh &> /dev/null
|
||||
22
.github/workflows/publish.yml
vendored
22
.github/workflows/publish.yml
vendored
@@ -5,8 +5,8 @@ concurrency:
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 10 * * *'
|
||||
# schedule:
|
||||
# - cron: '0 10 * * *'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -41,21 +41,10 @@ jobs:
|
||||
- name: Package client
|
||||
run: dotnet run --project Content.Packaging client --no-wipe-release
|
||||
|
||||
- name: Upload build artifact
|
||||
id: artifact-upload-step
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build
|
||||
path: release/*.zip
|
||||
compression-level: 0
|
||||
retention-days: 0
|
||||
|
||||
- name: Publish version
|
||||
run: Tools/publish_github_artifact.py
|
||||
run: Tools/publish_multi_request.py
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
|
||||
ARTIFACT_ID: ${{ steps.artifact-upload-step.outputs.artifact-id }}
|
||||
GITHUB_REPOSITORY: ${{ vars.GITHUB_REPOSITORY }}
|
||||
|
||||
- name: Publish changelog (Discord)
|
||||
@@ -68,8 +57,3 @@ jobs:
|
||||
run: Tools/actions_changelog_rss.py
|
||||
env:
|
||||
CHANGELOG_RSS_KEY: ${{ secrets.CHANGELOG_RSS_KEY }}
|
||||
|
||||
- uses: geekyeggo/delete-artifact@v5
|
||||
if: always()
|
||||
with:
|
||||
name: build
|
||||
|
||||
@@ -51,6 +51,29 @@ namespace Content.Client.Actions
|
||||
SubscribeLocalEvent<EntityWorldTargetActionComponent, ComponentHandleState>(OnEntityWorldTargetHandleState);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
|
||||
var worldActionQuery = EntityQueryEnumerator<WorldTargetActionComponent>();
|
||||
while (worldActionQuery.MoveNext(out var uid, out var action))
|
||||
{
|
||||
UpdateAction(uid, action);
|
||||
}
|
||||
|
||||
var instantActionQuery = EntityQueryEnumerator<InstantActionComponent>();
|
||||
while (instantActionQuery.MoveNext(out var uid, out var action))
|
||||
{
|
||||
UpdateAction(uid, action);
|
||||
}
|
||||
|
||||
var entityActionQuery = EntityQueryEnumerator<EntityTargetActionComponent>();
|
||||
while (entityActionQuery.MoveNext(out var uid, out var action))
|
||||
{
|
||||
UpdateAction(uid, action);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInstantHandleState(EntityUid uid, InstantActionComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not InstantActionComponentState state)
|
||||
@@ -95,6 +118,8 @@ namespace Content.Client.Actions
|
||||
component.Icon = state.Icon;
|
||||
component.IconOn = state.IconOn;
|
||||
component.IconColor = state.IconColor;
|
||||
component.OriginalIconColor = state.OriginalIconColor;
|
||||
component.DisabledIconColor = state.DisabledIconColor;
|
||||
component.Keywords.Clear();
|
||||
component.Keywords.UnionWith(state.Keywords);
|
||||
component.Enabled = state.Enabled;
|
||||
@@ -125,6 +150,8 @@ namespace Content.Client.Actions
|
||||
if (!ResolveActionData(actionId, ref action))
|
||||
return;
|
||||
|
||||
action.IconColor = action.Charges < 1 ? action.DisabledIconColor : action.OriginalIconColor;
|
||||
|
||||
base.UpdateAction(actionId, action);
|
||||
if (_playerManager.LocalEntity != action.AttachedEntity)
|
||||
return;
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Content.Client.Actions.UI
|
||||
{
|
||||
var duration = Cooldown.Value.End - Cooldown.Value.Start;
|
||||
|
||||
if (!FormattedMessage.TryFromMarkup($"[color=#a10505]{(int) duration.TotalSeconds} sec cooldown ({(int) timeLeft.TotalSeconds + 1} sec remaining)[/color]", out var markup))
|
||||
if (!FormattedMessage.TryFromMarkup(Loc.GetString("ui-actionslot-duration", ("duration", (int)duration.TotalSeconds), ("timeLeft", (int)timeLeft.TotalSeconds + 1)), out var markup))
|
||||
return;
|
||||
|
||||
_cooldownLabel.SetMessage(markup);
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Client.Administration.UI.SetOutfit
|
||||
@@ -65,9 +64,18 @@ namespace Content.Client.Administration.UI.SetOutfit
|
||||
PopulateByFilter(SearchBar.Text);
|
||||
}
|
||||
|
||||
private IEnumerable<StartingGearPrototype> GetPrototypes()
|
||||
{
|
||||
// Filter out any StartingGearPrototypes that belong to loadouts
|
||||
var loadouts = _prototypeManager.EnumeratePrototypes<LoadoutPrototype>();
|
||||
var loadoutGears = loadouts.Select(l => l.StartingGear);
|
||||
return _prototypeManager.EnumeratePrototypes<StartingGearPrototype>()
|
||||
.Where(p => !loadoutGears.Contains(p.ID));
|
||||
}
|
||||
|
||||
private void PopulateList()
|
||||
{
|
||||
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
|
||||
foreach (var gear in GetPrototypes())
|
||||
{
|
||||
OutfitList.Add(GetItem(gear, OutfitList));
|
||||
}
|
||||
@@ -76,7 +84,7 @@ namespace Content.Client.Administration.UI.SetOutfit
|
||||
private void PopulateByFilter(string filter)
|
||||
{
|
||||
OutfitList.Clear();
|
||||
foreach (var gear in _prototypeManager.EnumeratePrototypes<StartingGearPrototype>())
|
||||
foreach (var gear in GetPrototypes())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(filter) &&
|
||||
gear.ID.ToLowerInvariant().Contains(filter.Trim().ToLowerInvariant()))
|
||||
|
||||
@@ -20,8 +20,9 @@ public sealed class AnomalySystem : SharedAnomalySystem
|
||||
SubscribeLocalEvent<AnomalyComponent, AppearanceChangeEvent>(OnAppearanceChanged);
|
||||
SubscribeLocalEvent<AnomalyComponent, ComponentStartup>(OnStartup);
|
||||
SubscribeLocalEvent<AnomalyComponent, AnimationCompletedEvent>(OnAnimationComplete);
|
||||
}
|
||||
|
||||
SubscribeLocalEvent<AnomalySupercriticalComponent, ComponentShutdown>(OnShutdown);
|
||||
}
|
||||
private void OnStartup(EntityUid uid, AnomalyComponent component, ComponentStartup args)
|
||||
{
|
||||
_floating.FloatAnimation(uid, component.FloatingOffset, component.AnimationKey, component.AnimationTime);
|
||||
@@ -75,4 +76,13 @@ public sealed class AnomalySystem : SharedAnomalySystem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShutdown(Entity<AnomalySupercriticalComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.Scale = Vector2.One;
|
||||
sprite.Color = sprite.Color.WithAlpha(1f);
|
||||
}
|
||||
}
|
||||
|
||||
50
Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs
Normal file
50
Content.Client/Anomaly/Effects/ClientInnerBodySystem.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Effects;
|
||||
using Content.Shared.Body.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Anomaly.Effects;
|
||||
|
||||
public sealed class ClientInnerBodyAnomalySystem : SharedInnerBodyAnomalySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, AfterAutoHandleStateEvent>(OnAfterHandleState);
|
||||
SubscribeLocalEvent<InnerBodyAnomalyComponent, ComponentShutdown>(OnCompShutdown);
|
||||
}
|
||||
|
||||
private void OnAfterHandleState(Entity<InnerBodyAnomalyComponent> ent, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
if (ent.Comp.FallbackSprite is null)
|
||||
return;
|
||||
|
||||
if (!sprite.LayerMapTryGet(ent.Comp.LayerMap, out var index))
|
||||
index = sprite.LayerMapReserveBlank(ent.Comp.LayerMap);
|
||||
|
||||
if (TryComp<BodyComponent>(ent, out var body) &&
|
||||
body.Prototype is not null &&
|
||||
ent.Comp.SpeciesSprites.TryGetValue(body.Prototype.Value, out var speciesSprite))
|
||||
{
|
||||
sprite.LayerSetSprite(index, speciesSprite);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetSprite(index, ent.Comp.FallbackSprite);
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(index, true);
|
||||
sprite.LayerSetShader(index, "unshaded");
|
||||
}
|
||||
|
||||
private void OnCompShutdown(Entity<InnerBodyAnomalyComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
var index = sprite.LayerMapGet(ent.Comp.LayerMap);
|
||||
sprite.LayerSetVisible(index, false);
|
||||
}
|
||||
}
|
||||
@@ -306,6 +306,9 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
.WithMaxDistance(comp.Range);
|
||||
|
||||
var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams);
|
||||
if (stream == null)
|
||||
continue;
|
||||
|
||||
_playingSounds[sourceEntity] = (stream.Value.Entity, comp.Sound, key);
|
||||
playingCount++;
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
if(!_adminAudioEnabled) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_adminAudio.Add(stream.Value.Entity);
|
||||
_adminAudio.Add(stream?.Entity);
|
||||
}
|
||||
|
||||
private void PlayStationEventMusic(StationEventMusicEvent soundEvent)
|
||||
@@ -76,7 +76,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_eventAudio.Add(soundEvent.Type, stream.Value.Entity);
|
||||
_eventAudio.Add(soundEvent.Type, stream?.Entity);
|
||||
}
|
||||
|
||||
private void PlayGameSound(GameGlobalSoundEvent soundEvent)
|
||||
|
||||
@@ -214,9 +214,9 @@ public sealed partial class ContentAudioSystem
|
||||
false,
|
||||
AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider));
|
||||
|
||||
_ambientMusicStream = strim.Value.Entity;
|
||||
_ambientMusicStream = strim?.Entity;
|
||||
|
||||
if (_musicProto.FadeIn)
|
||||
if (_musicProto.FadeIn && strim != null)
|
||||
{
|
||||
FadeIn(_ambientMusicStream, strim.Value.Component, AmbientMusicFadeTime);
|
||||
}
|
||||
|
||||
@@ -80,6 +80,10 @@ public sealed partial class ContentAudioSystem
|
||||
.WithLoop(true)
|
||||
.WithVolume(proto.Sound.Params.Volume + _volumeSlider)
|
||||
.WithPlayOffset(_random.NextFloat(0f, 100f)));
|
||||
|
||||
if (newLoop is null)
|
||||
return;
|
||||
|
||||
_loopStreams.Add(proto, newLoop.Value.Entity);
|
||||
|
||||
FadeIn(newLoop.Value.Entity, newLoop.Value.Component, AmbientLoopFadeInTime);
|
||||
|
||||
@@ -20,7 +20,6 @@ public sealed partial class ContentAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
private readonly AudioParams _lobbySoundtrackParams = new(-5f, 1, 0, 0, 0, false, 0f);
|
||||
@@ -71,7 +70,7 @@ public sealed partial class ContentAudioSystem
|
||||
Subs.CVar(_configManager, CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
|
||||
Subs.CVar(_configManager, CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
|
||||
|
||||
_stateManager.OnStateChanged += StateManagerOnStateChanged;
|
||||
_state.OnStateChanged += StateManagerOnStateChanged;
|
||||
|
||||
_client.PlayerLeaveServer += OnLeave;
|
||||
|
||||
@@ -115,7 +114,7 @@ public sealed partial class ContentAudioSystem
|
||||
|
||||
private void LobbyMusicCVarChanged(bool musicEnabled)
|
||||
{
|
||||
if (musicEnabled && _stateManager.CurrentState is LobbyState)
|
||||
if (musicEnabled && _state.CurrentState is LobbyState)
|
||||
{
|
||||
StartLobbyMusic();
|
||||
}
|
||||
@@ -185,7 +184,7 @@ public sealed partial class ContentAudioSystem
|
||||
false,
|
||||
_lobbySoundtrackParams.WithVolume(_lobbySoundtrackParams.Volume + SharedAudioSystem.GainToVolume(_configManager.GetCVar(CCVars.LobbyMusicVolume)))
|
||||
);
|
||||
if (playResult.Value.Entity == default)
|
||||
if (playResult == null)
|
||||
{
|
||||
_sawmill.Warning(
|
||||
$"Tried to play lobby soundtrack '{{Filename}}' using {nameof(SharedAudioSystem)}.{nameof(SharedAudioSystem.PlayGlobal)} but it returned default value of EntityUid!",
|
||||
@@ -234,7 +233,7 @@ public sealed partial class ContentAudioSystem
|
||||
|
||||
private void ShutdownLobbyMusic()
|
||||
{
|
||||
_stateManager.OnStateChanged -= StateManagerOnStateChanged;
|
||||
_state.OnStateChanged -= StateManagerOnStateChanged;
|
||||
|
||||
_client.PlayerLeaveServer -= OnLeave;
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BuckleComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<BuckleComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<StrapComponent, MoveEvent>(OnStrapMoveEvent);
|
||||
}
|
||||
@@ -57,21 +56,6 @@ internal sealed class BuckleSystem : SharedBuckleSystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHandleState(Entity<BuckleComponent> ent, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not BuckleState state)
|
||||
return;
|
||||
|
||||
ent.Comp.DontCollide = state.DontCollide;
|
||||
ent.Comp.BuckleTime = state.BuckleTime;
|
||||
var strapUid = EnsureEntity<BuckleComponent>(state.BuckledTo, ent);
|
||||
|
||||
SetBuckledTo(ent, strapUid == null ? null : new (strapUid.Value, null));
|
||||
|
||||
var (uid, component) = ent;
|
||||
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(EntityUid uid, BuckleComponent component, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (!TryComp<RotationVisualsComponent>(uid, out var rotVisuals))
|
||||
|
||||
21
Content.Client/Cargo/Systems/ClientPriceGunSystem.cs
Normal file
21
Content.Client/Cargo/Systems/ClientPriceGunSystem.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Cargo.Systems;
|
||||
|
||||
namespace Content.Client.Cargo.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// This handles...
|
||||
/// </summary>
|
||||
public sealed class ClientPriceGunSystem : SharedPriceGunSystem
|
||||
{
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
|
||||
protected override bool GetPriceOrBounty(EntityUid priceGunUid, EntityUid target, EntityUid user)
|
||||
{
|
||||
if (!TryComp(priceGunUid, out UseDelayComponent? useDelay) || _useDelay.IsDelayed((priceGunUid, useDelay)))
|
||||
return false;
|
||||
|
||||
// It feels worse if the cooldown is predicted but the popup isn't! So only do the cooldown reset on the server.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
30
Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs
Normal file
30
Content.Client/CartridgeLoader/Cartridges/WantedListUi.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Content.Client.UserInterface.Fragments;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
public sealed partial class WantedListUi : UIFragment
|
||||
{
|
||||
private WantedListUiFragment? _fragment;
|
||||
|
||||
public override Control GetUIFragmentRoot()
|
||||
{
|
||||
return _fragment!;
|
||||
}
|
||||
|
||||
public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
|
||||
{
|
||||
_fragment = new WantedListUiFragment();
|
||||
}
|
||||
|
||||
public override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case WantedListUiState cast:
|
||||
_fragment?.UpdateState(cast.Records);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.CriminalRecords.Systems;
|
||||
using Content.Shared.Security;
|
||||
using Content.Shared.StatusIcon;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client.CartridgeLoader.Cartridges;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class WantedListUiFragment : BoxContainer
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private readonly SpriteSystem _spriteSystem;
|
||||
|
||||
private string? _selectedTargetName;
|
||||
private List<WantedRecord> _wantedRecords = new();
|
||||
|
||||
public WantedListUiFragment()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
_spriteSystem = _entitySystem.GetEntitySystem<SpriteSystem>();
|
||||
|
||||
SearchBar.OnTextChanged += OnSearchBarTextChanged;
|
||||
}
|
||||
|
||||
private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args)
|
||||
{
|
||||
var found = !String.IsNullOrWhiteSpace(args.Text)
|
||||
? _wantedRecords.FindAll(r =>
|
||||
r.TargetInfo.Name.Contains(args.Text) ||
|
||||
r.Status.ToString().Contains(args.Text, StringComparison.OrdinalIgnoreCase))
|
||||
: _wantedRecords;
|
||||
|
||||
UpdateState(found, false);
|
||||
}
|
||||
|
||||
public void UpdateState(List<WantedRecord> records, bool refresh = true)
|
||||
{
|
||||
if (records.Count == 0)
|
||||
{
|
||||
NoRecords.Visible = true;
|
||||
RecordsList.Visible = false;
|
||||
RecordUnselected.Visible = false;
|
||||
PersonContainer.Visible = false;
|
||||
|
||||
_selectedTargetName = null;
|
||||
if (refresh)
|
||||
_wantedRecords.Clear();
|
||||
|
||||
RecordsList.PopulateList(new List<ListData>());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
NoRecords.Visible = false;
|
||||
RecordsList.Visible = true;
|
||||
RecordUnselected.Visible = true;
|
||||
PersonContainer.Visible = false;
|
||||
|
||||
var dataList = records.Select(r => new StatusListData(r)).ToList();
|
||||
|
||||
RecordsList.GenerateItem = GenerateItem;
|
||||
RecordsList.ItemPressed = OnItemSelected;
|
||||
RecordsList.PopulateList(dataList);
|
||||
|
||||
if (refresh)
|
||||
_wantedRecords = records;
|
||||
}
|
||||
|
||||
private void OnItemSelected(BaseButton.ButtonEventArgs args, ListData data)
|
||||
{
|
||||
if (data is not StatusListData(var record))
|
||||
return;
|
||||
|
||||
FormattedMessage GetLoc(string fluentId, params (string,object)[] args)
|
||||
{
|
||||
var msg = new FormattedMessage();
|
||||
var fluent = Loc.GetString(fluentId, args);
|
||||
msg.AddMarkupPermissive(fluent);
|
||||
return msg;
|
||||
}
|
||||
|
||||
// Set personal info
|
||||
PersonName.Text = record.TargetInfo.Name;
|
||||
TargetAge.SetMessage(GetLoc(
|
||||
"wanted-list-age-label",
|
||||
("age", record.TargetInfo.Age)
|
||||
));
|
||||
TargetJob.SetMessage(GetLoc(
|
||||
"wanted-list-job-label",
|
||||
("job", record.TargetInfo.JobTitle.ToLower())
|
||||
));
|
||||
TargetSpecies.SetMessage(GetLoc(
|
||||
"wanted-list-species-label",
|
||||
("species", record.TargetInfo.Species.ToLower())
|
||||
));
|
||||
TargetGender.SetMessage(GetLoc(
|
||||
"wanted-list-gender-label",
|
||||
("gender", record.TargetInfo.Gender)
|
||||
));
|
||||
|
||||
// Set reason
|
||||
WantedReason.SetMessage(GetLoc(
|
||||
"wanted-list-reason-label",
|
||||
("reason", record.Reason ?? Loc.GetString("wanted-list-unknown-reason-label"))
|
||||
));
|
||||
|
||||
// Set status
|
||||
PersonState.SetMessage(GetLoc(
|
||||
"wanted-list-status-label",
|
||||
("status", record.Status.ToString().ToLower())
|
||||
));
|
||||
|
||||
// Set initiator
|
||||
InitiatorName.SetMessage(GetLoc(
|
||||
"wanted-list-initiator-label",
|
||||
("initiator", record.Initiator ?? Loc.GetString("wanted-list-unknown-initiator-label"))
|
||||
));
|
||||
|
||||
// History table
|
||||
// Clear table if it exists
|
||||
HistoryTable.RemoveAllChildren();
|
||||
|
||||
HistoryTable.AddChild(new Label()
|
||||
{
|
||||
Text = Loc.GetString("wanted-list-history-table-time-col"),
|
||||
StyleClasses = { "LabelSmall" },
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
});
|
||||
HistoryTable.AddChild(new Label()
|
||||
{
|
||||
Text = Loc.GetString("wanted-list-history-table-reason-col"),
|
||||
StyleClasses = { "LabelSmall" },
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
HorizontalExpand = true,
|
||||
});
|
||||
|
||||
HistoryTable.AddChild(new Label()
|
||||
{
|
||||
Text = Loc.GetString("wanted-list-history-table-initiator-col"),
|
||||
StyleClasses = { "LabelSmall" },
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
});
|
||||
|
||||
if (record.History.Count > 0)
|
||||
{
|
||||
HistoryTable.Visible = true;
|
||||
|
||||
foreach (var history in record.History.OrderByDescending(h => h.AddTime))
|
||||
{
|
||||
HistoryTable.AddChild(new Label()
|
||||
{
|
||||
Text = $"{history.AddTime.Hours:00}:{history.AddTime.Minutes:00}:{history.AddTime.Seconds:00}",
|
||||
StyleClasses = { "LabelSmall" },
|
||||
VerticalAlignment = VAlignment.Top,
|
||||
});
|
||||
|
||||
HistoryTable.AddChild(new RichTextLabel()
|
||||
{
|
||||
Text = $"[color=white]{history.Crime}[/color]",
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Top,
|
||||
StyleClasses = { "LabelSubText" },
|
||||
Margin = new(10f, 0f),
|
||||
});
|
||||
|
||||
HistoryTable.AddChild(new RichTextLabel()
|
||||
{
|
||||
Text = $"[color=white]{history.InitiatorName}[/color]",
|
||||
StyleClasses = { "LabelSubText" },
|
||||
VerticalAlignment = VAlignment.Top,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
RecordUnselected.Visible = false;
|
||||
PersonContainer.Visible = true;
|
||||
|
||||
// Save selected item
|
||||
_selectedTargetName = record.TargetInfo.Name;
|
||||
}
|
||||
|
||||
private void GenerateItem(ListData data, ListContainerButton button)
|
||||
{
|
||||
if (data is not StatusListData(var record))
|
||||
return;
|
||||
|
||||
var box = new BoxContainer() { Orientation = LayoutOrientation.Horizontal, HorizontalExpand = true };
|
||||
var label = new Label() { Text = record.TargetInfo.Name };
|
||||
var rect = new TextureRect()
|
||||
{
|
||||
TextureScale = new(2.2f),
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Margin = new(0f, 0f, 6f, 0f),
|
||||
};
|
||||
|
||||
if (record.Status is not SecurityStatus.None)
|
||||
{
|
||||
var proto = "SecurityIcon" + record.Status switch
|
||||
{
|
||||
SecurityStatus.Detained => "Incarcerated",
|
||||
_ => record.Status.ToString(),
|
||||
};
|
||||
|
||||
if (_prototypeManager.TryIndex<SecurityIconPrototype>(proto, out var prototype))
|
||||
{
|
||||
rect.Texture = _spriteSystem.Frame0(prototype.Icon);
|
||||
}
|
||||
}
|
||||
|
||||
box.AddChild(rect);
|
||||
box.AddChild(label);
|
||||
button.AddChild(box);
|
||||
button.AddStyleClass(ListContainer.StyleClassListContainerButton);
|
||||
|
||||
if (record.TargetInfo.Name.Equals(_selectedTargetName))
|
||||
{
|
||||
button.Pressed = true;
|
||||
// For some reason the event is not called when `Pressed` changed, call it manually.
|
||||
OnItemSelected(
|
||||
new(button, new(new(), BoundKeyState.Down, new(), false, new(), new())),
|
||||
data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal record StatusListData(WantedRecord Record) : ListData;
|
||||
@@ -0,0 +1,50 @@
|
||||
<cartridges:WantedListUiFragment xmlns:cartridges="clr-namespace:Content.Client.CartridgeLoader.Cartridges"
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Orientation="Vertical"
|
||||
VerticalExpand="True"
|
||||
HorizontalExpand="True">
|
||||
|
||||
<LineEdit Name="SearchBar" PlaceHolder="{Loc 'wanted-list-search-placeholder'}"/>
|
||||
|
||||
<BoxContainer Name="MainContainer" Orientation="Horizontal" HorizontalExpand="True" VerticalExpand="True">
|
||||
<Label Name="NoRecords" Text="{Loc 'wanted-list-label-no-records'}" Align="Center" VAlign="Center" HorizontalExpand="True" FontColorOverride="DarkGray"/>
|
||||
|
||||
<!-- Any attempts to set dimensions for ListContainer breaks the renderer, I have to roughly set sizes and margins in other controllers. -->
|
||||
<controls:ListContainer
|
||||
Name="RecordsList"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalExpand="True"
|
||||
Visible="False"
|
||||
Toggle="True"
|
||||
Group="True"
|
||||
SetWidth="192" />
|
||||
|
||||
<Label Name="RecordUnselected"
|
||||
Text="{Loc 'criminal-records-console-select-record-info'}"
|
||||
Align="Center"
|
||||
FontColorOverride="DarkGray"
|
||||
Visible="False"
|
||||
HorizontalExpand="True" />
|
||||
<BoxContainer Name="PersonContainer" Orientation="Vertical" HorizontalExpand="True" SetWidth="334" Margin="5 0 77 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Name="PersonName" StyleClasses="LabelBig" />
|
||||
<RichTextLabel Name="PersonState" HorizontalAlignment="Right" HorizontalExpand="True" />
|
||||
</BoxContainer>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
|
||||
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||
<BoxContainer Name="DataContainer" Orientation="Vertical">
|
||||
<RichTextLabel Name="TargetAge" />
|
||||
<RichTextLabel Name="TargetJob" />
|
||||
<RichTextLabel Name="TargetSpecies" />
|
||||
<RichTextLabel Name="TargetGender" />
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5"/>
|
||||
<RichTextLabel Name="InitiatorName" VerticalAlignment="Stretch"/>
|
||||
<RichTextLabel Name="WantedReason" VerticalAlignment="Stretch"/>
|
||||
<PanelContainer StyleClasses="LowDivider" Margin="0 5 0 5" />
|
||||
<controls:TableContainer Name="HistoryTable" Columns="3" Visible="False" HorizontalAlignment="Stretch" />
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</cartridges:WantedListUiFragment>
|
||||
@@ -21,6 +21,16 @@ internal sealed class ChatManager : IChatManager
|
||||
_sawmill.Level = LogLevel.Info;
|
||||
}
|
||||
|
||||
public void SendAdminAlert(string message)
|
||||
{
|
||||
// See server-side manager. This just exists for shared code.
|
||||
}
|
||||
|
||||
public void SendAdminAlert(EntityUid player, string message)
|
||||
{
|
||||
// See server-side manager. This just exists for shared code.
|
||||
}
|
||||
|
||||
public void SendMessage(string text, ChatSelectChannel channel)
|
||||
{
|
||||
var str = text.ToString();
|
||||
|
||||
@@ -2,10 +2,8 @@ using Content.Shared.Chat;
|
||||
|
||||
namespace Content.Client.Chat.Managers
|
||||
{
|
||||
public interface IChatManager
|
||||
public interface IChatManager : ISharedChatManager
|
||||
{
|
||||
void Initialize();
|
||||
|
||||
public void SendMessage(string text, ChatSelectChannel channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,7 +327,8 @@ public sealed class ClientClothingSystem : ClothingSystem
|
||||
if (layerData.State is not null && inventory.SpeciesId is not null && layerData.State.EndsWith(inventory.SpeciesId))
|
||||
continue;
|
||||
|
||||
_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers);
|
||||
if (_displacement.TryAddDisplacement(displacementData, sprite, index, key, revealedLayers))
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ using Content.Shared.Ensnaring;
|
||||
using Content.Shared.Ensnaring.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Ensnaring.Visualizers;
|
||||
namespace Content.Client.Ensnaring;
|
||||
|
||||
public sealed class EnsnareableSystem : SharedEnsnareableSystem
|
||||
{
|
||||
@@ -12,13 +12,14 @@ public sealed class EnsnareableSystem : SharedEnsnareableSystem
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<EnsnareableComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<EnsnareableComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnComponentInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
|
||||
protected override void OnEnsnareInit(Entity<EnsnareableComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
if(!TryComp<SpriteComponent>(uid, out var sprite))
|
||||
base.OnEnsnareInit(ent, ref args);
|
||||
|
||||
if(!TryComp<SpriteComponent>(ent.Owner, out var sprite))
|
||||
return;
|
||||
|
||||
// TODO remove this, this should just be in yaml.
|
||||
|
||||
@@ -70,7 +70,6 @@ namespace Content.Client.Entry
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IReplayLoadManager _replayLoad = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly ContentReplayPlaybackManager _replayMan = default!;
|
||||
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
|
||||
|
||||
public override void Init()
|
||||
@@ -192,7 +191,7 @@ namespace Content.Client.Entry
|
||||
_resourceManager,
|
||||
ReplayConstants.ReplayZipFolder.ToRootedPath());
|
||||
|
||||
_replayMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath());
|
||||
_playbackMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath());
|
||||
_replayLoad.LoadAndStartReplay(reader);
|
||||
}
|
||||
else if (_gameController.LaunchState.FromLauncher)
|
||||
|
||||
@@ -2,7 +2,4 @@ using Content.Shared.Explosion.EntitySystems;
|
||||
|
||||
namespace Content.Client.Explosion.EntitySystems;
|
||||
|
||||
public sealed class ExplosionSystem : SharedExplosionSystem
|
||||
{
|
||||
|
||||
}
|
||||
public sealed class ExplosionSystem : SharedExplosionSystem;
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Content.Client.Flash
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private readonly SharedFlashSystem _flash;
|
||||
private readonly StatusEffectsSystem _statusSys;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
@@ -27,6 +28,7 @@ namespace Content.Client.Flash
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_shader = _prototypeManager.Index<ShaderPrototype>("FlashedEffect").InstanceUnique();
|
||||
_flash = _entityManager.System<SharedFlashSystem>();
|
||||
_statusSys = _entityManager.System<StatusEffectsSystem>();
|
||||
}
|
||||
|
||||
@@ -41,7 +43,7 @@ namespace Content.Client.Flash
|
||||
|| !_entityManager.TryGetComponent<StatusEffectsComponent>(playerEntity, out var status))
|
||||
return;
|
||||
|
||||
if (!_statusSys.TryGetTime(playerEntity.Value, SharedFlashSystem.FlashedKey, out var time, status))
|
||||
if (!_statusSys.TryGetTime(playerEntity.Value, _flash.FlashedKey, out var time, status))
|
||||
return;
|
||||
|
||||
var curTime = _timing.CurTime;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Content.Shared.GPS;
|
||||
|
||||
namespace Content.Client.GPS.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class HandheldGPSComponent : SharedHandheldGPSComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Client.GPS.Components;
|
||||
using Content.Shared.GPS.Components;
|
||||
using Content.Client.GPS.UI;
|
||||
using Content.Client.Items;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Client.GPS.Components;
|
||||
using Content.Shared.GPS.Components;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Robust.Client.GameObjects;
|
||||
@@ -30,6 +30,13 @@ public sealed class HandheldGpsStatusControl : Control
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
// don't display the label if the gps component is being removed
|
||||
if (_parent.Comp.LifeStage > ComponentLifeStage.Running)
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_updateDif += args.DeltaSeconds;
|
||||
if (_updateDif < _parent.Comp.UpdateRate)
|
||||
return;
|
||||
@@ -44,9 +51,9 @@ public sealed class HandheldGpsStatusControl : Control
|
||||
var posText = "Error";
|
||||
if (_entMan.TryGetComponent(_parent, out TransformComponent? transComp))
|
||||
{
|
||||
var pos = _transform.GetMapCoordinates(_parent.Owner, xform: transComp);
|
||||
var x = (int) pos.X;
|
||||
var y = (int) pos.Y;
|
||||
var pos = _transform.GetMapCoordinates(_parent.Owner, xform: transComp);
|
||||
var x = (int)pos.X;
|
||||
var y = (int)pos.Y;
|
||||
posText = $"({x}, {y})";
|
||||
}
|
||||
_label.SetMarkup(Loc.GetString("handheld-gps-coordinates-title", ("coordinates", posText)));
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Client.UserInterface.Controls;
|
||||
using Content.Client.UserInterface.Controls.FancyTree;
|
||||
using Content.Client.UserInterface.Systems.Info;
|
||||
using Content.Shared.Guidebook;
|
||||
using Content.Shared.Localizations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -168,6 +169,8 @@ public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
|
||||
foreach (var entry in GetSortedEntries(roots))
|
||||
{
|
||||
if (!entry.CrystallPunkAllowed) continue; //CrystallPunk guidebook filter
|
||||
if (entry.LocFilter is not null && entry.LocFilter != ContentLocalizationManager.Culture) continue; //CrystallPunk guidebook filter
|
||||
|
||||
AddEntry(entry.Id, parent, addedEntries);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" Margin="0 0 0 5">
|
||||
<SpriteView OverrideDirection="South" Scale="2 2" Name="SpriteView" Access="Public" SetSize="64 64" />
|
||||
<TextureRect Name="NoDataTex" Access="Public" SetSize="64 64" Visible="false" Stretch="KeepAspectCentered" TexturePath="/Textures/Interface/Misc/health_analyzer_out_of_range.png"/>
|
||||
<BoxContainer Margin="5 0 0 0" Orientation="Vertical" VerticalAlignment="Top">
|
||||
<RichTextLabel Name="NameLabel" SetWidth="150" />
|
||||
<Label Name="SpeciesLabel" VerticalAlignment="Top" StyleClasses="LabelSubText" />
|
||||
|
||||
@@ -73,6 +73,8 @@ namespace Content.Client.HealthAnalyzer.UI
|
||||
// Patient Information
|
||||
|
||||
SpriteView.SetEntity(target.Value);
|
||||
SpriteView.Visible = msg.ScanMode.HasValue && msg.ScanMode.Value;
|
||||
NoDataTex.Visible = !SpriteView.Visible;
|
||||
|
||||
var name = new FormattedMessage();
|
||||
name.PushColor(Color.White);
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Content.Client.Inventory
|
||||
}
|
||||
}
|
||||
|
||||
if (EntMan.TryGetComponent<HandsComponent>(Owner, out var handsComp))
|
||||
if (EntMan.TryGetComponent<HandsComponent>(Owner, out var handsComp) && handsComp.CanBeStripped)
|
||||
{
|
||||
// good ol hands shit code. there is a GuiHands comparer that does the same thing... but these are hands
|
||||
// and not gui hands... which are different...
|
||||
@@ -136,7 +136,7 @@ namespace Content.Client.Inventory
|
||||
StyleClasses = { StyleBase.ButtonOpenRight }
|
||||
};
|
||||
|
||||
button.OnPressed += (_) => SendMessage(new StrippingEnsnareButtonPressed());
|
||||
button.OnPressed += (_) => SendPredictedMessage(new StrippingEnsnareButtonPressed());
|
||||
|
||||
_strippingMenu.SnareContainer.AddChild(button);
|
||||
}
|
||||
@@ -177,7 +177,7 @@ namespace Content.Client.Inventory
|
||||
// So for now: only stripping & examining
|
||||
if (ev.Function == EngineKeyFunctions.Use)
|
||||
{
|
||||
SendMessage(new StrippingSlotButtonPressed(slot.SlotName, slot is HandButton));
|
||||
SendPredictedMessage(new StrippingSlotButtonPressed(slot.SlotName, slot is HandButton));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,11 @@ using Content.Client.Viewport;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Administration.Logs;
|
||||
using Content.Client.Lobby;
|
||||
using Content.Client.Players.RateLimiting;
|
||||
using Content.Shared.Administration.Managers;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Players.PlayTimeTracking;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
|
||||
namespace Content.Client.IoC
|
||||
{
|
||||
@@ -31,6 +34,7 @@ namespace Content.Client.IoC
|
||||
|
||||
collection.Register<IParallaxManager, ParallaxManager>();
|
||||
collection.Register<IChatManager, ChatManager>();
|
||||
collection.Register<ISharedChatManager, ChatManager>();
|
||||
collection.Register<IClientPreferencesManager, ClientPreferencesManager>();
|
||||
collection.Register<IStylesheetManager, StylesheetManager>();
|
||||
collection.Register<IScreenshotHook, ScreenshotHook>();
|
||||
@@ -47,10 +51,12 @@ namespace Content.Client.IoC
|
||||
collection.Register<ExtendedDisconnectInformationManager>();
|
||||
collection.Register<JobRequirementsManager>();
|
||||
collection.Register<DocumentParsingManager>();
|
||||
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
|
||||
collection.Register<ContentReplayPlaybackManager>();
|
||||
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
|
||||
collection.Register<MappingManager>();
|
||||
collection.Register<DebugMonitorManager>();
|
||||
collection.Register<PlayerRateLimitManager>();
|
||||
collection.Register<SharedPlayerRateLimitManager, PlayerRateLimitManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
Stretch="KeepAspectCovered" />
|
||||
<BoxContainer Name="MainContainer" VerticalExpand="True" HorizontalExpand="True" Orientation="Horizontal"
|
||||
Margin="10 10 10 10" SeparationOverride="2">
|
||||
<SplitContainer State="Auto" HorizontalExpand="True">
|
||||
<SplitContainer State="Auto" ResizeMode="NotResizable" HorizontalExpand="True">
|
||||
<!-- LHS Controls -->
|
||||
<BoxContainer Name="LeftSide" Orientation="Vertical" SeparationOverride="4" HorizontalExpand="True">
|
||||
<Control Name="DefaultState" VerticalExpand="True">
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
ToolTip="Pick (Hold 5)" />
|
||||
<mapping:MappingActionsButton Name="Delete" Access="Public"
|
||||
ToolTip="Delete (Hold 6)" />
|
||||
<mapping:MappingActionsButton Name="Flip" Access="Public" ToggleMode="False"/>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
</LayoutContainer>
|
||||
|
||||
@@ -96,6 +96,22 @@ public sealed partial class MappingScreen : InGameScreen
|
||||
|
||||
Pick.Texture.TexturePath = "/Textures/Interface/eyedropper.svg.png";
|
||||
Delete.Texture.TexturePath = "/Textures/Interface/eraser.svg.png";
|
||||
Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png";
|
||||
Flip.OnPressed += args => FlipSides();
|
||||
}
|
||||
|
||||
public void FlipSides()
|
||||
{
|
||||
ScreenContainer.Flip();
|
||||
|
||||
if (SpawnContainer.GetPositionInParent() == 0)
|
||||
{
|
||||
Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_cw.svg.192dpi.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
Flip.Texture.TexturePath = "/Textures/Interface/VerbIcons/rotate_ccw.svg.192dpi.png";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDecalColorPicked(Color color)
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
<LineEdit Name="SearchLineEdit" HorizontalExpand="True"
|
||||
PlaceHolder="{Loc crew-monitor-filter-line-placeholder}" />
|
||||
|
||||
<ScrollContainer Name="SensorScroller"
|
||||
VerticalExpand="True"
|
||||
SetWidth="520"
|
||||
|
||||
@@ -156,6 +156,11 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
||||
// Populate departments
|
||||
foreach (var sensor in departmentSensors)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(SearchLineEdit.Text)
|
||||
&& !sensor.Name.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase)
|
||||
&& !sensor.Job.Contains(SearchLineEdit.Text, StringComparison.CurrentCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
var coordinates = _entManager.GetCoordinates(sensor.Coordinates);
|
||||
|
||||
// Add a button that will hold a username and other details
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Content.Client.Buckle;
|
||||
using Content.Client.Gravity;
|
||||
using Content.Shared.ActionBlocker;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Movement.Components;
|
||||
using Content.Shared.Movement.Systems;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
|
||||
namespace Content.Client.Movement.Systems;
|
||||
|
||||
public sealed class WaddleAnimationSystem : SharedWaddleAnimationSystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
|
||||
[Dependency] private readonly GravitySystem _gravity = default!;
|
||||
[Dependency] private readonly ActionBlockerSystem _actionBlocker = default!;
|
||||
[Dependency] private readonly BuckleSystem _buckle = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeAllEvent<StartedWaddlingEvent>(OnStartWaddling);
|
||||
SubscribeLocalEvent<WaddleAnimationComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeAllEvent<StoppedWaddlingEvent>(OnStopWaddling);
|
||||
}
|
||||
|
||||
private void OnStartWaddling(StartedWaddlingEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
|
||||
StartWaddling((GetEntity(msg.Entity), comp));
|
||||
}
|
||||
|
||||
private void OnStopWaddling(StoppedWaddlingEvent msg, EntitySessionEventArgs args)
|
||||
{
|
||||
if (TryComp<WaddleAnimationComponent>(GetEntity(msg.Entity), out var comp))
|
||||
StopWaddling((GetEntity(msg.Entity), comp));
|
||||
}
|
||||
|
||||
private void StartWaddling(Entity<WaddleAnimationComponent> entity)
|
||||
{
|
||||
if (_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
|
||||
return;
|
||||
|
||||
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
|
||||
return;
|
||||
|
||||
if (_gravity.IsWeightless(entity.Owner))
|
||||
return;
|
||||
|
||||
if (!_actionBlocker.CanMove(entity.Owner, mover))
|
||||
return;
|
||||
|
||||
// Do nothing if buckled in
|
||||
if (_buckle.IsBuckled(entity.Owner))
|
||||
return;
|
||||
|
||||
// Do nothing if crit or dead (for obvious reasons)
|
||||
if (_mobState.IsIncapacitated(entity.Owner))
|
||||
return;
|
||||
|
||||
PlayWaddleAnimationUsing(
|
||||
(entity.Owner, entity.Comp),
|
||||
CalculateAnimationLength(entity.Comp, mover),
|
||||
CalculateTumbleIntensity(entity.Comp)
|
||||
);
|
||||
}
|
||||
|
||||
private static float CalculateTumbleIntensity(WaddleAnimationComponent component)
|
||||
{
|
||||
return component.LastStep ? 360 - component.TumbleIntensity : component.TumbleIntensity;
|
||||
}
|
||||
|
||||
private static float CalculateAnimationLength(WaddleAnimationComponent component, InputMoverComponent mover)
|
||||
{
|
||||
return mover.Sprinting ? component.AnimationLength * component.RunAnimationLengthMultiplier : component.AnimationLength;
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(Entity<WaddleAnimationComponent> entity, ref AnimationCompletedEvent args)
|
||||
{
|
||||
if (args.Key != entity.Comp.KeyName)
|
||||
return;
|
||||
|
||||
if (!TryComp<InputMoverComponent>(entity.Owner, out var mover))
|
||||
return;
|
||||
|
||||
PlayWaddleAnimationUsing(
|
||||
(entity.Owner, entity.Comp),
|
||||
CalculateAnimationLength(entity.Comp, mover),
|
||||
CalculateTumbleIntensity(entity.Comp)
|
||||
);
|
||||
}
|
||||
|
||||
private void StopWaddling(Entity<WaddleAnimationComponent> entity)
|
||||
{
|
||||
if (!_animation.HasRunningAnimation(entity.Owner, entity.Comp.KeyName))
|
||||
return;
|
||||
|
||||
_animation.Stop(entity.Owner, entity.Comp.KeyName);
|
||||
|
||||
if (!TryComp<SpriteComponent>(entity.Owner, out var sprite))
|
||||
return;
|
||||
|
||||
sprite.Offset = new Vector2();
|
||||
sprite.Rotation = Angle.FromDegrees(0);
|
||||
}
|
||||
|
||||
private void PlayWaddleAnimationUsing(Entity<WaddleAnimationComponent> entity, float len, float tumbleIntensity)
|
||||
{
|
||||
entity.Comp.LastStep = !entity.Comp.LastStep;
|
||||
|
||||
var anim = new Animation()
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(len),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Rotation),
|
||||
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), 0),
|
||||
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(tumbleIntensity), len/2),
|
||||
new AnimationTrackProperty.KeyFrame(Angle.FromDegrees(0), len/2),
|
||||
}
|
||||
},
|
||||
new AnimationTrackComponentProperty()
|
||||
{
|
||||
ComponentType = typeof(SpriteComponent),
|
||||
Property = nameof(SpriteComponent.Offset),
|
||||
InterpolationMode = AnimationInterpolationMode.Linear,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(new Vector2(), 0),
|
||||
new AnimationTrackProperty.KeyFrame(entity.Comp.HopIntensity, len/2),
|
||||
new AnimationTrackProperty.KeyFrame(new Vector2(), len/2),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_animation.Play(entity.Owner, anim, entity.Comp.KeyName);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:tabs="clr-namespace:Content.Client.Options.UI.Tabs"
|
||||
xmlns:options="clr-namespace:Content.Client._CP14.Options"
|
||||
Title="{Loc 'ui-options-title'}"
|
||||
MinSize="800 450">
|
||||
<TabContainer Name="Tabs" Access="Public">
|
||||
@@ -8,5 +9,8 @@
|
||||
<tabs:KeyRebindTab Name="KeyRebindTab" />
|
||||
<tabs:AudioTab Name="AudioTab" />
|
||||
<tabs:AccessibilityTab Name="AccessibilityTab" />
|
||||
<!-- CP14-options-menu-start -->
|
||||
<options:CP14OptionsMenuMainTab Name="CP14OptionsMenuTab"/>
|
||||
<!-- CP14-options-menu-end -->
|
||||
</TabContainer>
|
||||
</DefaultWindow>
|
||||
|
||||
@@ -19,6 +19,10 @@ namespace Content.Client.Options.UI
|
||||
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
|
||||
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
|
||||
|
||||
// CP14-options-menu-start
|
||||
Tabs.SetTabTitle(5, Loc.GetString("cp14-ui-options-tab-main"));
|
||||
// CP14-options-menu-end
|
||||
|
||||
UpdateTabs();
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public sealed partial class StencilOverlay
|
||||
|
||||
// Draw the rain
|
||||
worldHandle.UseShader(_protoManager.Index<ShaderPrototype>("StencilDraw").Instance());
|
||||
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, Vector2.Zero, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha));
|
||||
_parallax.DrawParallax(worldHandle, worldAABB, sprite, curTime, position, weatherProto.OffsetSpeed, modulate: (weatherProto.Color ?? Color.White).WithAlpha(alpha*weatherProto.Alpha)); //CP14 alpha and offset scrolling
|
||||
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
worldHandle.UseShader(null);
|
||||
|
||||
@@ -74,16 +74,19 @@ public sealed partial class StencilOverlay : Overlay
|
||||
DrawRestrictedRange(args, restrictedRangeComponent, invMatrix);
|
||||
}
|
||||
|
||||
|
||||
//CP14 Overlays
|
||||
if (_entManager.TryGetComponent<CP14WorldEdgeComponent>(mapUid, out var worldEdge))
|
||||
{
|
||||
DrawWorldEdge(args, worldEdge, invMatrix);
|
||||
}
|
||||
//CP14 Overlays end
|
||||
|
||||
//CP14 Overlays
|
||||
if (_entManager.TryGetComponent<CP14CloudShadowsComponent>(mapUid, out var shadows))
|
||||
{
|
||||
DrawCloudShadows(args, shadows, invMatrix);
|
||||
}
|
||||
|
||||
if (_entManager.TryGetComponent<CP14WorldEdgeComponent>(mapUid, out var worldEdge))
|
||||
{
|
||||
DrawWorldEdge(args, worldEdge, invMatrix);
|
||||
}
|
||||
//CP14 Overlays end
|
||||
|
||||
args.WorldHandle.UseShader(null);
|
||||
|
||||
@@ -2,6 +2,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Utility;
|
||||
using Content.Shared.Paper;
|
||||
using static Content.Shared.Paper.PaperComponent;
|
||||
|
||||
namespace Content.Client.Paper.UI;
|
||||
@@ -23,6 +24,10 @@ public sealed class PaperBoundUserInterface : BoundUserInterface
|
||||
_window = this.CreateWindow<PaperWindow>();
|
||||
_window.OnSaved += InputOnTextEntered;
|
||||
|
||||
if (EntMan.TryGetComponent<PaperComponent>(Owner, out var paper))
|
||||
{
|
||||
_window.MaxInputLength = paper.ContentSize;
|
||||
}
|
||||
if (EntMan.TryGetComponent<PaperVisualsComponent>(Owner, out var visuals))
|
||||
{
|
||||
_window.InitVisuals(Owner, visuals);
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
<Control Name="TextAlignmentPadding" VerticalAlignment="Top"/>
|
||||
<RichTextLabel Name="BlankPaperIndicator" StyleClasses="LabelSecondaryColor" VerticalAlignment="Top" HorizontalAlignment="Center"/>
|
||||
<RichTextLabel StyleClasses="PaperWrittenText" Name="WrittenTextLabel" VerticalAlignment="Top"/>
|
||||
<PanelContainer Name="InputContainer" StyleClasses="TransparentBorderedWindowPanel" MinHeight="100"
|
||||
VerticalAlignment="Stretch" VerticalExpand="True" HorizontalExpand="True">
|
||||
<TextEdit Name="Input" StyleClasses="PaperLineEdit" Access="Public" />
|
||||
</PanelContainer>
|
||||
<BoxContainer Name="InputContainer" Orientation="Vertical" VerticalExpand="True" VerticalAlignment="Stretch">
|
||||
<PanelContainer StyleClasses="TransparentBorderedWindowPanel" MinHeight="100"
|
||||
VerticalAlignment="Stretch" VerticalExpand="True" HorizontalExpand="True">
|
||||
<TextEdit Name="Input" StyleClasses="PaperLineEdit" Access="Public" />
|
||||
</PanelContainer>
|
||||
<Label Name="FillStatus" StyleClasses="LabelSecondaryColor"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
<paper:StampCollection Name="StampDisplay" VerticalAlignment="Bottom" Margin="6"/>
|
||||
|
||||
|
||||
@@ -48,6 +48,20 @@ namespace Content.Client.Paper.UI
|
||||
|
||||
public event Action<string>? OnSaved;
|
||||
|
||||
private int _MaxInputLength = -1;
|
||||
public int MaxInputLength
|
||||
{
|
||||
get
|
||||
{
|
||||
return _MaxInputLength;
|
||||
}
|
||||
set
|
||||
{
|
||||
_MaxInputLength = value;
|
||||
UpdateFillState();
|
||||
}
|
||||
}
|
||||
|
||||
public PaperWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -63,11 +77,21 @@ namespace Content.Client.Paper.UI
|
||||
{
|
||||
if (args.Function == EngineKeyFunctions.MultilineTextSubmit)
|
||||
{
|
||||
RunOnSaved();
|
||||
args.Handle();
|
||||
// SaveButton is disabled when we hit the max input limit. Just check
|
||||
// that flag instead of trying to calculate the input length again
|
||||
if (!SaveButton.Disabled)
|
||||
{
|
||||
RunOnSaved();
|
||||
args.Handle();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Input.OnTextChanged += args =>
|
||||
{
|
||||
UpdateFillState();
|
||||
};
|
||||
|
||||
SaveButton.OnPressed += _ =>
|
||||
{
|
||||
RunOnSaved();
|
||||
@@ -126,6 +150,7 @@ namespace Content.Client.Paper.UI
|
||||
|
||||
PaperContent.ModulateSelfOverride = visuals.ContentImageModulate;
|
||||
WrittenTextLabel.ModulateSelfOverride = visuals.FontAccentColor;
|
||||
FillStatus.ModulateSelfOverride = visuals.FontAccentColor;
|
||||
|
||||
var contentImage = visuals.ContentImagePath != null ? _resCache.GetResource<TextureResource>(visuals.ContentImagePath) : null;
|
||||
if (contentImage != null)
|
||||
@@ -294,7 +319,29 @@ namespace Content.Client.Paper.UI
|
||||
|
||||
private void RunOnSaved()
|
||||
{
|
||||
// Prevent further saving while text processing still in
|
||||
SaveButton.Disabled = true;
|
||||
OnSaved?.Invoke(Rope.Collapse(Input.TextRope));
|
||||
}
|
||||
|
||||
private void UpdateFillState()
|
||||
{
|
||||
if (MaxInputLength != -1)
|
||||
{
|
||||
var inputLength = Input.TextLength;
|
||||
|
||||
FillStatus.Text = Loc.GetString("paper-ui-fill-level",
|
||||
("currentLength", inputLength),
|
||||
("maxLength", MaxInputLength));
|
||||
|
||||
// Disable the save button if we've gone over the limit
|
||||
SaveButton.Disabled = inputLength > MaxInputLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
FillStatus.Text = "";
|
||||
SaveButton.Disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml
Normal file
19
Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml
Normal file
@@ -0,0 +1,19 @@
|
||||
<Control xmlns="https://spacestation14.io" HorizontalExpand="True">
|
||||
<BoxContainer Name="MainContainer"
|
||||
Orientation="Horizontal"
|
||||
HorizontalExpand="True">
|
||||
<PanelContainer Name="ColorPanel"
|
||||
VerticalExpand="True"
|
||||
SetWidth="7"
|
||||
Margin="0 1 0 0" />
|
||||
<Button Name="MainButton"
|
||||
HorizontalExpand="True"
|
||||
VerticalExpand="True"
|
||||
StyleClasses="ButtonSquare"
|
||||
Margin="-1 0 0 0">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
|
||||
<Label Name="BeaconNameLabel" />
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
</BoxContainer>
|
||||
</Control>
|
||||
50
Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml.cs
Normal file
50
Content.Client/Pinpointer/UI/StationMapBeaconControl.xaml.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using Content.Shared.Pinpointer;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StationMapBeaconControl : Control, IComparable<StationMapBeaconControl>
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
public readonly EntityCoordinates BeaconPosition;
|
||||
public Action<EntityCoordinates>? OnPressed;
|
||||
public string? Label => BeaconNameLabel.Text;
|
||||
private StyleBoxFlat _styleBox;
|
||||
public Color Color => _styleBox.BackgroundColor;
|
||||
|
||||
public StationMapBeaconControl(EntityUid mapUid, SharedNavMapSystem.NavMapBeacon beacon)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
BeaconPosition = new EntityCoordinates(mapUid, beacon.Position);
|
||||
|
||||
_styleBox = new StyleBoxFlat { BackgroundColor = beacon.Color };
|
||||
ColorPanel.PanelOverride = _styleBox;
|
||||
BeaconNameLabel.Text = beacon.Text;
|
||||
|
||||
MainButton.OnPressed += args => OnPressed?.Invoke(BeaconPosition);
|
||||
}
|
||||
|
||||
public int CompareTo(StationMapBeaconControl? other)
|
||||
{
|
||||
if (other == null)
|
||||
return 1;
|
||||
|
||||
// Group by color
|
||||
var colorCompare = Color.ToArgb().CompareTo(other.Color.ToArgb());
|
||||
if (colorCompare != 0)
|
||||
{
|
||||
return colorCompare;
|
||||
}
|
||||
|
||||
// If same color, sort by text
|
||||
return string.Compare(Label, other.Label);
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,16 @@ public sealed class StationMapBoundUserInterface : BoundUserInterface
|
||||
|
||||
_window = this.CreateWindow<StationMapWindow>();
|
||||
_window.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
|
||||
string stationName = string.Empty;
|
||||
if(EntMan.TryGetComponent<MetaDataComponent>(gridUid, out var gridMetaData))
|
||||
{
|
||||
stationName = gridMetaData.EntityName;
|
||||
}
|
||||
|
||||
if (EntMan.TryGetComponent<StationMapComponent>(Owner, out var comp) && comp.ShowLocation)
|
||||
_window.Set(gridUid, Owner);
|
||||
_window.Set(stationName, gridUid, Owner);
|
||||
else
|
||||
_window.Set(gridUid, null);
|
||||
_window.Set(stationName, gridUid, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,28 @@
|
||||
xmlns:ui="clr-namespace:Content.Client.Pinpointer.UI"
|
||||
Title="{Loc 'station-map-window-title'}"
|
||||
Resizable="False"
|
||||
SetSize="668 713"
|
||||
MinSize="668 713">
|
||||
SetSize="868 748"
|
||||
MinSize="868 748">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="0 8 0 10" VerticalAlignment="Top">
|
||||
<!-- Station name -->
|
||||
<controls:StripeBack>
|
||||
<PanelContainer>
|
||||
<Label Name="StationName" Text="Unknown station" StyleClasses="LabelBig" Align="Center"/>
|
||||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
|
||||
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" VerticalAlignment="Top">
|
||||
<ui:NavMapControl Name="NavMapScreen"/>
|
||||
|
||||
<BoxContainer Orientation="Vertical" SetWidth="200">
|
||||
<!-- Search bar -->
|
||||
<LineEdit Name="FilterBar" PlaceHolder="{Loc 'station-map-filter-placeholder'}" Margin="0 0 10 10" HorizontalExpand="True"/>
|
||||
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True">
|
||||
<!-- Beacon Buttons (filled by code) -->
|
||||
<BoxContainer Name="BeaconButtons" Orientation="Vertical" HorizontalExpand="True" />
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
|
||||
<!-- Footer -->
|
||||
|
||||
@@ -3,24 +3,75 @@ using Content.Client.UserInterface.Controls;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Map;
|
||||
using Content.Shared.Pinpointer;
|
||||
|
||||
namespace Content.Client.Pinpointer.UI;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class StationMapWindow : FancyWindow
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
|
||||
private readonly List<StationMapBeaconControl> _buttons = new();
|
||||
|
||||
public StationMapWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
FilterBar.OnTextChanged += (bar) => OnFilterChanged(bar.Text);
|
||||
}
|
||||
|
||||
public void Set(EntityUid? mapUid, EntityUid? trackedEntity)
|
||||
public void Set(string stationName, EntityUid? mapUid, EntityUid? trackedEntity)
|
||||
{
|
||||
NavMapScreen.MapUid = mapUid;
|
||||
|
||||
if (trackedEntity != null)
|
||||
NavMapScreen.TrackedCoordinates.Add(new EntityCoordinates(trackedEntity.Value, Vector2.Zero), (true, Color.Cyan));
|
||||
|
||||
if (!string.IsNullOrEmpty(stationName))
|
||||
{
|
||||
StationName.Text = stationName;
|
||||
}
|
||||
|
||||
NavMapScreen.ForceNavMapUpdate();
|
||||
UpdateBeaconList(mapUid);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnFilterChanged(string newFilter)
|
||||
{
|
||||
foreach (var button in _buttons)
|
||||
{
|
||||
button.Visible = string.IsNullOrEmpty(newFilter) || (
|
||||
!string.IsNullOrEmpty(button.Label) &&
|
||||
button.Label.Contains(newFilter, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateBeaconList(EntityUid? mapUid)
|
||||
{
|
||||
BeaconButtons.Children.Clear();
|
||||
_buttons.Clear();
|
||||
|
||||
if (!mapUid.HasValue)
|
||||
return;
|
||||
|
||||
if (!_entMan.TryGetComponent<NavMapComponent>(mapUid, out var navMap))
|
||||
return;
|
||||
|
||||
foreach (var beacon in navMap.Beacons.Values)
|
||||
{
|
||||
var button = new StationMapBeaconControl(mapUid.Value, beacon);
|
||||
|
||||
button.OnPressed += NavMapScreen.CenterToCoordinates;
|
||||
|
||||
_buttons.Add(button);
|
||||
}
|
||||
|
||||
_buttons.Sort();
|
||||
|
||||
foreach (var button in _buttons)
|
||||
BeaconButtons.AddChild(button);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Players.RateLimiting;
|
||||
|
||||
public sealed class PlayerRateLimitManager : SharedPlayerRateLimitManager
|
||||
{
|
||||
public override RateLimitStatus CountAction(ICommonSession player, string key)
|
||||
{
|
||||
// TODO Rate-Limit
|
||||
// Add support for rate limit prediction
|
||||
// I.e., dont mis-predict just because somebody is clicking too quickly.
|
||||
return RateLimitStatus.Allowed;
|
||||
}
|
||||
|
||||
public override void Register(string key, RateLimitRegistration registration)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,12 @@ namespace Content.Client.Popups
|
||||
}
|
||||
|
||||
public override void PopupCursor(string? message, PopupType type = PopupType.Small)
|
||||
=> PopupCursorInternal(message, type, true);
|
||||
{
|
||||
if (!_timing.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
PopupCursorInternal(message, type, true);
|
||||
}
|
||||
|
||||
public override void PopupCursor(string? message, ICommonSession recipient, PopupType type = PopupType.Small)
|
||||
{
|
||||
|
||||
@@ -199,7 +199,9 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
|
||||
var gridMatrix = _transform.GetWorldMatrix(gUid);
|
||||
var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert);
|
||||
var color = _shuttles.GetIFFColor(grid, self: false, iff);
|
||||
|
||||
var labelColor = _shuttles.GetIFFColor(grid, self: false, iff);
|
||||
var coordColor = new Color(labelColor.R * 0.8f, labelColor.G * 0.8f, labelColor.B * 0.8f, 0.5f);
|
||||
|
||||
// Others default:
|
||||
// Color.FromHex("#FFC000FF")
|
||||
@@ -213,25 +215,52 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
|
||||
var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty);
|
||||
gridCentre.Y = -gridCentre.Y;
|
||||
|
||||
var distance = gridCentre.Length();
|
||||
var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName),
|
||||
("distance", $"{distance:0.0}"));
|
||||
|
||||
var mapCoords = _transform.GetWorldPosition(gUid);
|
||||
var coordsText = $"({mapCoords.X:0.0}, {mapCoords.Y:0.0})";
|
||||
|
||||
// yes 1.0 scale is intended here.
|
||||
var labelDimensions = handle.GetDimensions(Font, labelText, 1f);
|
||||
var coordsDimensions = handle.GetDimensions(Font, coordsText, 0.7f);
|
||||
|
||||
// y-offset the control to always render below the grid (vertically)
|
||||
var yOffset = Math.Max(gridBounds.Height, gridBounds.Width) * MinimapScale / 1.8f;
|
||||
|
||||
// The actual position in the UI. We offset the matrix position to render it off by half its width
|
||||
// plus by the offset.
|
||||
var uiPosition = ScalePosition(gridCentre)- new Vector2(labelDimensions.X / 2f, -yOffset);
|
||||
// The actual position in the UI. We centre the label by offsetting the matrix position
|
||||
// by half the label's width, plus the y-offset
|
||||
var gridScaledPosition = ScalePosition(gridCentre) - new Vector2(0, -yOffset);
|
||||
|
||||
// Look this is uggo so feel free to cleanup. We just need to clamp the UI position to within the viewport.
|
||||
uiPosition = new Vector2(Math.Clamp(uiPosition.X, 0f, PixelWidth - labelDimensions.X ),
|
||||
Math.Clamp(uiPosition.Y, 0f, PixelHeight - labelDimensions.Y));
|
||||
// Normalize the grid position if it exceeds the viewport bounds
|
||||
// normalizing it instead of clamping it preserves the direction of the vector and prevents corner-hugging
|
||||
var gridOffset = gridScaledPosition / PixelSize - new Vector2(0.5f, 0.5f);
|
||||
var offsetMax = Math.Max(Math.Abs(gridOffset.X), Math.Abs(gridOffset.Y)) * 2f;
|
||||
if (offsetMax > 1)
|
||||
{
|
||||
gridOffset = new Vector2(gridOffset.X / offsetMax, gridOffset.Y / offsetMax);
|
||||
|
||||
handle.DrawString(Font, uiPosition, labelText, color);
|
||||
gridScaledPosition = (gridOffset + new Vector2(0.5f, 0.5f)) * PixelSize;
|
||||
}
|
||||
|
||||
var labelUiPosition = gridScaledPosition - new Vector2(labelDimensions.X / 2f, 0);
|
||||
var coordUiPosition = gridScaledPosition - new Vector2(coordsDimensions.X / 2f, -labelDimensions.Y);
|
||||
|
||||
// clamp the IFF label's UI position to within the viewport extents so it hugs the edges of the viewport
|
||||
// coord label intentionally isn't clamped so we don't get ugly clutter at the edges
|
||||
var controlExtents = PixelSize - new Vector2(labelDimensions.X, labelDimensions.Y); //new Vector2(labelDimensions.X * 2f, labelDimensions.Y);
|
||||
labelUiPosition = Vector2.Clamp(labelUiPosition, Vector2.Zero, controlExtents);
|
||||
|
||||
// draw IFF label
|
||||
handle.DrawString(Font, labelUiPosition, labelText, labelColor);
|
||||
|
||||
// only draw coords label if close enough
|
||||
if (offsetMax < 1)
|
||||
{
|
||||
handle.DrawString(Font, coordUiPosition, coordsText, 0.7f, coordColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Detailed view
|
||||
@@ -241,7 +270,7 @@ public sealed partial class ShuttleNavControl : BaseShuttleControl
|
||||
if (!gridAABB.Intersects(viewAABB))
|
||||
continue;
|
||||
|
||||
DrawGrid(handle, matty, grid, color);
|
||||
DrawGrid(handle, matty, grid, labelColor);
|
||||
DrawDocks(handle, gUid, matty);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Content.Shared.Doors.Components;
|
||||
using Content.Shared.Electrocution;
|
||||
using Content.Shared.Silicons.StationAi;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -6,25 +7,69 @@ namespace Content.Client.Silicons.StationAi;
|
||||
|
||||
public sealed partial class StationAiSystem
|
||||
{
|
||||
private readonly ResPath _aiActionsRsi = new ResPath("/Textures/Interface/Actions/actions_ai.rsi");
|
||||
|
||||
private void InitializeAirlock()
|
||||
{
|
||||
SubscribeLocalEvent<DoorBoltComponent, GetStationAiRadialEvent>(OnDoorBoltGetRadial);
|
||||
SubscribeLocalEvent<AirlockComponent, GetStationAiRadialEvent>(OnEmergencyAccessGetRadial);
|
||||
SubscribeLocalEvent<ElectrifiedComponent, GetStationAiRadialEvent>(OnDoorElectrifiedGetRadial);
|
||||
}
|
||||
|
||||
private void OnDoorBoltGetRadial(Entity<DoorBoltComponent> ent, ref GetStationAiRadialEvent args)
|
||||
{
|
||||
args.Actions.Add(new StationAiRadial()
|
||||
{
|
||||
Sprite = ent.Comp.BoltsDown ?
|
||||
new SpriteSpecifier.Rsi(
|
||||
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") :
|
||||
new SpriteSpecifier.Rsi(
|
||||
new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"),
|
||||
Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"),
|
||||
Event = new StationAiBoltEvent()
|
||||
args.Actions.Add(
|
||||
new StationAiRadial
|
||||
{
|
||||
Bolted = !ent.Comp.BoltsDown,
|
||||
Sprite = ent.Comp.BoltsDown
|
||||
? new SpriteSpecifier.Rsi(_aiActionsRsi, "unbolt_door")
|
||||
: new SpriteSpecifier.Rsi(_aiActionsRsi, "bolt_door"),
|
||||
Tooltip = ent.Comp.BoltsDown
|
||||
? Loc.GetString("bolt-open")
|
||||
: Loc.GetString("bolt-close"),
|
||||
Event = new StationAiBoltEvent
|
||||
{
|
||||
Bolted = !ent.Comp.BoltsDown,
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
private void OnEmergencyAccessGetRadial(Entity<AirlockComponent> ent, ref GetStationAiRadialEvent args)
|
||||
{
|
||||
args.Actions.Add(
|
||||
new StationAiRadial
|
||||
{
|
||||
Sprite = ent.Comp.EmergencyAccess
|
||||
? new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_off")
|
||||
: new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_on"),
|
||||
Tooltip = ent.Comp.EmergencyAccess
|
||||
? Loc.GetString("emergency-access-off")
|
||||
: Loc.GetString("emergency-access-on"),
|
||||
Event = new StationAiEmergencyAccessEvent
|
||||
{
|
||||
EmergencyAccess = !ent.Comp.EmergencyAccess,
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void OnDoorElectrifiedGetRadial(Entity<ElectrifiedComponent> ent, ref GetStationAiRadialEvent args)
|
||||
{
|
||||
args.Actions.Add(
|
||||
new StationAiRadial
|
||||
{
|
||||
Sprite = ent.Comp.Enabled
|
||||
? new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_off")
|
||||
: new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_on"),
|
||||
Tooltip = ent.Comp.Enabled
|
||||
? Loc.GetString("electrify-door-off")
|
||||
: Loc.GetString("electrify-door-on"),
|
||||
Event = new StationAiElectrifiedEvent
|
||||
{
|
||||
Electrified = !ent.Comp.Enabled,
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,6 +695,18 @@ namespace Content.Client.Stylesheets
|
||||
new StyleProperty("font-color", Color.FromHex("#E5E5E581")),
|
||||
}),
|
||||
|
||||
// ItemStatus for hands
|
||||
Element()
|
||||
.Class(StyleClassItemStatusNotHeld)
|
||||
.Prop("font", notoSansItalic10)
|
||||
.Prop("font-color", ItemStatusNotHeldColor)
|
||||
.Prop(nameof(Control.Margin), new Thickness(4, 0, 0, 2)),
|
||||
|
||||
Element()
|
||||
.Class(StyleClassItemStatus)
|
||||
.Prop(nameof(RichTextLabel.LineHeightScale), 0.7f)
|
||||
.Prop(nameof(Control.Margin), new Thickness(4, 0, 0, 2)),
|
||||
|
||||
// Context Menu window
|
||||
Element<PanelContainer>().Class(ContextMenuPopup.StyleClassContextMenuPopup)
|
||||
.Prop(PanelContainer.StylePropertyPanel, contextMenuBackground),
|
||||
|
||||
@@ -69,7 +69,7 @@ public sealed class ParacusiaSystem : SharedParacusiaSystem
|
||||
var newCoords = Transform(uid).Coordinates.Offset(randomOffset);
|
||||
|
||||
// Play the sound
|
||||
paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords).Value.Entity;
|
||||
paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords)?.Entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -87,6 +87,9 @@ public sealed partial class DialogWindow : FancyWindow
|
||||
Prompts.AddChild(box);
|
||||
}
|
||||
|
||||
// Grab keyboard focus for the first dialog entry
|
||||
_promptLines[0].Item2.GrabKeyboardFocus();
|
||||
|
||||
OkButton.OnPressed += _ => Confirm();
|
||||
|
||||
CancelButton.OnPressed += _ =>
|
||||
|
||||
@@ -398,10 +398,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
||||
{
|
||||
QueueWindowUpdate();
|
||||
|
||||
// TODO ACTIONS allow buttons to persist across state applications
|
||||
// Then we don't have to interrupt drags any time the buttons get rebuilt.
|
||||
_menuDragHelper.EndDrag();
|
||||
|
||||
if (_actionsSystem != null)
|
||||
_container?.SetActionData(_actionsSystem, _actions.ToArray());
|
||||
}
|
||||
|
||||
@@ -28,14 +28,26 @@ public class ActionButtonContainer : GridContainer
|
||||
get => (ActionButton) GetChild(index);
|
||||
}
|
||||
|
||||
private void BuildActionButtons(int count)
|
||||
public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes)
|
||||
{
|
||||
var uniqueCount = Math.Min(system.GetClientActions().Count(), actionTypes.Length + 1);
|
||||
var keys = ContentKeyFunctions.GetHotbarBoundKeys();
|
||||
|
||||
Children.Clear();
|
||||
for (var index = 0; index < count; index++)
|
||||
for (var i = 0; i < uniqueCount; i++)
|
||||
{
|
||||
Children.Add(MakeButton(index));
|
||||
if (i >= ChildCount)
|
||||
{
|
||||
AddChild(MakeButton(i));
|
||||
}
|
||||
|
||||
if (!actionTypes.TryGetValue(i, out var action))
|
||||
action = null;
|
||||
((ActionButton) GetChild(i)).UpdateData(action, system);
|
||||
}
|
||||
|
||||
for (var i = ChildCount - 1; i >= uniqueCount; i--)
|
||||
{
|
||||
RemoveChild(GetChild(i));
|
||||
}
|
||||
|
||||
ActionButton MakeButton(int index)
|
||||
@@ -55,20 +67,6 @@ public class ActionButtonContainer : GridContainer
|
||||
}
|
||||
}
|
||||
|
||||
public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes)
|
||||
{
|
||||
var uniqueCount = Math.Min(system.GetClientActions().Count(), actionTypes.Length + 1);
|
||||
if (ChildCount != uniqueCount)
|
||||
BuildActionButtons(uniqueCount);
|
||||
|
||||
for (var i = 0; i < uniqueCount; i++)
|
||||
{
|
||||
if (!actionTypes.TryGetValue(i, out var action))
|
||||
action = null;
|
||||
((ActionButton) GetChild(i)).UpdateData(action, system);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearActionData()
|
||||
{
|
||||
foreach (var button in Children)
|
||||
|
||||
@@ -23,29 +23,17 @@ namespace Content.Client.VendingMachines
|
||||
{
|
||||
base.Open();
|
||||
|
||||
var vendingMachineSys = EntMan.System<VendingMachineSystem>();
|
||||
|
||||
_cachedInventory = vendingMachineSys.GetAllInventory(Owner);
|
||||
|
||||
_menu = this.CreateWindow<VendingMachineMenu>();
|
||||
_menu.OpenCenteredLeft();
|
||||
_menu.Title = EntMan.GetComponent<MetaDataComponent>(Owner).EntityName;
|
||||
|
||||
_menu.OnItemSelected += OnItemSelected;
|
||||
|
||||
_menu.Populate(_cachedInventory);
|
||||
|
||||
_menu.OpenCenteredLeft();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
public void Refresh()
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
if (state is not VendingMachineInterfaceState newState)
|
||||
return;
|
||||
|
||||
_cachedInventory = newState.Inventory;
|
||||
var system = EntMan.System<VendingMachineSystem>();
|
||||
_cachedInventory = system.GetAllInventory(Owner);
|
||||
|
||||
_menu?.Populate(_cachedInventory);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem
|
||||
{
|
||||
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
[Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -15,6 +16,15 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem
|
||||
|
||||
SubscribeLocalEvent<VendingMachineComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
SubscribeLocalEvent<VendingMachineComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<VendingMachineComponent, AfterAutoHandleStateEvent>(OnVendingAfterState);
|
||||
}
|
||||
|
||||
private void OnVendingAfterState(EntityUid uid, VendingMachineComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
if (_uiSystem.TryGetOpenUi<VendingMachineBoundUserInterface>(uid, VendingMachineUiKey.Key, out var bui))
|
||||
{
|
||||
bui.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(EntityUid uid, VendingMachineComponent component, AnimationCompletedEvent args)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Examine;
|
||||
using Content.Client.Gameplay;
|
||||
using Content.Client.Popups;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
@@ -13,6 +13,8 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -28,11 +30,11 @@ namespace Content.Client.Verbs
|
||||
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
|
||||
/// <summary>
|
||||
/// When a user right clicks somewhere, how large is the box we use to get entities for the context menu?
|
||||
/// </summary>
|
||||
public const float EntityMenuLookupSize = 0.25f;
|
||||
private float _lookupSize;
|
||||
|
||||
/// <summary>
|
||||
/// These flags determine what entities the user can see on the context menu.
|
||||
@@ -41,114 +43,127 @@ namespace Content.Client.Verbs
|
||||
|
||||
public Action<VerbsResponseEvent>? OnVerbsResponse;
|
||||
|
||||
private List<EntityUid> _entities = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<VerbsResponseEvent>(HandleVerbResponse);
|
||||
Subs.CVar(_cfg, CCVars.GameEntityMenuLookup, OnLookupChanged, true);
|
||||
}
|
||||
|
||||
private void OnLookupChanged(float val)
|
||||
{
|
||||
_lookupSize = val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all of the entities in an area for displaying on the context menu.
|
||||
/// Get all of the entities in an area for displaying on the context menu.
|
||||
/// </summary>
|
||||
public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List<EntityUid>? result)
|
||||
/// <returns>True if any entities were found.</returns>
|
||||
public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List<EntityUid>? entities)
|
||||
{
|
||||
result = null;
|
||||
entities = null;
|
||||
|
||||
if (_stateManager.CurrentState is not GameplayStateBase gameScreenBase)
|
||||
if (_stateManager.CurrentState is not GameplayStateBase)
|
||||
return false;
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
if (player == null)
|
||||
if (_playerManager.LocalEntity is not { } player)
|
||||
return false;
|
||||
|
||||
// If FOV drawing is disabled, we will modify the visibility option to ignore visiblity checks.
|
||||
var visibility = _eyeManager.CurrentEye.DrawFov
|
||||
? Visibility
|
||||
: Visibility | MenuVisibility.NoFov;
|
||||
var visibility = _eyeManager.CurrentEye.DrawFov ? Visibility : Visibility | MenuVisibility.NoFov;
|
||||
|
||||
var ev = new MenuVisibilityEvent()
|
||||
var ev = new MenuVisibilityEvent
|
||||
{
|
||||
TargetPos = targetPos,
|
||||
Visibility = visibility,
|
||||
};
|
||||
|
||||
RaiseLocalEvent(player.Value, ref ev);
|
||||
RaiseLocalEvent(player, ref ev);
|
||||
visibility = ev.Visibility;
|
||||
|
||||
// Get entities
|
||||
_entities.Clear();
|
||||
var entitiesUnderMouse = _tree.QueryAabb(targetPos.MapId, Box2.CenteredAround(targetPos.Position, new Vector2(EntityMenuLookupSize, EntityMenuLookupSize)));
|
||||
|
||||
// Do we have to do FoV checks?
|
||||
if ((visibility & MenuVisibility.NoFov) == 0)
|
||||
// Initially, we include all entities returned by a sprite area lookup
|
||||
var box = Box2.CenteredAround(targetPos.Position, new Vector2(_lookupSize, _lookupSize));
|
||||
var queryResult = _tree.QueryAabb(targetPos.MapId, box);
|
||||
entities = new List<EntityUid>(queryResult.Count);
|
||||
foreach (var ent in queryResult)
|
||||
{
|
||||
bool Predicate(EntityUid e) => e == player;
|
||||
|
||||
TryComp(player.Value, out ExaminerComponent? examiner);
|
||||
|
||||
foreach (var ent in entitiesUnderMouse)
|
||||
{
|
||||
if (_examine.CanExamine(player.Value, targetPos, Predicate, ent.Uid, examiner))
|
||||
_entities.Add(ent.Uid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var ent in entitiesUnderMouse)
|
||||
{
|
||||
_entities.Add(ent.Uid);
|
||||
}
|
||||
entities.Add(ent.Uid);
|
||||
}
|
||||
|
||||
if (_entities.Count == 0)
|
||||
return false;
|
||||
|
||||
if (visibility == MenuVisibility.All)
|
||||
// If we're in a container list all other entities in it.
|
||||
// E.g., allow players in lockers to examine / interact with other entities in the same locker
|
||||
if (_containers.TryGetContainingContainer((player, null), out var container))
|
||||
{
|
||||
result = new (_entities);
|
||||
return true;
|
||||
}
|
||||
|
||||
// remove any entities in containers
|
||||
if ((visibility & MenuVisibility.InContainer) == 0)
|
||||
{
|
||||
for (var i = _entities.Count - 1; i >= 0; i--)
|
||||
// Only include the container contents when clicking near it.
|
||||
if (entities.Contains(container.Owner)
|
||||
|| _containers.TryGetOuterContainer(container.Owner, Transform(container.Owner), out var outer)
|
||||
&& entities.Contains(outer.Owner))
|
||||
{
|
||||
var entity = _entities[i];
|
||||
// The container itself might be in some other container, so it might not have been added by the
|
||||
// sprite tree lookup.
|
||||
if (!entities.Contains(container.Owner))
|
||||
entities.Add(container.Owner);
|
||||
|
||||
if (ContainerSystem.IsInSameOrTransparentContainer(player.Value, entity))
|
||||
continue;
|
||||
|
||||
_entities.RemoveSwap(i);
|
||||
}
|
||||
}
|
||||
|
||||
// remove any invisible entities
|
||||
if ((visibility & MenuVisibility.Invisible) == 0)
|
||||
{
|
||||
var spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
|
||||
for (var i = _entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var entity = _entities[i];
|
||||
|
||||
if (!spriteQuery.TryGetComponent(entity, out var spriteComponent) ||
|
||||
!spriteComponent.Visible ||
|
||||
_tagSystem.HasTag(entity, "HideContextMenu"))
|
||||
// TODO Context Menu
|
||||
// This might miss entities in some situations. E.g., one of the contained entities entity in it, that
|
||||
// itself has another entity attached to it, then we should be able to "see" that entity.
|
||||
// E.g., if a security guard is on a segway and gets thrown in a locker, this wouldn't let you see the guard.
|
||||
foreach (var ent in container.ContainedEntities)
|
||||
{
|
||||
_entities.RemoveSwap(i);
|
||||
if (!entities.Contains(ent))
|
||||
entities.Add(ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_entities.Count == 0)
|
||||
return false;
|
||||
if ((visibility & MenuVisibility.InContainer) != 0)
|
||||
{
|
||||
// This is inefficient, but I'm lazy and CBF implementing my own recursive container method. Note that
|
||||
// this might actually fail to add the contained children of some entities in the menu. E.g., an entity
|
||||
// with a large sprite aabb, but small broadphase might appear in the menu, but have its children added
|
||||
// by this.
|
||||
var flags = LookupFlags.All & ~LookupFlags.Sensors;
|
||||
foreach (var e in _lookup.GetEntitiesInRange(targetPos, _lookupSize, flags: flags))
|
||||
{
|
||||
if (!entities.Contains(e))
|
||||
entities.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
result = new(_entities);
|
||||
return true;
|
||||
// Do we have to do FoV checks?
|
||||
if ((visibility & MenuVisibility.NoFov) == 0)
|
||||
{
|
||||
TryComp(player, out ExaminerComponent? examiner);
|
||||
for (var i = entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!_examine.CanExamine(player, targetPos, e => e == player, entities[i], examiner))
|
||||
entities.RemoveSwap(i);
|
||||
}
|
||||
}
|
||||
|
||||
if ((visibility & MenuVisibility.Invisible) != 0)
|
||||
return entities.Count != 0;
|
||||
|
||||
for (var i = entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_tagSystem.HasTag(entities[i], "HideContextMenu"))
|
||||
entities.RemoveSwap(i);
|
||||
}
|
||||
|
||||
// Unless we added entities in containers, every entity should already have a visible sprite due to
|
||||
// the fact that we used the sprite tree query.
|
||||
if (container == null && (visibility & MenuVisibility.InContainer) == 0)
|
||||
return entities.Count != 0;
|
||||
|
||||
var spriteQuery = GetEntityQuery<SpriteComponent>();
|
||||
for (var i = entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!spriteQuery.TryGetComponent(entities[i], out var spriteComponent) || !spriteComponent.Visible)
|
||||
entities.RemoveSwap(i);
|
||||
}
|
||||
|
||||
return entities.Count != 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -22,6 +22,7 @@ public sealed class VoiceMaskBoundUserInterface : BoundUserInterface
|
||||
|
||||
_window = this.CreateWindow<VoiceMaskNameChangeWindow>();
|
||||
_window.ReloadVerbs(_protomanager);
|
||||
_window.AddVerbs();
|
||||
|
||||
_window.OnNameChange += OnNameSelected;
|
||||
_window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb));
|
||||
|
||||
@@ -31,8 +31,6 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
|
||||
OnVerbChange?.Invoke((string?) args.Button.GetItemMetadata(args.Id));
|
||||
SpeechVerbSelector.SelectId(args.Id);
|
||||
};
|
||||
|
||||
AddVerbs();
|
||||
}
|
||||
|
||||
public void ReloadVerbs(IPrototypeManager proto)
|
||||
@@ -44,7 +42,7 @@ public sealed partial class VoiceMaskNameChangeWindow : FancyWindow
|
||||
_verbs.Sort((a, b) => a.Item1.CompareTo(b.Item1));
|
||||
}
|
||||
|
||||
private void AddVerbs()
|
||||
public void AddVerbs()
|
||||
{
|
||||
SpeechVerbSelector.Clear();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<ui:VoteCallMenu xmlns="https://spacestation14.io"
|
||||
<ui:VoteCallMenu xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Voting.UI"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
MouseFilter="Stop" MinSize="350 150">
|
||||
MouseFilter="Stop" MinSize="350 200">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Margin="8 0" Orientation="Horizontal">
|
||||
@@ -13,16 +13,18 @@
|
||||
<controls:HighDivider />
|
||||
|
||||
<BoxContainer Orientation="Vertical" Margin="8 2 8 0" VerticalExpand="True" VerticalAlignment="Top">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<OptionButton Name="VoteTypeButton" HorizontalExpand="True" />
|
||||
<Control HorizontalExpand="True">
|
||||
<OptionButton Name="VoteSecondButton" Visible="False" />
|
||||
</Control>
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<OptionButton Margin="2 1" Name="VoteTypeButton" HorizontalExpand="False" />
|
||||
<BoxContainer Name="VoteOptionsButtonContainer" HorizontalExpand="False" Orientation="Vertical">
|
||||
</BoxContainer>
|
||||
<Button Margin="64 4" Name="FollowButton" Text="{Loc 'ui-vote-follow-button'}" Visible="False" />
|
||||
<Label Margin="2 2" Name="VoteNotTrustedLabel" Text="{Loc 'ui-vote-trusted-users-notice'}" Visible="False" />
|
||||
<Label Margin="2 2" Name="VoteWarningLabel" Text="{Loc 'ui-vote-abuse-warning'}" Visible="False" HorizontalAlignment="Center"/>
|
||||
</BoxContainer>
|
||||
<Label Name="VoteTypeTimeoutLabel" Visible="False" />
|
||||
<Label Margin="8 2" Name="VoteTypeTimeoutLabel" Visible="False" />
|
||||
</BoxContainer>
|
||||
|
||||
<Button Margin="8 2" Name="CreateButton" Text="{Loc 'ui-vote-create-button'}" />
|
||||
|
||||
<Button Margin="8 32 8 2" Name="CreateButton" Text="{Loc 'ui-vote-create-button'}" />
|
||||
|
||||
<PanelContainer StyleClasses="LowDivider" />
|
||||
<Label Margin="12 0 0 0" StyleClasses="LabelSubText" Text="{Loc 'ui-vote-fluff'}" />
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Ghost;
|
||||
using Content.Shared.Voting;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.AutoGenerated;
|
||||
@@ -9,10 +11,8 @@ using Robust.Client.Console;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -25,32 +25,54 @@ namespace Content.Client.Voting.UI
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IEntityNetworkManager _entNetManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
public static readonly (string name, StandardVoteType type, (string name, string id)[]? secondaries)[]
|
||||
AvailableVoteTypes =
|
||||
{
|
||||
("ui-vote-type-restart", StandardVoteType.Restart, null),
|
||||
("ui-vote-type-gamemode", StandardVoteType.Preset, null),
|
||||
("ui-vote-type-map", StandardVoteType.Map, null)
|
||||
};
|
||||
private VotingSystem _votingSystem;
|
||||
|
||||
public StandardVoteType Type;
|
||||
|
||||
public Dictionary<StandardVoteType, CreateVoteOption> AvailableVoteOptions = new Dictionary<StandardVoteType, CreateVoteOption>()
|
||||
{
|
||||
{ StandardVoteType.Restart, new CreateVoteOption("ui-vote-type-restart", new(), false, null) },
|
||||
{ StandardVoteType.Preset, new CreateVoteOption("ui-vote-type-gamemode", new(), false, null) },
|
||||
{ StandardVoteType.Map, new CreateVoteOption("ui-vote-type-map", new(), false, null) },
|
||||
{ StandardVoteType.Votekick, new CreateVoteOption("ui-vote-type-votekick", new(), true, 0) }
|
||||
};
|
||||
|
||||
public Dictionary<string, string> VotekickReasons = new Dictionary<string, string>()
|
||||
{
|
||||
{ VotekickReasonType.Raiding.ToString(), Loc.GetString("ui-vote-votekick-type-raiding") },
|
||||
{ VotekickReasonType.Cheating.ToString(), Loc.GetString("ui-vote-votekick-type-cheating") },
|
||||
{ VotekickReasonType.Spam.ToString(), Loc.GetString("ui-vote-votekick-type-spamming") }
|
||||
};
|
||||
|
||||
public Dictionary<NetUserId, (NetEntity, string)> PlayerList = new();
|
||||
|
||||
public OptionButton? _followDropdown = null;
|
||||
|
||||
public bool IsAllowedVotekick = false;
|
||||
|
||||
public VoteCallMenu()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
RobustXamlLoader.Load(this);
|
||||
_votingSystem = _entityManager.System<VotingSystem>();
|
||||
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
CloseButton.OnPressed += _ => Close();
|
||||
VoteNotTrustedLabel.Text = Loc.GetString("ui-vote-trusted-users-notice", ("timeReq", _cfg.GetCVar(CCVars.VotekickEligibleVoterDeathtime) / 60));
|
||||
|
||||
for (var i = 0; i < AvailableVoteTypes.Length; i++)
|
||||
foreach (StandardVoteType voteType in Enum.GetValues<StandardVoteType>())
|
||||
{
|
||||
var (text, _, _) = AvailableVoteTypes[i];
|
||||
VoteTypeButton.AddItem(Loc.GetString(text), i);
|
||||
var option = AvailableVoteOptions[voteType];
|
||||
VoteTypeButton.AddItem(Loc.GetString(option.Name), (int)voteType);
|
||||
}
|
||||
|
||||
VoteTypeButton.OnItemSelected += VoteTypeSelected;
|
||||
VoteSecondButton.OnItemSelected += VoteSecondSelected;
|
||||
CreateButton.OnPressed += CreatePressed;
|
||||
FollowButton.OnPressed += FollowSelected;
|
||||
}
|
||||
|
||||
protected override void Opened()
|
||||
@@ -60,6 +82,8 @@ namespace Content.Client.Voting.UI
|
||||
_netManager.ClientSendMessage(new MsgVoteMenu());
|
||||
|
||||
_voteManager.CanCallVoteChanged += CanCallVoteChanged;
|
||||
_votingSystem.VotePlayerListResponse += UpdateVotePlayerList;
|
||||
_votingSystem.RequestVotePlayerList();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
@@ -67,6 +91,7 @@ namespace Content.Client.Voting.UI
|
||||
base.Close();
|
||||
|
||||
_voteManager.CanCallVoteChanged -= CanCallVoteChanged;
|
||||
_votingSystem.VotePlayerListResponse -= UpdateVotePlayerList;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
@@ -82,21 +107,50 @@ namespace Content.Client.Voting.UI
|
||||
Close();
|
||||
}
|
||||
|
||||
private void UpdateVotePlayerList(VotePlayerListResponseEvent msg)
|
||||
{
|
||||
Dictionary<string, string> optionsList = new();
|
||||
Dictionary<NetUserId, (NetEntity, string)> playerList = new();
|
||||
foreach ((NetUserId, NetEntity, string) player in msg.Players)
|
||||
{
|
||||
optionsList.Add(player.Item1.ToString(), player.Item3);
|
||||
playerList.Add(player.Item1, (player.Item2, player.Item3));
|
||||
}
|
||||
if (optionsList.Count == 0)
|
||||
optionsList.Add(" ", " ");
|
||||
|
||||
PlayerList = playerList;
|
||||
|
||||
IsAllowedVotekick = !msg.Denied;
|
||||
|
||||
var updatedDropdownOption = AvailableVoteOptions[StandardVoteType.Votekick];
|
||||
updatedDropdownOption.Dropdowns = new List<Dictionary<string, string>>() { optionsList, VotekickReasons };
|
||||
AvailableVoteOptions[StandardVoteType.Votekick] = updatedDropdownOption;
|
||||
}
|
||||
|
||||
private void CreatePressed(BaseButton.ButtonEventArgs obj)
|
||||
{
|
||||
var typeId = VoteTypeButton.SelectedId;
|
||||
var (_, typeKey, secondaries) = AvailableVoteTypes[typeId];
|
||||
var voteType = AvailableVoteOptions[(StandardVoteType)typeId];
|
||||
|
||||
if (secondaries != null)
|
||||
var commandArgs = "";
|
||||
|
||||
if (voteType.Dropdowns == null || voteType.Dropdowns.Count == 0)
|
||||
{
|
||||
var secondaryId = VoteSecondButton.SelectedId;
|
||||
var (_, secondKey) = secondaries[secondaryId];
|
||||
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {typeKey} {secondKey}");
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {((StandardVoteType)typeId).ToString()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {typeKey}");
|
||||
int i = 0;
|
||||
foreach(var dropdowns in VoteOptionsButtonContainer.Children)
|
||||
{
|
||||
if (dropdowns is OptionButton optionButton && AvailableVoteOptions[(StandardVoteType)typeId].Dropdowns != null)
|
||||
{
|
||||
commandArgs += AvailableVoteOptions[(StandardVoteType)typeId].Dropdowns[i].ElementAt(optionButton.SelectedId).Key + " ";
|
||||
i++;
|
||||
}
|
||||
}
|
||||
_consoleHost.LocalShell.RemoteExecuteCommand($"createvote {((StandardVoteType)typeId).ToString()} {commandArgs}");
|
||||
}
|
||||
|
||||
Close();
|
||||
@@ -104,9 +158,16 @@ namespace Content.Client.Voting.UI
|
||||
|
||||
private void UpdateVoteTimeout()
|
||||
{
|
||||
var (_, typeKey, _) = AvailableVoteTypes[VoteTypeButton.SelectedId];
|
||||
var typeKey = (StandardVoteType)VoteTypeButton.SelectedId;
|
||||
var isAvailable = _voteManager.CanCallStandardVote(typeKey, out var timeout);
|
||||
CreateButton.Disabled = !isAvailable;
|
||||
if (typeKey == StandardVoteType.Votekick && !IsAllowedVotekick)
|
||||
{
|
||||
CreateButton.Disabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
CreateButton.Disabled = !isAvailable;
|
||||
}
|
||||
VoteTypeTimeoutLabel.Visible = !isAvailable;
|
||||
|
||||
if (!isAvailable)
|
||||
@@ -123,29 +184,73 @@ namespace Content.Client.Voting.UI
|
||||
}
|
||||
}
|
||||
|
||||
private static void VoteSecondSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
private static void ButtonSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
{
|
||||
obj.Button.SelectId(obj.Id);
|
||||
}
|
||||
|
||||
private void FollowSelected(Button.ButtonEventArgs obj)
|
||||
{
|
||||
if (_followDropdown == null)
|
||||
return;
|
||||
|
||||
if (_followDropdown.SelectedId >= PlayerList.Count)
|
||||
return;
|
||||
|
||||
var netEntity = PlayerList.ElementAt(_followDropdown.SelectedId).Value.Item1;
|
||||
|
||||
var msg = new GhostWarpToTargetRequestEvent(netEntity);
|
||||
_entNetManager.SendSystemNetworkMessage(msg);
|
||||
}
|
||||
|
||||
private void VoteTypeSelected(OptionButton.ItemSelectedEventArgs obj)
|
||||
{
|
||||
VoteTypeButton.SelectId(obj.Id);
|
||||
|
||||
var (_, _, options) = AvailableVoteTypes[obj.Id];
|
||||
if (options == null)
|
||||
VoteNotTrustedLabel.Visible = false;
|
||||
if ((StandardVoteType)obj.Id == StandardVoteType.Votekick)
|
||||
{
|
||||
VoteSecondButton.Visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
VoteSecondButton.Visible = true;
|
||||
VoteSecondButton.Clear();
|
||||
|
||||
for (var i = 0; i < options.Length; i++)
|
||||
if (!IsAllowedVotekick)
|
||||
{
|
||||
var (text, _) = options[i];
|
||||
VoteSecondButton.AddItem(Loc.GetString(text), i);
|
||||
VoteNotTrustedLabel.Visible = true;
|
||||
var updatedDropdownOption = AvailableVoteOptions[StandardVoteType.Votekick];
|
||||
updatedDropdownOption.Dropdowns = new List<Dictionary<string, string>>();
|
||||
AvailableVoteOptions[StandardVoteType.Votekick] = updatedDropdownOption;
|
||||
}
|
||||
else
|
||||
{
|
||||
_votingSystem.RequestVotePlayerList();
|
||||
}
|
||||
}
|
||||
|
||||
VoteWarningLabel.Visible = AvailableVoteOptions[(StandardVoteType)obj.Id].EnableVoteWarning;
|
||||
FollowButton.Visible = false;
|
||||
|
||||
var voteList = AvailableVoteOptions[(StandardVoteType)obj.Id].Dropdowns;
|
||||
|
||||
VoteOptionsButtonContainer.RemoveAllChildren();
|
||||
if (voteList != null)
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var voteDropdown in voteList)
|
||||
{
|
||||
var optionButton = new OptionButton();
|
||||
int j = 0;
|
||||
foreach (var (key, value) in voteDropdown)
|
||||
{
|
||||
optionButton.AddItem(Loc.GetString(value), j);
|
||||
j++;
|
||||
}
|
||||
VoteOptionsButtonContainer.AddChild(optionButton);
|
||||
optionButton.Visible = true;
|
||||
optionButton.OnItemSelected += ButtonSelected;
|
||||
optionButton.Margin = new Thickness(2, 1);
|
||||
if (AvailableVoteOptions[(StandardVoteType)obj.Id].FollowDropdownId != null && AvailableVoteOptions[(StandardVoteType)obj.Id].FollowDropdownId == i)
|
||||
{
|
||||
_followDropdown = optionButton;
|
||||
FollowButton.Visible = true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,4 +273,20 @@ namespace Content.Client.Voting.UI
|
||||
new VoteCallMenu().OpenCentered();
|
||||
}
|
||||
}
|
||||
|
||||
public record struct CreateVoteOption
|
||||
{
|
||||
public string Name;
|
||||
public List<Dictionary<string, string>> Dropdowns;
|
||||
public bool EnableVoteWarning;
|
||||
public int? FollowDropdownId; // If set, this will enable the Follow button and use the dropdown matching the ID as input.
|
||||
|
||||
public CreateVoteOption(string name, List<Dictionary<string, string>> dropdowns, bool enableVoteWarning, int? followDropdownId)
|
||||
{
|
||||
Name = name;
|
||||
Dropdowns = dropdowns;
|
||||
EnableVoteWarning = enableVoteWarning;
|
||||
FollowDropdownId = followDropdownId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<Control xmlns="https://spacestation14.io" MinWidth="300" MaxWidth="500">
|
||||
<Control xmlns="https://spacestation14.io" MinWidth="300" MaxWidth="500">
|
||||
<PanelContainer StyleClasses="AngleRect" />
|
||||
<BoxContainer Margin="4" Orientation="Vertical">
|
||||
<Label Name="VoteCaller" />
|
||||
<RichTextLabel Name="VoteTitle" />
|
||||
|
||||
<GridContainer Columns="3" Name="VoteOptionsContainer" />
|
||||
<Button Margin="4 4" Name="FollowVoteTarget" Text="{Loc 'ui-vote-follow-button-popup'}" Visible="False"></Button>
|
||||
|
||||
<GridContainer Columns="3" Name="VoteOptionsContainer"/>
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<ProgressBar Margin="4" HorizontalExpand="True" Name="TimeLeftBar" MinValue="0" MaxValue="1" />
|
||||
<Label Name="TimeLeftText" />
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Ghost;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -17,9 +15,11 @@ namespace Content.Client.Voting.UI
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IVoteManager _voteManager = default!;
|
||||
[Dependency] private readonly IEntityNetworkManager _net = default!;
|
||||
|
||||
private readonly VoteManager.ActiveVote _vote;
|
||||
private readonly Button[] _voteButtons;
|
||||
private readonly NetEntity? _targetEntity;
|
||||
|
||||
public VotePopup(VoteManager.ActiveVote vote)
|
||||
{
|
||||
@@ -29,6 +29,13 @@ namespace Content.Client.Voting.UI
|
||||
|
||||
Stylesheet = IoCManager.Resolve<IStylesheetManager>().SheetSpace;
|
||||
|
||||
if (_vote.TargetEntity != null && _vote.TargetEntity != 0)
|
||||
{
|
||||
_targetEntity = new NetEntity(_vote.TargetEntity.Value);
|
||||
FollowVoteTarget.Visible = true;
|
||||
FollowVoteTarget.OnPressed += _ => AttemptFollowVoteEntity();
|
||||
}
|
||||
|
||||
Modulate = Color.White.WithAlpha(0.75f);
|
||||
_voteButtons = new Button[vote.Entries.Length];
|
||||
var group = new ButtonGroup();
|
||||
@@ -55,13 +62,29 @@ namespace Content.Client.Voting.UI
|
||||
for (var i = 0; i < _voteButtons.Length; i++)
|
||||
{
|
||||
var entry = _vote.Entries[i];
|
||||
_voteButtons[i].Text = Loc.GetString("ui-vote-button", ("text", entry.Text), ("votes", entry.Votes));
|
||||
if (_vote.DisplayVotes)
|
||||
{
|
||||
_voteButtons[i].Text = Loc.GetString("ui-vote-button", ("text", entry.Text), ("votes", entry.Votes));
|
||||
}
|
||||
else
|
||||
{
|
||||
_voteButtons[i].Text = Loc.GetString("ui-vote-button-no-votes", ("text", entry.Text));
|
||||
}
|
||||
|
||||
if (_vote.OurVote == i)
|
||||
_voteButtons[i].Pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void AttemptFollowVoteEntity()
|
||||
{
|
||||
if (_targetEntity != null)
|
||||
{
|
||||
var msg = new GhostWarpToTargetRequestEvent(_targetEntity.Value);
|
||||
_net.SendSystemNetworkMessage(msg);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
// Logger.Debug($"{_gameTiming.ServerTime}, {_vote.StartTime}, {_vote.EndTime}");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Voting;
|
||||
@@ -184,6 +184,8 @@ namespace Content.Client.Voting
|
||||
existingVote.Title = message.VoteTitle;
|
||||
existingVote.StartTime = _gameTiming.RealServerToLocal(message.StartTime);
|
||||
existingVote.EndTime = _gameTiming.RealServerToLocal(message.EndTime);
|
||||
existingVote.DisplayVotes = message.DisplayVotes;
|
||||
existingVote.TargetEntity = message.TargetEntity;
|
||||
|
||||
// Logger.Debug($"{existingVote.StartTime}, {existingVote.EndTime}, {_gameTiming.RealTime}");
|
||||
|
||||
@@ -245,7 +247,8 @@ namespace Content.Client.Voting
|
||||
public string Initiator = "";
|
||||
public int? OurVote;
|
||||
public int Id;
|
||||
|
||||
public bool DisplayVotes;
|
||||
public int? TargetEntity; // NetEntity
|
||||
public ActiveVote(int voteId)
|
||||
{
|
||||
Id = voteId;
|
||||
|
||||
34
Content.Client/Voting/VotingSystem.cs
Normal file
34
Content.Client/Voting/VotingSystem.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using Content.Client.Ghost;
|
||||
using Content.Shared.Voting;
|
||||
|
||||
namespace Content.Client.Voting;
|
||||
|
||||
public sealed class VotingSystem : EntitySystem
|
||||
{
|
||||
|
||||
public event Action<VotePlayerListResponseEvent>? VotePlayerListResponse; //Provides a list of players elligble for vote actions
|
||||
|
||||
[Dependency] private readonly GhostSystem _ghostSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<VotePlayerListResponseEvent>(OnVotePlayerListResponseEvent);
|
||||
}
|
||||
|
||||
private void OnVotePlayerListResponseEvent(VotePlayerListResponseEvent msg)
|
||||
{
|
||||
if (!_ghostSystem.IsGhost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VotePlayerListResponse?.Invoke(msg);
|
||||
}
|
||||
|
||||
public void RequestVotePlayerList()
|
||||
{
|
||||
RaiseNetworkEvent(new VotePlayerListRequestEvent());
|
||||
}
|
||||
}
|
||||
@@ -47,10 +47,11 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
if (!Timing.IsFirstTimePredicted || weatherProto.Sound == null)
|
||||
return;
|
||||
|
||||
weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true).Value.Entity;
|
||||
weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true)?.Entity;
|
||||
|
||||
if (!TryComp(weather.Stream, out AudioComponent? comp))
|
||||
return;
|
||||
|
||||
var stream = weather.Stream.Value;
|
||||
var comp = Comp<AudioComponent>(stream);
|
||||
var occlusion = 0f;
|
||||
|
||||
// Work out tiles nearby to determine volume.
|
||||
@@ -115,7 +116,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
|
||||
var alpha = GetPercent(weather, uid);
|
||||
alpha *= SharedAudioSystem.VolumeToGain(weatherProto.Sound.Params.Volume);
|
||||
_audio.SetGain(stream, alpha, comp);
|
||||
_audio.SetGain(weather.Stream, alpha, comp);
|
||||
comp.Occlusion = occlusion;
|
||||
}
|
||||
|
||||
|
||||
@@ -584,17 +584,10 @@ namespace Content.Client.Wires.UI
|
||||
|
||||
private sealed class HelpPopup : Popup
|
||||
{
|
||||
private const string Text = "Click on the gold contacts with a multitool in hand to pulse their wire.\n" +
|
||||
"Click on the wires with a pair of wirecutters in hand to cut/mend them.\n\n" +
|
||||
"The lights at the top show the state of the machine, " +
|
||||
"messing with wires will probably do stuff to them.\n" +
|
||||
"Wire layouts are different each round, " +
|
||||
"but consistent between machines of the same type.";
|
||||
|
||||
public HelpPopup()
|
||||
{
|
||||
var label = new RichTextLabel();
|
||||
label.SetMessage(Text);
|
||||
label.SetMessage(Loc.GetString("wires-menu-help-popup"));
|
||||
AddChild(new PanelContainer
|
||||
{
|
||||
StyleClasses = {ExamineSystem.StyleClassEntityTooltip},
|
||||
|
||||
35
Content.Client/_CP14/MagicRituals/CP14ClientRitualSystem.cs
Normal file
35
Content.Client/_CP14/MagicRituals/CP14ClientRitualSystem.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using Content.Shared._CP14.MagicRitual;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Server._CP14.MagicRituals;
|
||||
|
||||
public partial class CP14ClientRitualSystem : CP14SharedRitualSystem
|
||||
{
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<CP14MagicRitualComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(Entity<CP14MagicRitualComponent> ent, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (!args.Sprite.LayerMapTryGet(ent.Comp.RitualLayerMap, out var ritualLayer))
|
||||
return;
|
||||
|
||||
if (_appearance.TryGetData<Color>(ent, RitualVisuals.Color, out var ritualColor, args.Component))
|
||||
{
|
||||
args.Sprite.LayerSetColor(ritualLayer, ritualColor);
|
||||
}
|
||||
|
||||
if (_appearance.TryGetData<bool>(ent, RitualVisuals.Enabled, out var enabled, args.Component))
|
||||
{
|
||||
args.Sprite.LayerSetVisible(ritualLayer, enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Content.Client/_CP14/Options/CP14OptionsMenuMainTab.xaml
Normal file
35
Content.Client/_CP14/Options/CP14OptionsMenuMainTab.xaml
Normal file
@@ -0,0 +1,35 @@
|
||||
<options:CP14OptionsMenuMainTab
|
||||
xmlns="https://spacestation14.io"
|
||||
xmlns:ui="clr-namespace:Content.Client.Options.UI"
|
||||
xmlns:options="clr-namespace:Content.Client._CP14.Options">
|
||||
|
||||
<!--
|
||||
Place for all custom settings of CP14
|
||||
-->
|
||||
<BoxContainer Orientation="Vertical">
|
||||
|
||||
<!-- Main options container -->
|
||||
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
|
||||
<BoxContainer Orientation="Vertical" Margin="8 8 8 8" VerticalExpand="True">
|
||||
|
||||
<!-- Graphics group -->
|
||||
<Label Text="{Loc 'cp14-ui-options-main-graphics-label'}"
|
||||
StyleClasses="LabelKeyText"/>
|
||||
|
||||
<!-- CP14 Wave shader settings -->
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Text="{Loc 'cp14-ui-options-main-graphics-wave-shader'}" Margin="0 0 4 0"/>
|
||||
<CheckBox Name="WaveShaderEnabled"/>
|
||||
</BoxContainer>
|
||||
|
||||
</BoxContainer>
|
||||
</ScrollContainer>
|
||||
|
||||
<!--
|
||||
Utilitarian element to simplify configuration work,
|
||||
ust don't touch that, okay?
|
||||
-->
|
||||
<ui:OptionsTabControlRow Name="Control" />
|
||||
|
||||
</BoxContainer>
|
||||
</options:CP14OptionsMenuMainTab>
|
||||
19
Content.Client/_CP14/Options/CP14OptionsMenuMainTab.xaml.cs
Normal file
19
Content.Client/_CP14/Options/CP14OptionsMenuMainTab.xaml.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Content.Shared._CP14.Configuration;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Content.Client._CP14.Options;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CP14OptionsMenuMainTab : Control
|
||||
{
|
||||
public CP14OptionsMenuMainTab()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
|
||||
Control.AddOptionCheckBox(CP14ConfigVars.WaveShaderEnabled, WaveShaderEnabled);
|
||||
|
||||
Control.Initialize();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<GridContainer Columns="6">
|
||||
|
||||
<TextureRect Name="GoldView"
|
||||
MinSize="10 10"
|
||||
Stretch="KeepAspectCentered"/>
|
||||
<Label Name="GoldText"
|
||||
Margin="0,0,5,0"/>
|
||||
|
||||
<TextureRect Name="SilverView"
|
||||
MinSize="10 10"
|
||||
Stretch="KeepAspectCentered"/>
|
||||
<Label Name="SilverText"
|
||||
Margin="0,0,5,0"/>
|
||||
|
||||
<TextureRect Name="CopperView"
|
||||
MinSize="10 10"
|
||||
Stretch="KeepAspectCentered"/>
|
||||
<Label Name="CopperText"
|
||||
Margin="0,0,5,0"/>
|
||||
</GridContainer>
|
||||
</Control>
|
||||
@@ -0,0 +1,46 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client._CP14.TravelingStoreShip;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CP14PriceControl : Control
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
private readonly SpriteSystem _sprite;
|
||||
|
||||
public CP14PriceControl(int price)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_sprite = _entity.System<SpriteSystem>();
|
||||
|
||||
var rsiPath = new ResPath("_CP14/Interface/Misc/coins.rsi");
|
||||
|
||||
var total = price;
|
||||
|
||||
var gp = total / 100;
|
||||
total %= 100;
|
||||
|
||||
var sp = total / 10;
|
||||
total %= 10;
|
||||
|
||||
var cp = total;
|
||||
|
||||
CopperView.Texture = _sprite.Frame0(new SpriteSpecifier.Rsi(rsiPath, "c"));
|
||||
CopperText.Text = cp.ToString();
|
||||
|
||||
SilverView.Texture = _sprite.Frame0(new SpriteSpecifier.Rsi(rsiPath, "s"));
|
||||
SilverText.Text = sp.ToString();
|
||||
|
||||
GoldView.Texture = _sprite.Frame0(new SpriteSpecifier.Rsi(rsiPath, "g"));
|
||||
GoldText.Text = gp.ToString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Content.Shared._CP14.TravelingStoreShip;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client._CP14.TravelingStoreShip;
|
||||
|
||||
public sealed class CP14StoreBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private CP14StoreWindow? _window;
|
||||
|
||||
public CP14StoreBoundUserInterface(EntityUid owner, [NotNull] Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = this.CreateWindow<CP14StoreWindow>();
|
||||
}
|
||||
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case CP14StoreUiState storeState:
|
||||
_window?.UpdateUI(storeState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Control xmlns="https://spacestation14.io">
|
||||
<Button Name="ProductButton" Access="Public">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<TextureRect Name="View"
|
||||
MinSize="48 48"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Stretch="KeepAspectCentered"/>
|
||||
<RichTextLabel Name="ProductName" VerticalAlignment="Center" Access="Public"/>
|
||||
<BoxContainer Name="PriceHolder" VerticalAlignment="Center" HorizontalExpand="True" HorizontalAlignment="Right"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</Button>
|
||||
</Control>
|
||||
@@ -0,0 +1,44 @@
|
||||
using Content.Shared._CP14.TravelingStoreShip;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Client._CP14.TravelingStoreShip;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CP14StoreProductControl : Control
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entity = default!;
|
||||
|
||||
private readonly SpriteSystem _sprite;
|
||||
|
||||
public CP14StoreProductControl(CP14StoreUiProductEntry entry)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_sprite = _entity.System<SpriteSystem>();
|
||||
|
||||
UpdateName(entry.Name);
|
||||
UpdateView(entry.Icon);
|
||||
UpdatePrice(entry.Price);
|
||||
}
|
||||
|
||||
private void UpdatePrice(int price)
|
||||
{
|
||||
PriceHolder.RemoveAllChildren();
|
||||
PriceHolder.AddChild(new CP14PriceControl(price));
|
||||
}
|
||||
|
||||
private void UpdateName(string name)
|
||||
{
|
||||
ProductName.Text = $"[bold]{name}[/bold]";
|
||||
}
|
||||
|
||||
private void UpdateView(SpriteSpecifier spriteSpecifier)
|
||||
{
|
||||
View.Texture = _sprite.Frame0(spriteSpecifier);
|
||||
}
|
||||
}
|
||||
31
Content.Client/_CP14/TravelingStoreShip/CP14StoreWindow.xaml
Normal file
31
Content.Client/_CP14/TravelingStoreShip/CP14StoreWindow.xaml
Normal file
@@ -0,0 +1,31 @@
|
||||
<DefaultWindow xmlns="https://spacestation14.io"
|
||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||
Title="{Loc 'cp14-store-ui-title'}"
|
||||
MinSize="800 600"
|
||||
SetSize="800 600">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<BoxContainer HorizontalExpand="True" VerticalExpand="True" Orientation="Horizontal">
|
||||
<!-- Product list (left side UI) -->
|
||||
<TabContainer SizeFlagsStretchRatio="0.5" Name="Tabs" HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
|
||||
<BoxContainer Name="BuyProductsContainer" Orientation="Vertical" HorizontalExpand="True"/>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HorizontalExpand="True" VerticalExpand="True" MinSize="0 200">
|
||||
<BoxContainer Name="SellProductsContainer" Orientation="Vertical" HorizontalExpand="True"/>
|
||||
</ScrollContainer>
|
||||
</TabContainer>
|
||||
<!-- Station trading data (right side UI) -->
|
||||
<BoxContainer SizeFlagsStretchRatio="0.5" Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True" Margin="0 0 10 0">
|
||||
<controls:StripeBack>
|
||||
<PanelContainer>
|
||||
<Label Text="{Loc 'cp14-store-ui-order'}" Align="Center" Margin="0 5 0 3"/>
|
||||
</PanelContainer>
|
||||
</controls:StripeBack>
|
||||
<Label Name="TravelTimeLabel" Text="00:00" HorizontalAlignment="Center" HorizontalExpand="True" Margin="0 15 0 0"/>
|
||||
<controls:HLine Color="#404040" Thickness="2" Margin="0 5"/>
|
||||
<RichTextLabel Name="SelectedName" HorizontalExpand="True" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="5"/>
|
||||
<RichTextLabel Name="SelectedDesc" HorizontalExpand="True" VerticalExpand="True" VerticalAlignment="Top" Margin="5"/>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</DefaultWindow>
|
||||
@@ -0,0 +1,79 @@
|
||||
using Content.Shared._CP14.TravelingStoreShip;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client._CP14.TravelingStoreShip;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CP14StoreWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private TimeSpan? _nextTravelTime;
|
||||
private bool _onStation;
|
||||
|
||||
public CP14StoreWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Tabs.SetTabTitle(0, Loc.GetString("cp14-store-ui-tab-buy"));
|
||||
Tabs.SetTabTitle(1, Loc.GetString("cp14-store-ui-tab-sell"));
|
||||
}
|
||||
|
||||
public void UpdateUI(CP14StoreUiState state)
|
||||
{
|
||||
UpdateProducts(state);
|
||||
|
||||
_nextTravelTime = state.NextTravelTime;
|
||||
_onStation = state.OnStation;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
//Updating time
|
||||
if (_nextTravelTime is not null)
|
||||
{
|
||||
var time = _nextTravelTime.Value - _timing.CurTime;
|
||||
|
||||
TravelTimeLabel.Text =
|
||||
$"{Loc.GetString(_onStation ? "cp14-store-ui-next-travel-out" : "cp14-store-ui-next-travel-in")} {Math.Max(time.Minutes, 0):00}:{Math.Max(time.Seconds, 0):00}";
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateProducts(CP14StoreUiState state)
|
||||
{
|
||||
BuyProductsContainer.RemoveAllChildren();
|
||||
SellProductsContainer.RemoveAllChildren();
|
||||
|
||||
foreach (var product in state.ProductsBuy)
|
||||
{
|
||||
var control = new CP14StoreProductControl(product);
|
||||
control.ProductButton.OnPressed += _ =>
|
||||
{
|
||||
SelectProduct(product);
|
||||
};
|
||||
BuyProductsContainer.AddChild(control);
|
||||
}
|
||||
|
||||
foreach (var product in state.ProductsSell)
|
||||
{
|
||||
var control = new CP14StoreProductControl(product);
|
||||
control.ProductButton.OnPressed += _ =>
|
||||
{
|
||||
SelectProduct(product);
|
||||
};
|
||||
SellProductsContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectProduct(CP14StoreUiProductEntry? entry)
|
||||
{
|
||||
SelectedName.Text = entry is null ? string.Empty : $"[bold]{entry.Value.Name}[/bold]";
|
||||
SelectedDesc.Text = entry is null ? string.Empty : entry.Value.Desc;
|
||||
}
|
||||
}
|
||||
@@ -35,9 +35,9 @@ public sealed partial class TestPair
|
||||
mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0);
|
||||
var plating = tileDefinitionManager[tile];
|
||||
var platingTile = new Tile(plating.TileId);
|
||||
mapData.Grid.Comp.SetTile(mapData.GridCoords, platingTile);
|
||||
Server.System<SharedMapSystem>().SetTile(mapData.Grid.Owner, mapData.Grid.Comp, mapData.GridCoords, platingTile);
|
||||
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
|
||||
mapData.Tile = mapData.Grid.Comp.GetAllTiles().First();
|
||||
mapData.Tile = Server.System<SharedMapSystem>().GetAllTiles(mapData.Grid.Owner, mapData.Grid.Comp).First();
|
||||
});
|
||||
|
||||
TestMap = mapData;
|
||||
|
||||
@@ -36,7 +36,9 @@ public static partial class PoolManager
|
||||
(CCVars.ConfigPresetDevelopment.Name, "false"),
|
||||
(CCVars.AdminLogsEnabled.Name, "false"),
|
||||
(CCVars.AutosaveEnabled.Name, "false"),
|
||||
(CVars.NetBufferSize.Name, "0")
|
||||
(CVars.NetBufferSize.Name, "0"),
|
||||
(CCVars.InteractionRateLimitCount.Name, "9999999"),
|
||||
(CCVars.InteractionRateLimitPeriod.Name, "0.1"),
|
||||
};
|
||||
|
||||
public static async Task SetupCVars(RobustIntegrationTest.IntegrationInstance instance, PoolSettings settings)
|
||||
|
||||
108
Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs
Normal file
108
Content.IntegrationTests/Tests/Buckle/BuckleTest.Interact.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using Content.Shared.Buckle;
|
||||
using Content.Shared.Buckle.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Buckle;
|
||||
|
||||
public sealed partial class BuckleTest
|
||||
{
|
||||
[Test]
|
||||
public async Task BuckleInteractUnbuckleOther()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.ResolveDependency<IServerEntityManager>();
|
||||
var buckleSystem = entMan.System<SharedBuckleSystem>();
|
||||
|
||||
EntityUid user = default;
|
||||
EntityUid victim = default;
|
||||
EntityUid chair = default;
|
||||
BuckleComponent buckle = null;
|
||||
StrapComponent strap = null;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
user = entMan.SpawnEntity(BuckleDummyId, MapCoordinates.Nullspace);
|
||||
victim = entMan.SpawnEntity(BuckleDummyId, MapCoordinates.Nullspace);
|
||||
chair = entMan.SpawnEntity(StrapDummyId, MapCoordinates.Nullspace);
|
||||
|
||||
Assert.That(entMan.TryGetComponent(victim, out buckle));
|
||||
Assert.That(entMan.TryGetComponent(chair, out strap));
|
||||
|
||||
#pragma warning disable RA0002
|
||||
buckle.Delay = TimeSpan.Zero;
|
||||
#pragma warning restore RA0002
|
||||
|
||||
// Buckle victim to chair
|
||||
Assert.That(buckleSystem.TryBuckle(victim, user, chair, buckle));
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(buckle.BuckledTo, Is.EqualTo(chair), "Victim did not get buckled to the chair.");
|
||||
Assert.That(buckle.Buckled, "Victim is not buckled.");
|
||||
Assert.That(strap.BuckledEntities, Does.Contain(victim), "Chair does not have victim buckled to it.");
|
||||
});
|
||||
|
||||
// InteractHand with chair to unbuckle victim
|
||||
entMan.EventBus.RaiseLocalEvent(chair, new InteractHandEvent(user, chair));
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(buckle.BuckledTo, Is.Null);
|
||||
Assert.That(buckle.Buckled, Is.False);
|
||||
Assert.That(strap.BuckledEntities, Does.Not.Contain(victim));
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task BuckleInteractBuckleUnbuckleSelf()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var entMan = server.ResolveDependency<IServerEntityManager>();
|
||||
|
||||
EntityUid user = default;
|
||||
EntityUid chair = default;
|
||||
BuckleComponent buckle = null;
|
||||
StrapComponent strap = null;
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
user = entMan.SpawnEntity(BuckleDummyId, MapCoordinates.Nullspace);
|
||||
chair = entMan.SpawnEntity(StrapDummyId, MapCoordinates.Nullspace);
|
||||
|
||||
Assert.That(entMan.TryGetComponent(user, out buckle));
|
||||
Assert.That(entMan.TryGetComponent(chair, out strap));
|
||||
|
||||
#pragma warning disable RA0002
|
||||
buckle.Delay = TimeSpan.Zero;
|
||||
#pragma warning restore RA0002
|
||||
|
||||
// Buckle user to chair
|
||||
entMan.EventBus.RaiseLocalEvent(chair, new InteractHandEvent(user, chair));
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(buckle.BuckledTo, Is.EqualTo(chair), "Victim did not get buckled to the chair.");
|
||||
Assert.That(buckle.Buckled, "Victim is not buckled.");
|
||||
Assert.That(strap.BuckledEntities, Does.Contain(user), "Chair does not have victim buckled to it.");
|
||||
});
|
||||
|
||||
// InteractHand with chair to unbuckle
|
||||
entMan.EventBus.RaiseLocalEvent(chair, new InteractHandEvent(user, chair));
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(buckle.BuckledTo, Is.Null);
|
||||
Assert.That(buckle.Buckled, Is.False);
|
||||
Assert.That(strap.BuckledEntities, Does.Not.Contain(user));
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace Content.IntegrationTests.Tests.Buckle
|
||||
[TestFixture]
|
||||
[TestOf(typeof(BuckleComponent))]
|
||||
[TestOf(typeof(StrapComponent))]
|
||||
public sealed class BuckleTest
|
||||
public sealed partial class BuckleTest
|
||||
{
|
||||
private const string BuckleDummyId = "BuckleDummy";
|
||||
private const string StrapDummyId = "StrapDummy";
|
||||
|
||||
@@ -39,7 +39,7 @@ public sealed class ComputerConstruction : InteractionTest
|
||||
await StartDeconstruction(ComputerId);
|
||||
|
||||
// Initial interaction turns id computer into generic computer
|
||||
await InteractUsing(Screw);
|
||||
await InteractUsing(Pry);
|
||||
AssertPrototype(ComputerFrame);
|
||||
|
||||
// Perform deconstruction steps
|
||||
@@ -69,7 +69,7 @@ public sealed class ComputerConstruction : InteractionTest
|
||||
await SpawnTarget(ComputerId);
|
||||
|
||||
// Initial interaction turns id computer into generic computer
|
||||
await InteractUsing(Screw);
|
||||
await InteractUsing(Pry);
|
||||
AssertPrototype(ComputerFrame);
|
||||
|
||||
// Perform partial deconstruction steps
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Body.Components;
|
||||
using Content.Server.GameTicking;
|
||||
@@ -120,8 +121,8 @@ public sealed class NukeOpsTest
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind));
|
||||
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
|
||||
var roles = roleSys.MindGetAllRoles(mind);
|
||||
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent);
|
||||
var roles = roleSys.MindGetAllRoleInfo(mind);
|
||||
var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander");
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
|
||||
// The second dummy player should be a medic
|
||||
@@ -131,8 +132,8 @@ public sealed class NukeOpsTest
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(dummyMind));
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
|
||||
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
|
||||
roles = roleSys.MindGetAllRoles(dummyMind);
|
||||
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent);
|
||||
roles = roleSys.MindGetAllRoleInfo(dummyMind);
|
||||
cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic");
|
||||
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
|
||||
|
||||
// The other two players should have just spawned in as normal.
|
||||
@@ -141,13 +142,14 @@ public sealed class NukeOpsTest
|
||||
void CheckDummy(int i)
|
||||
{
|
||||
var ent = dummyEnts[i];
|
||||
var mind = mindSys.GetMind(ent)!.Value;
|
||||
var mindCrew = mindSys.GetMind(ent)!.Value;
|
||||
Assert.That(entMan.HasComponent<NukeOperativeComponent>(ent), Is.False);
|
||||
Assert.That(roleSys.MindIsAntagonist(mind), Is.False);
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mind), Is.False);
|
||||
Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False);
|
||||
Assert.That(roleSys.MindHasRole<NukeopsRoleComponent>(mindCrew), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
|
||||
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
|
||||
Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False);
|
||||
var nukeroles = new List<string>() { "Nukeops", "NukeopsMedic", "NukeopsCommander" };
|
||||
Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False);
|
||||
}
|
||||
|
||||
// The game rule exists, and all the stations/shuttles/maps are properly initialized
|
||||
@@ -238,7 +240,8 @@ public sealed class NukeOpsTest
|
||||
for (var i = 0; i < nukies.Length - 1; i++)
|
||||
{
|
||||
entMan.DeleteEntity(nukies[i]);
|
||||
Assert.That(roundEndSys.IsRoundEndRequested, Is.False,
|
||||
Assert.That(roundEndSys.IsRoundEndRequested,
|
||||
Is.False,
|
||||
$"The round ended, but {nukies.Length - i - 1} nukies are still alive!");
|
||||
}
|
||||
// Delete the last nukie and make sure the round ends.
|
||||
|
||||
@@ -2,7 +2,6 @@ using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Body.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Internals;
|
||||
|
||||
@@ -25,10 +24,7 @@ public sealed class AutoInternalsTests
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
var profile = new HumanoidCharacterProfile();
|
||||
var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, new JobComponent()
|
||||
{
|
||||
Prototype = "TestInternalsDummy"
|
||||
}, profile, station: null);
|
||||
var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, "TestInternalsDummy", profile, station: null);
|
||||
|
||||
Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!");
|
||||
Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!");
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#nullable enable
|
||||
using System.Linq;
|
||||
using Content.Server.Ghost;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Ghost.Roles.Components;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Roles;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
@@ -18,7 +16,6 @@ using Robust.Server.Console;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -287,27 +284,27 @@ public sealed partial class MindTests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
|
||||
});
|
||||
|
||||
var traitorRole = new TraitorRoleComponent();
|
||||
var traitorRole = "MindRoleTraitor";
|
||||
|
||||
roleSystem.MindAddRole(mindId, traitorRole);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
|
||||
});
|
||||
|
||||
var jobRole = new JobComponent();
|
||||
var jobRole = "";
|
||||
|
||||
roleSystem.MindAddRole(mindId, jobRole);
|
||||
roleSystem.MindAddJobRole(mindId, jobPrototype:jobRole);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId));
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
|
||||
});
|
||||
|
||||
roleSystem.MindRemoveRole<TraitorRoleComponent>(mindId);
|
||||
@@ -315,15 +312,15 @@ public sealed partial class MindTests
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId));
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId));
|
||||
});
|
||||
|
||||
roleSystem.MindRemoveRole<JobComponent>(mindId);
|
||||
roleSystem.MindRemoveRole<JobRoleComponent>(mindId);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(roleSystem.MindHasRole<TraitorRoleComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobComponent>(mindId), Is.False);
|
||||
Assert.That(roleSystem.MindHasRole<JobRoleComponent>(mindId), Is.False);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using Content.Server.Station.Systems;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -68,10 +67,7 @@ public sealed class LoadoutTests
|
||||
|
||||
profile.SetLoadout(new RoleLoadout("LoadoutTester"));
|
||||
|
||||
var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
|
||||
{
|
||||
Prototype = "LoadoutTester"
|
||||
}, profile, station: null);
|
||||
var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: "LoadoutTester", profile, station: null);
|
||||
|
||||
var slotQuery = inventorySystem.GetSlotEnumerator(tester);
|
||||
var checkedCount = 0;
|
||||
|
||||
53
Content.IntegrationTests/Tests/_CP14/CP14RitualTest.cs
Normal file
53
Content.IntegrationTests/Tests/_CP14/CP14RitualTest.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using Content.Shared._CP14.MagicRitual;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests._CP14;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[TestFixture]
|
||||
public sealed class CP14RitualTest
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// States that all edges of the ritual phase have triggers.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task RitualHasAllTriggersTest()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var server = pair.Server;
|
||||
|
||||
var compFactory = server.ResolveDependency<IComponentFactory>();
|
||||
var protoManager = server.ResolveDependency<IPrototypeManager>();
|
||||
|
||||
await server.WaitAssertion(() =>
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
foreach (var proto in protoManager.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (!proto.TryGetComponent(out CP14MagicRitualPhaseComponent? phase, compFactory))
|
||||
continue;
|
||||
|
||||
if (phase.DeadEnd)
|
||||
{
|
||||
Assert.That(phase.Edges.Count == 0, $"{proto} is a ritual node, but has no paths to other nodes. Either add deadEnd = true, or add paths to other nodes.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.That(phase.Edges.Count > 0, $"{proto} is a deadEnd ritual node, but has {phase.Edges.Count} edges! Remove all edges, or make it a non dead-end node");
|
||||
}
|
||||
|
||||
foreach (var edge in phase.Edges)
|
||||
{
|
||||
Assert.That(edge.Triggers.Count > 0, $"{{proto}} is ritual node, but edge to {edge.Target} has no triggers and cannot be activated.");
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ namespace Content.Server.Access.Systems
|
||||
|
||||
private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args)
|
||||
{
|
||||
if (!TryComp<AccessComponent>(args.Target, out var targetAccess) || !HasComp<IdCardComponent>(args.Target) || args.Target == null)
|
||||
if (args.Target == null || !args.CanReach || !TryComp<AccessComponent>(args.Target, out var targetAccess) || !HasComp<IdCardComponent>(args.Target))
|
||||
return;
|
||||
|
||||
if (!TryComp<AccessComponent>(uid, out var access) || !HasComp<IdCardComponent>(uid))
|
||||
@@ -67,7 +67,7 @@ namespace Content.Server.Access.Systems
|
||||
if (!TryComp<IdCardComponent>(uid, out var idCard))
|
||||
return;
|
||||
|
||||
var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.JobTitle ?? "", idCard.JobIcon);
|
||||
var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.LocalizedJobTitle ?? "", idCard.JobIcon);
|
||||
_uiSystem.SetUiState(uid, AgentIDCardUiKey.Key, state);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ public sealed class IdCardConsoleSystem : SharedIdCardConsoleSystem
|
||||
PrivilegedIdIsAuthorized(uid, component),
|
||||
true,
|
||||
targetIdComponent.FullName,
|
||||
targetIdComponent.JobTitle,
|
||||
targetIdComponent.LocalizedJobTitle,
|
||||
targetAccessComponent.Tags.ToList(),
|
||||
possibleAccess,
|
||||
jobProto,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Content.Server.Administration.BanList;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.Database;
|
||||
using Content.Shared.Administration;
|
||||
using Robust.Server.Player;
|
||||
@@ -10,6 +12,12 @@ namespace Content.Server.Administration.Commands;
|
||||
[AdminCommand(AdminFlags.Ban)]
|
||||
public sealed class RoleBanListCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IServerDbManager _dbManager = default!;
|
||||
|
||||
[Dependency] private readonly EuiManager _eui = default!;
|
||||
|
||||
[Dependency] private readonly IPlayerLocator _locator = default!;
|
||||
|
||||
public string Command => "rolebanlist";
|
||||
public string Description => Loc.GetString("cmd-rolebanlist-desc");
|
||||
public string Help => Loc.GetString("cmd-rolebanlist-help");
|
||||
@@ -29,66 +37,37 @@ public sealed class RoleBanListCommand : IConsoleCommand
|
||||
return;
|
||||
}
|
||||
|
||||
var dbMan = IoCManager.Resolve<IServerDbManager>();
|
||||
var data = await _locator.LookupIdByNameOrIdAsync(args[0]);
|
||||
|
||||
var target = args[0];
|
||||
|
||||
var locator = IoCManager.Resolve<IPlayerLocator>();
|
||||
var located = await locator.LookupIdByNameOrIdAsync(target);
|
||||
if (located == null)
|
||||
if (data == null)
|
||||
{
|
||||
shell.WriteError("Unable to find a player with that name or id.");
|
||||
return;
|
||||
}
|
||||
|
||||
var targetUid = located.UserId;
|
||||
var targetHWid = located.LastHWId;
|
||||
var targetAddress = located.LastAddress;
|
||||
|
||||
var bans = await dbMan.GetServerRoleBansAsync(targetAddress, targetUid, targetHWid, includeUnbanned);
|
||||
|
||||
if (bans.Count == 0)
|
||||
if (shell.Player is not { } player)
|
||||
{
|
||||
shell.WriteLine("That user has no bans in their record.");
|
||||
|
||||
var bans = await _dbManager.GetServerRoleBansAsync(data.LastAddress, data.UserId, data.LastHWId, includeUnbanned);
|
||||
|
||||
if (bans.Count == 0)
|
||||
{
|
||||
shell.WriteLine("That user has no bans in their record.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var ban in bans)
|
||||
{
|
||||
var msg = $"ID: {ban.Id}: Role: {ban.Role} Reason: {ban.Reason}";
|
||||
shell.WriteLine(msg);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var bansString = new StringBuilder("Bans in record:\n");
|
||||
var ui = new BanListEui();
|
||||
_eui.OpenEui(ui, player);
|
||||
await ui.ChangeBanListPlayer(data.UserId);
|
||||
|
||||
var first = true;
|
||||
foreach (var ban in bans)
|
||||
{
|
||||
if (!first)
|
||||
bansString.Append("\n\n");
|
||||
else
|
||||
first = false;
|
||||
|
||||
bansString
|
||||
.Append("Ban ID: ")
|
||||
.Append(ban.Id)
|
||||
.Append('\n')
|
||||
.Append("Role: ")
|
||||
.Append(ban.Role)
|
||||
.Append('\n')
|
||||
.Append("Banned on ")
|
||||
.Append(ban.BanTime);
|
||||
|
||||
if (ban.ExpirationTime != null)
|
||||
{
|
||||
bansString
|
||||
.Append(" until ")
|
||||
.Append(ban.ExpirationTime.Value);
|
||||
}
|
||||
|
||||
bansString
|
||||
.Append('\n');
|
||||
|
||||
bansString
|
||||
.Append("Reason: ")
|
||||
.Append(ban.Reason);
|
||||
}
|
||||
|
||||
shell.WriteLine(bansString.ToString());
|
||||
}
|
||||
|
||||
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
|
||||
@@ -4,11 +4,15 @@ using Content.Server.Hands.Systems;
|
||||
using Content.Server.Preferences.Managers;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Clothing;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Preferences.Loadouts;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Station;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -82,9 +86,11 @@ namespace Content.Server.Administration.Commands
|
||||
return false;
|
||||
|
||||
HumanoidCharacterProfile? profile = null;
|
||||
ICommonSession? session = null;
|
||||
// Check if we are setting the outfit of a player to respect the preferences
|
||||
if (entityManager.TryGetComponent(target, out ActorComponent? actorComponent))
|
||||
{
|
||||
session = actorComponent.PlayerSession;
|
||||
var userId = actorComponent.PlayerSession.UserId;
|
||||
var preferencesManager = IoCManager.Resolve<IServerPreferencesManager>();
|
||||
var prefs = preferencesManager.GetPreferences(userId);
|
||||
@@ -128,6 +134,36 @@ namespace Content.Server.Administration.Commands
|
||||
}
|
||||
}
|
||||
|
||||
// See if this starting gear is associated with a job
|
||||
var jobs = prototypeManager.EnumeratePrototypes<JobPrototype>();
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
if (job.StartingGear != gear)
|
||||
continue;
|
||||
|
||||
var jobProtoId = LoadoutSystem.GetJobPrototype(job.ID);
|
||||
if (!prototypeManager.TryIndex<RoleLoadoutPrototype>(jobProtoId, out var jobProto))
|
||||
break;
|
||||
|
||||
// Don't require a player, so this works on Urists
|
||||
profile ??= entityManager.TryGetComponent<HumanoidAppearanceComponent>(target, out var comp)
|
||||
? HumanoidCharacterProfile.DefaultWithSpecies(comp.Species)
|
||||
: new HumanoidCharacterProfile();
|
||||
// Try to get the user's existing loadout for the role
|
||||
profile.Loadouts.TryGetValue(jobProtoId, out var roleLoadout);
|
||||
|
||||
if (roleLoadout == null)
|
||||
{
|
||||
// If they don't have a loadout for the role, make a default one
|
||||
roleLoadout = new RoleLoadout(jobProtoId);
|
||||
roleLoadout.SetDefault(profile, session, prototypeManager);
|
||||
}
|
||||
|
||||
// Equip the target with the job loadout
|
||||
var stationSpawning = entityManager.System<SharedStationSpawningSystem>();
|
||||
stationSpawning.EquipRoleLoadout(target, roleLoadout, jobProto);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,6 @@ public sealed partial class AdminVerbSystem
|
||||
[Dependency] private readonly StationJobsSystem _stationJobsSystem = default!;
|
||||
[Dependency] private readonly JointSystem _jointSystem = default!;
|
||||
[Dependency] private readonly BatterySystem _batterySystem = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
|
||||
[Dependency] private readonly GunSystem _gun = default!;
|
||||
|
||||
@@ -90,22 +89,22 @@ public sealed partial class AdminVerbSystem
|
||||
args.Verbs.Add(bolt);
|
||||
}
|
||||
|
||||
if (TryComp<AirlockComponent>(args.Target, out var airlock))
|
||||
if (TryComp<AirlockComponent>(args.Target, out var airlockComp))
|
||||
{
|
||||
Verb emergencyAccess = new()
|
||||
{
|
||||
Text = airlock.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
|
||||
Text = airlockComp.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
|
||||
Category = VerbCategory.Tricks,
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")),
|
||||
Act = () =>
|
||||
{
|
||||
_airlockSystem.ToggleEmergencyAccess(args.Target, airlock);
|
||||
_airlockSystem.SetEmergencyAccess((args.Target, airlockComp), !airlockComp.EmergencyAccess);
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString(airlock.EmergencyAccess
|
||||
Message = Loc.GetString(airlockComp.EmergencyAccess
|
||||
? "admin-trick-emergency-access-off-description"
|
||||
: "admin-trick-emergency-access-on-description"),
|
||||
Priority = (int) (airlock.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn),
|
||||
Priority = (int) (airlockComp.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn),
|
||||
};
|
||||
args.Verbs.Add(emergencyAccess);
|
||||
}
|
||||
@@ -327,7 +326,7 @@ public sealed partial class AdminVerbSystem
|
||||
Act = () =>
|
||||
{
|
||||
var (mapUid, gridUid) = _adminTestArenaSystem.AssertArenaLoaded(player);
|
||||
_xformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One));
|
||||
_transformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One));
|
||||
},
|
||||
Impact = LogImpact.Medium,
|
||||
Message = Loc.GetString("admin-trick-send-to-test-arena-description"),
|
||||
@@ -533,7 +532,7 @@ public sealed partial class AdminVerbSystem
|
||||
if (shuttle is null)
|
||||
return;
|
||||
|
||||
_xformSystem.SetCoordinates(args.User, new EntityCoordinates(shuttle.Value, Vector2.Zero));
|
||||
_transformSystem.SetCoordinates(args.User, new EntityCoordinates(shuttle.Value, Vector2.Zero));
|
||||
},
|
||||
Impact = LogImpact.Low,
|
||||
Message = Loc.GetString("admin-trick-locate-cargo-shuttle-description"),
|
||||
|
||||
@@ -35,10 +35,8 @@ using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using Content.Server.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws;
|
||||
using Content.Shared.Silicons.Laws.Components;
|
||||
using Robust.Server.Player;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using static Content.Shared.Configurable.ConfigurationComponent;
|
||||
|
||||
@@ -63,7 +61,6 @@ namespace Content.Server.Administration.Systems
|
||||
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
[Dependency] private readonly PrayerSystem _prayerSystem = default!;
|
||||
[Dependency] private readonly EuiManager _eui = default!;
|
||||
[Dependency] private readonly MindSystem _mindSystem = default!;
|
||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
|
||||
@@ -294,7 +291,7 @@ namespace Content.Server.Administration.Systems
|
||||
Act = () =>
|
||||
{
|
||||
var ui = new AdminLogsEui();
|
||||
_eui.OpenEui(ui, player);
|
||||
_euiManager.OpenEui(ui, player);
|
||||
ui.SetLogFilter(search:args.Target.Id.ToString());
|
||||
},
|
||||
Impact = LogImpact.Low
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Players.RateLimiting;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
@@ -104,12 +105,10 @@ namespace Content.Server.Administration.Systems
|
||||
|
||||
_rateLimit.Register(
|
||||
RateLimitKey,
|
||||
new RateLimitRegistration
|
||||
{
|
||||
CVarLimitPeriodLength = CCVars.AhelpRateLimitPeriod,
|
||||
CVarLimitCount = CCVars.AhelpRateLimitCount,
|
||||
PlayerLimitedAction = PlayerRateLimitedAction
|
||||
});
|
||||
new RateLimitRegistration(CCVars.AhelpRateLimitPeriod,
|
||||
CCVars.AhelpRateLimitCount,
|
||||
PlayerRateLimitedAction)
|
||||
);
|
||||
}
|
||||
|
||||
private void PlayerRateLimitedAction(ICommonSession obj)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
@@ -10,6 +11,7 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Power;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
@@ -25,6 +27,7 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -40,6 +43,34 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
SubscribeLocalEvent<AnomalyStabilityChangedEvent>(OnAnomalyStabilityChanged);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<AnomalySynchronizerComponent, TransformComponent>();
|
||||
while (query.MoveNext(out var uid, out var sync, out var xform))
|
||||
{
|
||||
if (sync.ConnectedAnomaly is null)
|
||||
continue;
|
||||
|
||||
if (_timing.CurTime < sync.NextCheckTime)
|
||||
continue;
|
||||
sync.NextCheckTime += sync.CheckFrequency;
|
||||
|
||||
if (Transform(sync.ConnectedAnomaly.Value).MapUid != Transform(uid).MapUid)
|
||||
{
|
||||
DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!xform.Coordinates.TryDistance(EntityManager, Transform(sync.ConnectedAnomaly.Value).Coordinates, out var distance))
|
||||
continue;
|
||||
|
||||
if (distance > sync.AttachRange)
|
||||
DisconnectFromAnomaly((uid, sync), sync.ConnectedAnomaly.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If powered, try to attach a nearby anomaly.
|
||||
/// </summary>
|
||||
@@ -73,10 +104,10 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
if (args.Powered)
|
||||
return;
|
||||
|
||||
if (!TryComp<AnomalyComponent>(ent.Comp.ConnectedAnomaly, out var anomaly))
|
||||
if (ent.Comp.ConnectedAnomaly is null)
|
||||
return;
|
||||
|
||||
DisconnectFromAnomaly(ent, anomaly);
|
||||
DisconnectFromAnomaly(ent, ent.Comp.ConnectedAnomaly.Value);
|
||||
}
|
||||
|
||||
private void OnExamined(Entity<AnomalySynchronizerComponent> ent, ref ExaminedEvent args)
|
||||
@@ -125,13 +156,16 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
|
||||
//TODO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer.
|
||||
//Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer.
|
||||
private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, AnomalyComponent anomaly)
|
||||
private void DisconnectFromAnomaly(Entity<AnomalySynchronizerComponent> ent, EntityUid other)
|
||||
{
|
||||
if (ent.Comp.ConnectedAnomaly == null)
|
||||
return;
|
||||
|
||||
if (ent.Comp.PulseOnDisconnect)
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
if (TryComp<AnomalyComponent>(other, out var anomaly))
|
||||
{
|
||||
if (ent.Comp.PulseOnDisconnect)
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
}
|
||||
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), ent, PopupType.Large);
|
||||
_audio.PlayPvs(ent.Comp.ConnectedSound, ent);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user