mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
Compare commits
215
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69b6463898 | ||
|
|
2dc3f61cd8 | ||
|
|
164b9be1f9 | ||
|
|
eb95b25eaa | ||
|
|
d3b13a6c83 | ||
|
|
2ab0163598 | ||
|
|
6cdeb3d9cf | ||
|
|
d7543bd24a | ||
|
|
f7f800c439 | ||
|
|
b6ed84325f | ||
|
|
61aee79dc2 | ||
|
|
55fe6026ea | ||
|
|
6dfa12a509 | ||
|
|
485fd14f05 | ||
|
|
031b37ffcb | ||
|
|
06365bdbf0 | ||
|
|
7f18c5f346 | ||
|
|
d407652eb0 | ||
|
|
9108e2c9b5 | ||
|
|
c7ef614f3d | ||
|
|
fa735f7669 | ||
|
|
6de6421636 | ||
|
|
3cb24c8b68 | ||
|
|
9fad412075 | ||
|
|
fbc3104836 | ||
|
|
e231d9d67c | ||
|
|
468d929c4b | ||
|
|
2f1aaff32c | ||
|
|
4f49ccde68 | ||
|
|
cb3b74e49e | ||
|
|
7d15a39a77 | ||
|
|
3ac816fee1 | ||
|
|
b909d9a2e5 | ||
|
|
7547752159 | ||
|
|
9b7819288e | ||
|
|
424a4569d8 | ||
|
|
12827c0e98 | ||
|
|
d1e61ba0a2 | ||
|
|
652e52dbca | ||
|
|
e7fe4865de | ||
|
|
84b600a39f | ||
|
|
31201c44ba | ||
|
|
4f9f93eb87 | ||
|
|
f8a4d823b9 | ||
|
|
c61d2c6a58 | ||
|
|
e25181e1bc | ||
|
|
7c052442a8 | ||
|
|
19b0de35dd | ||
|
|
1c66c1fe2e | ||
|
|
ef46675a2f | ||
|
|
c0b6929c9a | ||
|
|
c7aa50cf85 | ||
|
|
c69fb99600 | ||
|
|
3ca37d9c2b | ||
|
|
8ba993d1a9 | ||
|
|
edd1500bf3 | ||
|
|
2ec64360b7 | ||
|
|
e0540c6205 | ||
|
|
7953b0469a | ||
|
|
a3d3d23600 | ||
|
|
5ccfce1e7f | ||
|
|
879cbf43f5 | ||
|
|
9c84627db5 | ||
|
|
3134e91fa5 | ||
|
|
3ac30b7b7b | ||
|
|
4daea082bc | ||
|
|
6a37e1268e | ||
|
|
331a25c7d3 | ||
|
|
b5121e10e9 | ||
|
|
9584a7b0ae | ||
|
|
f9880c148b | ||
|
|
25ef15f162 | ||
|
|
997a6e31e6 | ||
|
|
5a342570a4 | ||
|
|
97bb68b2a6 | ||
|
|
ac9f8852b3 | ||
|
|
8c8f4b4f2c | ||
|
|
8eeb5f6523 | ||
|
|
45432aa3bb | ||
|
|
b9153f0f13 | ||
|
|
e3d3fe72f0 | ||
|
|
0a65236363 | ||
|
|
afe6021455 | ||
|
|
e5db02c736 | ||
|
|
cbc5f819c4 | ||
|
|
339b3fb32c | ||
|
|
e96faec07d | ||
|
|
a1bbf142a4 | ||
|
|
e47a0bf455 | ||
|
|
72f3edd378 | ||
|
|
030deb6f27 | ||
|
|
4397017c5a | ||
|
|
0f19340d41 | ||
|
|
3c28ee8d20 | ||
|
|
11cc04dd87 | ||
|
|
f87ed28744 | ||
|
|
62c76332f5 | ||
|
|
34c212e4fb | ||
|
|
353290e55b | ||
|
|
0cbe9c01fb | ||
|
|
d88938887b | ||
|
|
1656a91c5e | ||
|
|
c70cbcb9f5 | ||
|
|
3e22d89d1e | ||
|
|
a790954f06 | ||
|
|
0676186fe5 | ||
|
|
db6d1e3970 | ||
|
|
fd20b74b8f | ||
|
|
d016838687 | ||
|
|
160aa17052 | ||
|
|
2b6729fa64 | ||
|
|
3439340530 | ||
|
|
28c2223a8a | ||
|
|
1b47c246c9 | ||
|
|
8141512cc4 | ||
|
|
fd986fec6e | ||
|
|
dd2a6e1c28 | ||
|
|
6c7c9de253 | ||
|
|
2b96bb79f8 | ||
|
|
c1239ef7bf | ||
|
|
62b6401e48 | ||
|
|
f79a599c65 | ||
|
|
9acd3f2ecb | ||
|
|
47215d7876 | ||
|
|
fe53298d27 | ||
|
|
1aee2519df | ||
|
|
e8cd052c31 | ||
|
|
a27fa291e7 | ||
|
|
7e6f66a651 | ||
|
|
08c27979a1 | ||
|
|
b49a94100c | ||
|
|
cfa24eb20c | ||
|
|
1787683435 | ||
|
|
389fbf99e1 | ||
|
|
74c7bf0a05 | ||
|
|
6764276a47 | ||
|
|
03934d3fbd | ||
|
|
468150c817 | ||
|
|
310ac0809b | ||
|
|
7b71611dc9 | ||
|
|
f2153d66c3 | ||
|
|
b6833cbbcf | ||
|
|
4e2bd0c244 | ||
|
|
bf0abd7ffa | ||
|
|
c4d65da8ed | ||
|
|
6dac24f03a | ||
|
|
9550a7d53d | ||
|
|
d826a15ff2 | ||
|
|
23a9a75703 | ||
|
|
79a0000547 | ||
|
|
71badde1a0 | ||
|
|
5ab49cd46b | ||
|
|
0398c527a1 | ||
|
|
cdafd4c257 | ||
|
|
8dc06f010a | ||
|
|
fee62b449f | ||
|
|
c0d20b3d79 | ||
|
|
b2944fba52 | ||
|
|
2e39916957 | ||
|
|
874bbf4fd2 | ||
|
|
e1ff1ea820 | ||
|
|
79a4a15403 | ||
|
|
0bb2b6ee7b | ||
|
|
18d9325b60 | ||
|
|
f07648bc24 | ||
|
|
f5da329b98 | ||
|
|
ad6581f2d3 | ||
|
|
430fef2a10 | ||
|
|
6970cb9d28 | ||
|
|
ea2b8df2cc | ||
|
|
b87b113736 | ||
|
|
f087b2895e | ||
|
|
0a14310574 | ||
|
|
d9c2f26438 | ||
|
|
a4f11c63dc | ||
|
|
4ff05277fa | ||
|
|
a33e12e85e | ||
|
|
62e9ebc216 | ||
|
|
25b69a3f9e | ||
|
|
7ed7dbcbb1 | ||
|
|
fcdf1cbbf4 | ||
|
|
9f786a7c1c | ||
|
|
e2e5c40f83 | ||
|
|
d0c3cff669 | ||
|
|
ed1fa64a6e | ||
|
|
cb245477b8 | ||
|
|
0fdfef7175 | ||
|
|
646d538c4a | ||
|
|
6ebcf78d08 | ||
|
|
2b7e3c8058 | ||
|
|
4736a8db6f | ||
|
|
6057388bae | ||
|
|
50a651beea | ||
|
|
7e1308dd12 | ||
|
|
338ff271c7 | ||
|
|
fddd9079d3 | ||
|
|
bedc17ae73 | ||
|
|
ec80e99e26 | ||
|
|
4ceb567439 | ||
|
|
6a9984475c | ||
|
|
030eb95c63 | ||
|
|
00abed028d | ||
|
|
0f3d319a6d | ||
|
|
a9ddc102e7 | ||
|
|
a51896b08d | ||
|
|
356106342f | ||
|
|
a3b450ac07 | ||
|
|
4ddb5901b0 | ||
|
|
972d852bf4 | ||
|
|
ef0d317d12 | ||
|
|
003e08b583 | ||
|
|
74739709c4 | ||
|
|
61840fd5ab | ||
|
|
90f7aa7999 | ||
|
|
2260af7ce1 |
@@ -12,12 +12,21 @@ jobs:
|
|||||||
- name: Setup submodule
|
- name: Setup submodule
|
||||||
run: |
|
run: |
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
- name: Pull engine updates
|
- name: Pull engine updates
|
||||||
uses: space-wizards/submodule-dependency@v0.1.5
|
uses: space-wizards/submodule-dependency@v0.1.5
|
||||||
- name: Update Engine Submodules
|
- name: Update Engine Submodules
|
||||||
run: |
|
run: |
|
||||||
cd RobustToolbox/
|
cd RobustToolbox/
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
|
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ jobs:
|
|||||||
- name: Setup Submodule
|
- name: Setup Submodule
|
||||||
run: |
|
run: |
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
|
|
||||||
- name: Pull engine updates
|
- name: Pull engine updates
|
||||||
uses: space-wizards/submodule-dependency@v0.1.5
|
uses: space-wizards/submodule-dependency@v0.1.5
|
||||||
|
|
||||||
@@ -32,6 +36,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd RobustToolbox/
|
cd RobustToolbox/
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
|
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ jobs:
|
|||||||
- name: Setup Submodule
|
- name: Setup Submodule
|
||||||
run: |
|
run: |
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
|
|
||||||
- name: Pull engine updates
|
- name: Pull engine updates
|
||||||
uses: space-wizards/submodule-dependency@v0.1.5
|
uses: space-wizards/submodule-dependency@v0.1.5
|
||||||
@@ -32,6 +36,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd RobustToolbox/
|
cd RobustToolbox/
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
|
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4.2.2
|
- uses: actions/checkout@v4.2.2
|
||||||
with:
|
with:
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
|
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
with:
|
with:
|
||||||
@@ -30,6 +31,12 @@ jobs:
|
|||||||
cd RobustToolbox
|
cd RobustToolbox
|
||||||
git fetch --depth=1
|
git fetch --depth=1
|
||||||
|
|
||||||
|
- name: Get Engine Tag
|
||||||
|
run: |
|
||||||
|
cd RobustToolbox/Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ jobs:
|
|||||||
- name: Setup Submodule
|
- name: Setup Submodule
|
||||||
run: |
|
run: |
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
|
|
||||||
- name: Pull engine updates
|
- name: Pull engine updates
|
||||||
uses: space-wizards/submodule-dependency@v0.1.5
|
uses: space-wizards/submodule-dependency@v0.1.5
|
||||||
@@ -47,6 +51,10 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd RobustToolbox/
|
cd RobustToolbox/
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
|
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
|
|||||||
@@ -17,12 +17,20 @@ jobs:
|
|||||||
- name: Setup submodule
|
- name: Setup submodule
|
||||||
run: |
|
run: |
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd RobustToolbox/Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
- name: Pull engine updates
|
- name: Pull engine updates
|
||||||
uses: space-wizards/submodule-dependency@v0.1.5
|
uses: space-wizards/submodule-dependency@v0.1.5
|
||||||
- name: Update Engine Submodules
|
- name: Update Engine Submodules
|
||||||
run: |
|
run: |
|
||||||
cd RobustToolbox/
|
cd RobustToolbox/
|
||||||
git submodule update --init --recursive
|
git submodule update --init --recursive
|
||||||
|
pushd Lidgren.Network/Lidgren.Network
|
||||||
|
git fetch
|
||||||
|
git checkout 726dd552a4b104fb2b701848705f1250a073f060
|
||||||
|
popd
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v4.1.0
|
uses: actions/setup-dotnet@v4.1.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ Sentencing modifiers are to be applied by the sentencing officer, judge, or arbi
|
|||||||
| style="border: 1px solid black;" | [[File:SL_BreakingAndEntering.png]]
|
| style="border: 1px solid black;" | [[File:SL_BreakingAndEntering.png]]
|
||||||
! style="border: 1px solid black;" | {{anchor|Breaking and Entering}}Breaking and Entering
|
! style="border: 1px solid black;" | {{anchor|Breaking and Entering}}Breaking and Entering
|
||||||
| style="border: 1px solid black;" | 5 minutes
|
| style="border: 1px solid black;" | 5 minutes
|
||||||
| style="border: 1px solid black;" | To break and enter into a high security area where one is not authorised nor invited, with intent to commit a crime within.
|
| style="border: 1px solid black;" | To break and enter into an area where one is not authorised nor invited.
|
||||||
|-
|
|-
|
||||||
| style="border: 1px solid black;" | 203
|
| style="border: 1px solid black;" | 203
|
||||||
| style="border: 1px solid black;" | [[File:SL_Rioting.png]]
|
| style="border: 1px solid black;" | [[File:SL_Rioting.png]]
|
||||||
|
|||||||
+78
-18
@@ -73,28 +73,88 @@ If you are adding a lot of C# code, then take advantage of partial classes. Put
|
|||||||
|
|
||||||
Otherwise, **add comments on or around any changed lines.**
|
Otherwise, **add comments on or around any changed lines.**
|
||||||
|
|
||||||
A comment on a new imported namespace:
|
### Single-Line Changes
|
||||||
|
Format should look like this.
|
||||||
```cs
|
```cs
|
||||||
using Content.Server.Psionics.Glimmer; // DeltaV
|
/* Importing Namespaces - Include optional comment if its not obvious what its being used for. */
|
||||||
|
using Content.Server._DV.Psionics.Glimmer; // DeltaV
|
||||||
|
using Content.Shared.Damage.Systems; // DeltaV - Addition of HandHeldArmor
|
||||||
|
|
||||||
|
/* Changing an upstream line - Same line as the change */
|
||||||
|
if (!TryComp<EyeComponent>(ent, out var eye) || _disabled) // DeltaV - check if disabled
|
||||||
|
|
||||||
|
/* Adding - Either same line or above the line. */
|
||||||
|
EnsureComp<PotentialPsionicComponent>(entity); // Deltav - Psionics
|
||||||
|
|
||||||
|
/* "Deleting" - Don't actually delete, just comment out and say why. This only applies to upstream code. */
|
||||||
|
// args.StatusIcons.Add(_prototype.Index(component.Icon)); // DeltaV - commented out. status icon now added above
|
||||||
```
|
```
|
||||||
|
|
||||||
A pair of comments enclosing a block of added code:
|
> * Its pretty obvious in the example above that importing `Content.Server._DV.Psionics.Glimmer` means we'll be interacting with glimmer so putting `// DeltaV - Add Glimmer` is needlessly redundant.
|
||||||
|
> * It's not as obvious what the `Content.Shared.Damage.Systems` namespace is used for, since its so broad, so adding a comment what feature is using it helps.
|
||||||
|
> * Actual code changes should almost always include the comment after ``// DeltaV`.
|
||||||
|
|
||||||
|
### Multi-Line Changes
|
||||||
|
Depending on how much you are editing, putting a comment on EACH line may be excessive, so if you have a larger block of code you are changing, denote it like so:
|
||||||
```cs
|
```cs
|
||||||
private EntityUid Slice(...)
|
// BEGIN DeltaV - Remove innate radio and radios from pockets
|
||||||
|
for (var i = 1; i <= 4; i++) // Arachnids have 4 pockets
|
||||||
{
|
{
|
||||||
...
|
if (_inventory.TryGetSlotEntity(target, $"pocket{i}", out var headset) && HasComp<HeadsetComponent>(headset))
|
||||||
|
_inventory.TryUnequip(target, $"pocket{i}", true, true);
|
||||||
_transform.SetLocalRotation(sliceUid, 0);
|
|
||||||
|
|
||||||
// DeltaV - start of deep frier stuff
|
|
||||||
var slicedEv = new FoodSlicedEvent(user, uid, sliceUid);
|
|
||||||
RaiseLocalEvent(uid, ref slicedEv);
|
|
||||||
// DeltaV - end of deep frier stuff
|
|
||||||
|
|
||||||
...
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RemComp<ActiveRadioComponent>(target); // If the zombie has an innate radio, get rid of it.
|
||||||
|
// END DeltaV
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> * Denoting these with a BEGIN and END clearly shows they are block of code without having to read the entire comment. This makes it easier to tell when you're dealing with single-line comments versus a block with merging in conflicts.
|
||||||
|
> * Case and order of the first two words is less of a concern. `// DeltaV Begin` or `// Begin DeltaV` will work fine too.
|
||||||
|
> * Try to make your blocks as small as possible, but use your discretion.
|
||||||
|
> * If you deleting multiple lines, use line comments (``//``) if its a few lines but if its a larger block (like commenting out an entire function), it is preferable to use block comments (`/* */`).
|
||||||
|
|
||||||
|
#### Soft Exceptions to the Multi-Line "Rules"
|
||||||
|
Some multi-line changes can use a single-line comment in certain scenarios. But if you are UNSURE, just use `// BEGIN DeltaV` and `// END DeltaV` comments like the previous section does and it'll be fine.
|
||||||
|
|
||||||
|
I'll give some examples.
|
||||||
|
```cs
|
||||||
|
/* This change comments out 3 lines but only needs a single line comment because commenting out the if statement implies that its logic will be commented out too. */
|
||||||
|
// if (obj.WasModified<TraitPrototype>()) // DeltaV - Refreshed in TraitsTab
|
||||||
|
// {
|
||||||
|
// _profileEditor.RefreshTraits();
|
||||||
|
// }
|
||||||
|
|
||||||
|
/* Same principle here. This adds two lines but the if statement implies the next line so commenting both lines isn't really needed. */
|
||||||
|
if (_flight.IsFlying(entity.Owner)) // DeltaV - Harpy Flight
|
||||||
|
return true;
|
||||||
|
```
|
||||||
|
### New Methods or Component Variables
|
||||||
|
Sometimes, you'll need to implement a whole new method or component variable and instead of wrapping it in `// BEGIN DeltaV` and `// END DeltaV`, you can just denote that it's a DeltaV function in the summary block before the function. This denotes the WHOLE function as a DeltaV addition.
|
||||||
|
|
||||||
|
```cs
|
||||||
|
/* New Method Example */
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - Handle revealing ninja if cloaked when attacked by a hitscan attack.
|
||||||
|
/// </summary>
|
||||||
|
private void OnNinjaAttacked(Entity<SpaceNinjaComponent> ent, ref DamageChangedEvent args)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New Component Variable Example */
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - If disabled the action will not disable when no charges remain. Use if you want to handle no charges differently.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool DisableWhenEmpty = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
In short:
|
||||||
|
* Use `// BEGIN DeltaV` and `// END Delta` to denote a *block* of changes.
|
||||||
|
* Keep blocks as small as possible.
|
||||||
|
* Use `// DeltaV` on or before the line if its not a block of changes.
|
||||||
|
* Use exceptions when they make sense.
|
||||||
|
|
||||||
### Changing Upstream Localization Fluent .ftl files
|
### Changing Upstream Localization Fluent .ftl files
|
||||||
|
|
||||||
**Move all changed locale strings to a new DeltaV file** - use a `.ftl` file in the `_DV` folder. Comment out the old strings in the upstream file, and explain that they were moved.
|
**Move all changed locale strings to a new DeltaV file** - use a `.ftl` file in the `_DV` folder. Comment out the old strings in the upstream file, and explain that they were moved.
|
||||||
@@ -104,10 +164,10 @@ Example:
|
|||||||
Commented out old string in `Resources\Locale\en-US\xenoarchaeology\artifact-analyzer.ftl`
|
Commented out old string in `Resources\Locale\en-US\xenoarchaeology\artifact-analyzer.ftl`
|
||||||
```
|
```
|
||||||
# DeltaV - moved to _DV file
|
# DeltaV - moved to _DV file
|
||||||
#analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
|
# analysis-console-info-effect-value = [font="Monospace" size=11][color=gray]{ $state ->
|
||||||
# [true] {$info}
|
# [true] {$info}
|
||||||
# *[false] Unlock nodes to gain info
|
# *[false] Unlock nodes to gain info
|
||||||
#}[/color][/font]
|
# }[/color][/font]
|
||||||
```
|
```
|
||||||
|
|
||||||
The new version of the string in `Resources\Locale\en-US\_DV\xenoarchaeology\artifact-analyzer.ftl`
|
The new version of the string in `Resources\Locale\en-US\_DV\xenoarchaeology\artifact-analyzer.ftl`
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ using Content.Shared.CCVar;
|
|||||||
using Content.Shared.Ghost;
|
using Content.Shared.Ghost;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
|
using Content.Shared.SSDIndicator; // DeltaV - SSD time indicator
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.ResourceManagement;
|
using Robust.Client.ResourceManagement;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing; // DeltaV - SSD time indicator
|
||||||
|
|
||||||
namespace Content.Client.Administration;
|
namespace Content.Client.Administration;
|
||||||
|
|
||||||
@@ -26,6 +28,7 @@ internal sealed class AdminNameOverlay : Overlay
|
|||||||
private readonly IUserInterfaceManager _userInterfaceManager;
|
private readonly IUserInterfaceManager _userInterfaceManager;
|
||||||
private readonly SharedRoleSystem _roles;
|
private readonly SharedRoleSystem _roles;
|
||||||
private readonly IPrototypeManager _prototypeManager;
|
private readonly IPrototypeManager _prototypeManager;
|
||||||
|
private readonly IGameTiming _timing; // DeltaV - Add timing
|
||||||
private readonly Font _font;
|
private readonly Font _font;
|
||||||
private readonly Font _fontBold;
|
private readonly Font _fontBold;
|
||||||
private AdminOverlayAntagFormat _overlayFormat;
|
private AdminOverlayAntagFormat _overlayFormat;
|
||||||
@@ -53,7 +56,8 @@ internal sealed class AdminNameOverlay : Overlay
|
|||||||
IUserInterfaceManager userInterfaceManager,
|
IUserInterfaceManager userInterfaceManager,
|
||||||
IConfigurationManager config,
|
IConfigurationManager config,
|
||||||
SharedRoleSystem roles,
|
SharedRoleSystem roles,
|
||||||
IPrototypeManager prototypeManager)
|
IPrototypeManager prototypeManager,
|
||||||
|
IGameTiming timing) // DeltaV - Add timing
|
||||||
{
|
{
|
||||||
_system = system;
|
_system = system;
|
||||||
_entityManager = entityManager;
|
_entityManager = entityManager;
|
||||||
@@ -62,6 +66,7 @@ internal sealed class AdminNameOverlay : Overlay
|
|||||||
_userInterfaceManager = userInterfaceManager;
|
_userInterfaceManager = userInterfaceManager;
|
||||||
_roles = roles;
|
_roles = roles;
|
||||||
_prototypeManager = prototypeManager;
|
_prototypeManager = prototypeManager;
|
||||||
|
_timing = timing; // DeltaV - Add timing
|
||||||
ZIndex = 200;
|
ZIndex = 200;
|
||||||
// Setting these to a specific ttf would break the antag symbols
|
// Setting these to a specific ttf would break the antag symbols
|
||||||
_font = resourceCache.NotoStack();
|
_font = resourceCache.NotoStack();
|
||||||
@@ -231,6 +236,18 @@ internal sealed class AdminNameOverlay : Overlay
|
|||||||
currentOffset += lineoffset;
|
currentOffset += lineoffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeltaV - SSD Time START
|
||||||
|
if (_entityManager.TryGetComponent<SSDIndicatorComponent>(entity, out var ssdIndicator)
|
||||||
|
&& ssdIndicator.SsdSince is {} ssdSince)
|
||||||
|
{
|
||||||
|
color = Color.MediumPurple;
|
||||||
|
color.A = alpha;
|
||||||
|
var ssdText = Loc.GetString("admin-overlay-ssd-time", ("time", (_timing.CurTime - ssdSince).ToString("%hh':'mm':'ss")));
|
||||||
|
args.ScreenHandle.DrawString(_font, screenCoordinates + currentOffset, ssdText, uiScale, color);
|
||||||
|
currentOffset += lineoffset;
|
||||||
|
}
|
||||||
|
// DeltaV END
|
||||||
|
|
||||||
// Determine antag symbol
|
// Determine antag symbol
|
||||||
string? symbol;
|
string? symbol;
|
||||||
switch (_overlaySymbolStyle)
|
switch (_overlaySymbolStyle)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Robust.Client.ResourceManagement;
|
|||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Timing; // DeltaV - SSD time indicator
|
||||||
|
|
||||||
namespace Content.Client.Administration.Systems
|
namespace Content.Client.Administration.Systems
|
||||||
{
|
{
|
||||||
@@ -19,6 +20,7 @@ namespace Content.Client.Administration.Systems
|
|||||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||||
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
[Dependency] private readonly SharedRoleSystem _roles = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!; // DeltaV - added for SSD time indicator
|
||||||
|
|
||||||
private AdminNameOverlay _adminNameOverlay = default!;
|
private AdminNameOverlay _adminNameOverlay = default!;
|
||||||
|
|
||||||
@@ -36,7 +38,8 @@ namespace Content.Client.Administration.Systems
|
|||||||
_userInterfaceManager,
|
_userInterfaceManager,
|
||||||
_configurationManager,
|
_configurationManager,
|
||||||
_roles,
|
_roles,
|
||||||
_proto);
|
_proto,
|
||||||
|
_timing); // DeltaV - Add timing
|
||||||
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
_adminManager.AdminStatusUpdated += OnAdminStatusUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Content.Shared.IdentityManagement;
|
|||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
using Content.Shared.Item;
|
using Content.Shared.Item;
|
||||||
|
using Content.Shared.Radio.Components; // DeltaV - Client-side Radio Colors
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
@@ -158,6 +159,15 @@ namespace Content.Client.Examine
|
|||||||
// since there's probably one open already if it's coming in from the server.
|
// since there's probably one open already if it's coming in from the server.
|
||||||
var entity = GetEntity(ev.EntityUid);
|
var entity = GetEntity(ev.EntityUid);
|
||||||
|
|
||||||
|
|
||||||
|
// BEGIN DeltaV - Client-Side Radio Colors
|
||||||
|
// This *seems* a bit hacky, but we basically don't want to overwrite what's been pushed on the client-side,
|
||||||
|
// because the server will send us the server-side colors (default). I've yet to find a scenario where the
|
||||||
|
// server and client don't match, so until then, this is probably fine, if a little hacky.
|
||||||
|
if (HasComp<EncryptionKeyHolderComponent>(entity) || HasComp<EncryptionKeyComponent>(entity))
|
||||||
|
return;
|
||||||
|
// END DeltaV
|
||||||
|
|
||||||
OpenTooltip(player.Value, entity, ev.CenterAtCursor, ev.OpenAtOldTooltip, ev.KnowTarget);
|
OpenTooltip(player.Value, entity, ev.CenterAtCursor, ev.OpenAtOldTooltip, ev.KnowTarget);
|
||||||
UpdateTooltipInfo(player.Value, entity, ev.Message, ev.Verbs, getVerbs: false);
|
UpdateTooltipInfo(player.Value, entity, ev.Message, ev.Verbs, getVerbs: false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
<controls:FancyWindow xmlns="https://spacestation14.io"
|
<controls:FancyWindow xmlns="https://spacestation14.io"
|
||||||
xmlns:ui="clr-namespace:Content.Client.Medical.CrewMonitoring"
|
xmlns:ui="clr-namespace:Content.Client.Medical.CrewMonitoring"
|
||||||
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
xmlns:crewMonitoring="clr-namespace:Content.Client._DV.CrewMonitoring"
|
||||||
Title="{Loc crew-monitoring-ui-title}"
|
Title="{Loc crew-monitoring-ui-title}"
|
||||||
Resizable="False"
|
Resizable="False"
|
||||||
SetSize="1210 700"
|
SetSize="1210 700"
|
||||||
MinSize="1210 700">
|
MinSize="1210 700">
|
||||||
<BoxContainer Orientation="Vertical">
|
<BoxContainer Orientation="Vertical">
|
||||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
|
<BoxContainer Orientation="Horizontal" VerticalExpand="True" HorizontalExpand="True">
|
||||||
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20"/>
|
<ui:CrewMonitoringNavMapControl Name="NavMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20" Visible="False" />
|
||||||
|
<crewMonitoring:CrewMonitoringShuttleControl Name="ShuttleMap" HorizontalExpand="True" VerticalExpand="True" Margin="5 20" Visible="False" />
|
||||||
|
|
||||||
<BoxContainer Orientation="Vertical" Margin="0 0 10 0">
|
<BoxContainer Orientation="Vertical" Margin="0 0 10 0">
|
||||||
<controls:StripeBack>
|
<controls:StripeBack>
|
||||||
<PanelContainer>
|
<PanelContainer>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Client.Pinpointer.UI;
|
|||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.UserInterface.Controls;
|
using Content.Client.UserInterface.Controls;
|
||||||
using Content.Shared.Medical.SuitSensor;
|
using Content.Shared.Medical.SuitSensor;
|
||||||
|
using Content.Shared.Station; // DeltaV - crew monitoring radar
|
||||||
using Content.Shared.StatusIcon;
|
using Content.Shared.StatusIcon;
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
@@ -27,6 +28,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
|||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
private readonly SharedTransformSystem _transformSystem;
|
private readonly SharedTransformSystem _transformSystem;
|
||||||
private readonly SpriteSystem _spriteSystem;
|
private readonly SpriteSystem _spriteSystem;
|
||||||
|
private readonly SharedStationSystem _station; // DeltaV - crew monitoring radar
|
||||||
|
|
||||||
private NetEntity? _trackedEntity;
|
private NetEntity? _trackedEntity;
|
||||||
private bool _tryToScrollToListFocus;
|
private bool _tryToScrollToListFocus;
|
||||||
@@ -39,8 +41,10 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
|||||||
|
|
||||||
_transformSystem = _entManager.System<SharedTransformSystem>();
|
_transformSystem = _entManager.System<SharedTransformSystem>();
|
||||||
_spriteSystem = _entManager.System<SpriteSystem>();
|
_spriteSystem = _entManager.System<SpriteSystem>();
|
||||||
|
_station = _entManager.System<SharedStationSystem>(); // DeltaV - crew monitoring radar
|
||||||
|
|
||||||
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
|
NavMap.TrackedEntitySelectedAction += SetTrackedEntityFromNavMap;
|
||||||
|
ShuttleMap.NavMap = NavMap; // DeltaV - crew monitoring radar
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Set(string stationName, EntityUid? mapUid)
|
public void Set(string stationName, EntityUid? mapUid)
|
||||||
@@ -50,8 +54,10 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
|||||||
if (_entManager.TryGetComponent<TransformComponent>(mapUid, out var xform))
|
if (_entManager.TryGetComponent<TransformComponent>(mapUid, out var xform))
|
||||||
NavMap.MapUid = xform.GridUid;
|
NavMap.MapUid = xform.GridUid;
|
||||||
|
|
||||||
else
|
// Begin DeltaV - handle navmap visibility based on current position
|
||||||
NavMap.Visible = false;
|
// else
|
||||||
|
// NavMap.Visible = false;
|
||||||
|
// End DeltaV - handle navmap visibility based on current position
|
||||||
|
|
||||||
StationName.AddStyleClass("LabelBig");
|
StationName.AddStyleClass("LabelBig");
|
||||||
StationName.Text = stationName;
|
StationName.Text = stationName;
|
||||||
@@ -62,6 +68,19 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
|||||||
{
|
{
|
||||||
base.FrameUpdate(args);
|
base.FrameUpdate(args);
|
||||||
|
|
||||||
|
// Begin DeltaV - handle navmap visibility based on current position
|
||||||
|
if (_station.GetOwningStation(ShuttleMap.Owner) is not null)
|
||||||
|
{
|
||||||
|
NavMap.Visible = true;
|
||||||
|
ShuttleMap.Visible = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NavMap.Visible = false;
|
||||||
|
ShuttleMap.Visible = true;
|
||||||
|
}
|
||||||
|
// End DeltaV - handle navmap visibility based on current position
|
||||||
|
|
||||||
if (_tryToScrollToListFocus)
|
if (_tryToScrollToListFocus)
|
||||||
TryToScrollToFocus();
|
TryToScrollToFocus();
|
||||||
}
|
}
|
||||||
@@ -69,6 +88,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
|||||||
public void ShowSensors(List<SuitSensorStatus> sensors, EntityUid monitor, EntityCoordinates? monitorCoords)
|
public void ShowSensors(List<SuitSensorStatus> sensors, EntityUid monitor, EntityCoordinates? monitorCoords)
|
||||||
{
|
{
|
||||||
ClearOutDatedData();
|
ClearOutDatedData();
|
||||||
|
ShuttleMap.Owner = monitor; // DeltaV - crew monitoring radar
|
||||||
|
|
||||||
// No server label
|
// No server label
|
||||||
if (sensors.Count == 0)
|
if (sensors.Count == 0)
|
||||||
@@ -304,7 +324,7 @@ public sealed partial class CrewMonitoringWindow : FancyWindow
|
|||||||
jobContainer.AddChild(jobLabel);
|
jobContainer.AddChild(jobLabel);
|
||||||
|
|
||||||
// Add user coordinates to the navmap
|
// Add user coordinates to the navmap
|
||||||
if (coordinates != null && NavMap.Visible && _blipTexture != null)
|
if (coordinates != null && _blipTexture != null) // DeltaV - don't filter the navmap visibility
|
||||||
{
|
{
|
||||||
NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid,
|
NavMap.TrackedEntities.TryAdd(sensor.SuitSensorUid,
|
||||||
new NavMapBlip
|
new NavMapBlip
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<tabs:AudioTab Name="AudioTab" />
|
<tabs:AudioTab Name="AudioTab" />
|
||||||
<tabs:AccessibilityTab Name="AccessibilityTab" />
|
<tabs:AccessibilityTab Name="AccessibilityTab" />
|
||||||
<tabs:AdminOptionsTab Name="AdminOptionsTab" />
|
<tabs:AdminOptionsTab Name="AdminOptionsTab" />
|
||||||
|
<dtabs:RadioTab Name="RadioTab" /> <!-- DeltaV - Client-side Radio Options -->
|
||||||
<dtabs:DeltaTab Name="DeltaTab" /> <!-- DeltaV -->
|
<dtabs:DeltaTab Name="DeltaTab" /> <!-- DeltaV -->
|
||||||
</TabContainer>
|
</TabContainer>
|
||||||
</DefaultWindow>
|
</DefaultWindow>
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ namespace Content.Client.Options.UI
|
|||||||
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
|
Tabs.SetTabTitle(3, Loc.GetString("ui-options-tab-audio"));
|
||||||
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
|
Tabs.SetTabTitle(4, Loc.GetString("ui-options-tab-accessibility"));
|
||||||
Tabs.SetTabTitle(5, Loc.GetString("ui-options-tab-admin"));
|
Tabs.SetTabTitle(5, Loc.GetString("ui-options-tab-admin"));
|
||||||
Tabs.SetTabTitle(6, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings
|
Tabs.SetTabTitle(6, Loc.GetString("ui-options-tab-radio")); // DeltaV - Radio Channel Colors
|
||||||
|
Tabs.SetTabTitle(7, Loc.GetString("ui-options-tab-deltav")); // DeltaV specific settings
|
||||||
|
|
||||||
UpdateTabs();
|
UpdateTabs();
|
||||||
}
|
}
|
||||||
@@ -36,6 +37,7 @@ namespace Content.Client.Options.UI
|
|||||||
AccessibilityTab.Control.ReloadValues();
|
AccessibilityTab.Control.ReloadValues();
|
||||||
AudioTab.Control.ReloadValues();
|
AudioTab.Control.ReloadValues();
|
||||||
AdminOptionsTab.Control.ReloadValues();
|
AdminOptionsTab.Control.ReloadValues();
|
||||||
|
RadioTab.Control.ReloadValues(); // DeltaV - Radio Channel Colors
|
||||||
DeltaTab.Control.ReloadValues(); // DeltaV
|
DeltaTab.Control.ReloadValues(); // DeltaV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
|
<CheckBox Name="ShowLoocAboveHeadCheckBox" Text="{Loc 'ui-options-show-looc-on-head'}" />
|
||||||
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
|
<CheckBox Name="FancySpeechBubblesCheckBox" Text="{Loc 'ui-options-fancy-speech'}" />
|
||||||
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
|
<CheckBox Name="FancyNameBackgroundsCheckBox" Text="{Loc 'ui-options-fancy-name-background'}" />
|
||||||
|
<CheckBox Name="ChatFollowButton" Text="{Loc 'ui-options-chat-follow-button'}" />
|
||||||
<Label Text="{Loc 'ui-options-general-cursor'}"
|
<Label Text="{Loc 'ui-options-general-cursor'}"
|
||||||
StyleClasses="LabelKeyText"/>
|
StyleClasses="LabelKeyText"/>
|
||||||
<CheckBox Name="ShowHeldItemCheckBox" Text="{Loc 'ui-options-show-held-item'}" />
|
<CheckBox Name="ShowHeldItemCheckBox" Text="{Loc 'ui-options-show-held-item'}" />
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public sealed partial class MiscTab : Control
|
|||||||
Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
|
Control.AddOptionCheckBox(CCVars.ChatEnableFancyBubbles, FancySpeechBubblesCheckBox);
|
||||||
Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
|
Control.AddOptionCheckBox(CCVars.ChatFancyNameBackground, FancyNameBackgroundsCheckBox);
|
||||||
Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
|
Control.AddOptionCheckBox(CCVars.StaticStorageUI, StaticStorageUI);
|
||||||
|
Control.AddOptionCheckBox(CCVars.InterfaceChatFollowButton, ChatFollowButton);
|
||||||
|
|
||||||
Control.Initialize();
|
Control.Initialize();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ public partial class NavMapControl : MapGridControl
|
|||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
protected float UpdateTime = 1.0f;
|
protected float UpdateTime = 1.0f;
|
||||||
protected float MaxSelectableDistance = 10f;
|
public float MaxSelectableDistance = 10f; // DeltaV - accessible to CrewMonitoringShuttleControl
|
||||||
protected float MinDragDistance = 5f;
|
public float MinDragDistance = 5f; // DeltaV - accessible to CrewMonitoringShuttleControl
|
||||||
protected static float MinDisplayedRange = 8f;
|
protected static float MinDisplayedRange = 8f;
|
||||||
protected static float MaxDisplayedRange = 128f;
|
protected static float MaxDisplayedRange = 128f;
|
||||||
protected static float DefaultDisplayedRange = 48f;
|
protected static float DefaultDisplayedRange = 48f;
|
||||||
@@ -193,6 +193,13 @@ public partial class NavMapControl : MapGridControl
|
|||||||
_recenter.Disabled = false;
|
_recenter.Disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Begin DeltaV - we need access to that from outside
|
||||||
|
public void TrackEntity(NetEntity? entity)
|
||||||
|
{
|
||||||
|
TrackedEntitySelectedAction?.Invoke(entity);
|
||||||
|
}
|
||||||
|
// End DeltaV - we need access to that from outside
|
||||||
|
|
||||||
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||||
{
|
{
|
||||||
base.KeyBindUp(args);
|
base.KeyBindUp(args);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
|||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
|
[Dependency] private readonly Shared.SSDIndicator.SSDIndicatorSystem _shared = default!; // DeltaV - SSD Recency, don't want to rename the upstream class
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -35,13 +36,26 @@ public sealed class SSDIndicatorSystem : EntitySystem
|
|||||||
!HasComp<ActiveNPCComponent>(uid) &&
|
!HasComp<ActiveNPCComponent>(uid) &&
|
||||||
HasComp<MindExaminableComponent>(uid))
|
HasComp<MindExaminableComponent>(uid))
|
||||||
{
|
{
|
||||||
// Begin DeltaV Addition
|
// Begin DeltaV Additions
|
||||||
var ev = new ShowSSDIndicatorEvent();
|
var ev = new ShowSSDIndicatorEvent();
|
||||||
RaiseLocalEvent(uid, ref ev);
|
RaiseLocalEvent(uid, ref ev);
|
||||||
if (ev.Hidden)
|
if (ev.Hidden)
|
||||||
return;
|
return;
|
||||||
// End DeltaV Addition
|
|
||||||
args.StatusIcons.Add(_prototype.Index(component.Icon));
|
// SSD Recency Indicator
|
||||||
|
var stage = _shared.GetStage(new Entity<SSDIndicatorComponent>(uid, component));
|
||||||
|
var icon = stage switch
|
||||||
|
{
|
||||||
|
SsdStage.VeryRecent => component.VeryRecentIcon,
|
||||||
|
SsdStage.Recent => component.RecentIcon,
|
||||||
|
SsdStage.Cryoable => component.Icon,
|
||||||
|
_ => throw new InvalidOperationException($"{ToPrettyString(uid)} has an invalid SSD stage {stage}."),
|
||||||
|
};
|
||||||
|
|
||||||
|
args.StatusIcons.Add(_prototype.Index(icon));
|
||||||
|
// End DeltaV Additions
|
||||||
|
|
||||||
|
// args.StatusIcons.Add(_prototype.Index(component.Icon)); // DeltaV - commented out. status icon now added above
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,35 +159,6 @@ namespace Content.Client.Stylesheets
|
|||||||
|
|
||||||
public static readonly Color ChatBackgroundColor = Color.FromHex("#25252ADD");
|
public static readonly Color ChatBackgroundColor = Color.FromHex("#25252ADD");
|
||||||
|
|
||||||
// DeltaV - AAC button variables
|
|
||||||
public static readonly string CommandButtonClass = "CommandButton";
|
|
||||||
public static readonly string EngineeringButtonClass = "EngineeringButton";
|
|
||||||
public static readonly string EpistemicsButtonClass = "EpistemicsButton";
|
|
||||||
public static readonly string JusticeButtonClass = "JusticeButton";
|
|
||||||
public static readonly string LogisticsButtonClass = "LogisticsButton";
|
|
||||||
public static readonly string MedicalButtonClass = "MedicalButton";
|
|
||||||
public static readonly string SecurityButtonClass = "SecurityButton";
|
|
||||||
public static readonly string ServiceButtonClass = "ServiceButton";
|
|
||||||
|
|
||||||
// DeltaV - AAC button colors
|
|
||||||
public static readonly Color CommandButtonColorDefault = Color.FromHex("#404A58");
|
|
||||||
public static readonly Color CommandColorHovered = Color.FromHex("#4F587B");
|
|
||||||
public static readonly Color EngineeringButtonColorDefault = Color.FromHex("#77684B");
|
|
||||||
public static readonly Color EngineeringColorHovered = Color.FromHex("#776D71");
|
|
||||||
public static readonly Color EpistemicsButtonColorDefault = Color.FromHex("#6F5973");
|
|
||||||
public static readonly Color EpistemicsColorHovered = Color.FromHex("#71638E");
|
|
||||||
public static readonly Color LogisticsButtonColorDefault = Color.FromHex("#61503A");
|
|
||||||
public static readonly Color LogisticsColorHovered = Color.FromHex("#675C64");
|
|
||||||
public static readonly Color JusticeButtonColorDefault = Color.FromHex("#4F3D4C");
|
|
||||||
public static readonly Color JusticeColorHovered = Color.FromHex("#5C4B5A");
|
|
||||||
public static readonly Color MedicalButtonColorDefault = Color.FromHex("#49687D");
|
|
||||||
public static readonly Color MedicalColorHovered = Color.FromHex("#556E95");
|
|
||||||
public static readonly Color SecurityButtonColorDefault = Color.FromHex("#724449");
|
|
||||||
public static readonly Color SecurityColorHovered = Color.FromHex("#745370");
|
|
||||||
public static readonly Color ServiceButtonColorDefault = Color.FromHex("#607952");
|
|
||||||
public static readonly Color ServiceColorHovered = Color.FromHex("#667A76");
|
|
||||||
// End DeltaV
|
|
||||||
|
|
||||||
public const string StyleClassCrewManifestGender = "CrewManifestGender"; //Delta-V - Manifest pronouns
|
public const string StyleClassCrewManifestGender = "CrewManifestGender"; //Delta-V - Manifest pronouns
|
||||||
|
|
||||||
// i'm not sure what the missing symbols were referencing, and this is getting obseleted anyway so:
|
// i'm not sure what the missing symbols were referencing, and this is getting obseleted anyway so:
|
||||||
@@ -1677,88 +1648,6 @@ namespace Content.Client.Stylesheets
|
|||||||
BackgroundColor = FancyTreeSelectedRowColor,
|
BackgroundColor = FancyTreeSelectedRowColor,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// DeltaV - AAC button styles
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(CommandButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassNormal)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, CommandButtonColorDefault),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(CommandButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassHover)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, CommandColorHovered),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(EngineeringButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassNormal)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, EngineeringButtonColorDefault),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(EngineeringButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassHover)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, EngineeringColorHovered),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(EpistemicsButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassNormal)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, EpistemicsButtonColorDefault),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(EpistemicsButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassHover)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, EpistemicsColorHovered),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(LogisticsButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassNormal)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, LogisticsButtonColorDefault),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(LogisticsButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassHover)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, LogisticsColorHovered),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(MedicalButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassNormal)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, MedicalButtonColorDefault),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(MedicalButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassHover)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, MedicalColorHovered),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(SecurityButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassNormal)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, SecurityButtonColorDefault),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(SecurityButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassHover)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, SecurityColorHovered),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(ServiceButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassNormal)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, ServiceButtonColorDefault),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(ServiceButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassHover)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, ServiceColorHovered),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(JusticeButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassNormal)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, JusticeButtonColorDefault),
|
|
||||||
|
|
||||||
Element<ContainerButton>()
|
|
||||||
.Class(JusticeButtonClass)
|
|
||||||
.Pseudo(ContainerButton.StylePseudoClassHover)
|
|
||||||
.Prop(Control.StylePropertyModulateSelf, JusticeColorHovered),
|
|
||||||
// End DeltaV
|
|
||||||
|
|
||||||
// Silicon law edit ui
|
// Silicon law edit ui
|
||||||
Element<Label>().Class(SiliconLawContainer.StyleClassSiliconLawPositionLabel)
|
Element<Label>().Class(SiliconLawContainer.StyleClassSiliconLawPositionLabel)
|
||||||
.Prop(Label.StylePropertyFontColor, NanoGold),
|
.Prop(Label.StylePropertyFontColor, NanoGold),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Text.RegularExpressions; // DeltaV - Client-Side Radio Channel Colors
|
||||||
using Content.Client.Administration.Managers;
|
using Content.Client.Administration.Managers;
|
||||||
using Content.Client.Chat;
|
using Content.Client.Chat;
|
||||||
using Content.Client.Chat.Managers;
|
using Content.Client.Chat.Managers;
|
||||||
@@ -839,8 +840,38 @@ public sealed partial class ChatUIController : UIController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - Regex expression to match on the chat message text in order to replace it with another color.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly Regex RadioChatColorRegex = new(@"color=#?([a-fA-F0-9]{8})", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - Replaces the channel color hex code in a message with the client-side hex color
|
||||||
|
/// overridden by <see cref="Content.Client._DV.Radio.RadioChannelColorSystem"/> .
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to find and replace the color hex code in.</param>
|
||||||
|
/// <param name="channelProtoId">The proto ID of the channel. Basically the replacement color.</param>
|
||||||
|
/// <returns>The message with the color hex code replaced, if it exists.</returns>
|
||||||
|
private string ReplaceRadioChannelColorFromServer(string message, string channelProtoId)
|
||||||
|
{
|
||||||
|
if (_prototypeManager.Resolve<RadioChannelPrototype>(channelProtoId, out var channel))
|
||||||
|
{
|
||||||
|
return RadioChatColorRegex.Replace(message, $"color={channel.Color.ToHex()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true)
|
public void ProcessChatMessage(ChatMessage msg, bool speechBubble = true)
|
||||||
{
|
{
|
||||||
|
// BEGIN DeltaV
|
||||||
|
// Needs to happen before ANY other color or pattern matching so that we don't screw up the other colors in the message.
|
||||||
|
if (msg.Channel == ChatChannel.Radio && msg.RadioChannelProtoId is { } channel)
|
||||||
|
{
|
||||||
|
msg.WrappedMessage = ReplaceRadioChannelColorFromServer(msg.WrappedMessage, channel);
|
||||||
|
}
|
||||||
|
// END DeltaV
|
||||||
|
|
||||||
// color the name unless it's something like "the old man"
|
// color the name unless it's something like "the old man"
|
||||||
if ((msg.Channel == ChatChannel.Local || msg.Channel == ChatChannel.Whisper) && _chatNameColorsEnabled)
|
if ((msg.Channel == ChatChannel.Local || msg.Channel == ChatChannel.Whisper) && _chatNameColorsEnabled)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,19 @@ namespace Content.Client._DV.AACTablet.UI;
|
|||||||
[CommonSheetlet]
|
[CommonSheetlet]
|
||||||
public sealed class AACSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet, IButtonConfig
|
public sealed class AACSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet, IButtonConfig
|
||||||
{
|
{
|
||||||
|
private static readonly (string StyleClass, Color Normal, Color Hover)[] SubjectButtonColors =
|
||||||
|
[
|
||||||
|
// Department colors
|
||||||
|
("CommandButton", Color.FromHex("#404A58"), Color.FromHex("#4F587B")),
|
||||||
|
("EngineeringButton", Color.FromHex("#77684B"), Color.FromHex("#776D71")),
|
||||||
|
("EpistemicsButton", Color.FromHex("#6F5973"), Color.FromHex("#71638E")),
|
||||||
|
("JusticeButton", Color.FromHex("#4F3D4C"), Color.FromHex("#5C4B5A")),
|
||||||
|
("LogisticsButton", Color.FromHex("#61503A"), Color.FromHex("#675C64")),
|
||||||
|
("MedicalButton", Color.FromHex("#49687D"), Color.FromHex("#556E95")),
|
||||||
|
("SecurityButton", Color.FromHex("#724449"), Color.FromHex("#745370")),
|
||||||
|
("ServiceButton", Color.FromHex("#607952"), Color.FromHex("#667A76")),
|
||||||
|
];
|
||||||
|
|
||||||
public override StyleRule[] GetRules(T sheet, object config)
|
public override StyleRule[] GetRules(T sheet, object config)
|
||||||
{
|
{
|
||||||
// Create style boxes
|
// Create style boxes
|
||||||
@@ -81,6 +94,21 @@ public sealed class AACSheetlet<T> : Sheetlet<T> where T : PalettedStylesheet, I
|
|||||||
.Prop(Control.StylePropertyModulateSelf, Color.DarkRed),
|
.Prop(Control.StylePropertyModulateSelf, Color.DarkRed),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
SubjectColorRules(rules);
|
||||||
|
|
||||||
return rules.ToArray();
|
return rules.ToArray();
|
||||||
}
|
}
|
||||||
|
// AAC - Makes sure colors are applied to subject buttons
|
||||||
|
private static void SubjectColorRules(List<StyleRule> rules)
|
||||||
|
{
|
||||||
|
foreach (var (styleClass, normal, hover) in SubjectButtonColors)
|
||||||
|
{
|
||||||
|
rules.AddRange([
|
||||||
|
E<ContainerButton>().Class(ContainerButton.StyleClassButton).Class(styleClass)
|
||||||
|
.PseudoNormal().Prop(Control.StylePropertyModulateSelf, normal),
|
||||||
|
E<ContainerButton>().Class(ContainerButton.StyleClassButton).Class(styleClass)
|
||||||
|
.PseudoHovered().Prop(Control.StylePropertyModulateSelf, hover),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ public sealed partial class AACWindow : FancyWindow
|
|||||||
MaxSize = new Vector2(ButtonWidth, ButtonWidth),
|
MaxSize = new Vector2(ButtonWidth, ButtonWidth),
|
||||||
ClipText = false,
|
ClipText = false,
|
||||||
HorizontalExpand = true,
|
HorizontalExpand = true,
|
||||||
StyleClasses = { styleClass }
|
StyleClasses = { ContainerButton.StyleClassButton, styleClass }
|
||||||
};
|
};
|
||||||
|
|
||||||
var buttonLabel = new RichTextLabel
|
var buttonLabel = new RichTextLabel
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Content.Client.Pinpointer.UI;
|
||||||
|
using Content.Client.Shuttles.UI;
|
||||||
|
using Content.Shared.Shuttles.Components;
|
||||||
|
using Content.Shared.Shuttles.Systems;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Shared.Input;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Physics;
|
||||||
|
using Robust.Shared.Physics.Components;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.CrewMonitoring;
|
||||||
|
|
||||||
|
public sealed class CrewMonitoringShuttleControl : BaseShuttleControl
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||||
|
private readonly SharedShuttleSystem _shuttles;
|
||||||
|
private readonly SharedTransformSystem _transform;
|
||||||
|
|
||||||
|
public EntityUid? Owner;
|
||||||
|
public NavMapControl? NavMap;
|
||||||
|
|
||||||
|
private List<Entity<MapGridComponent>> _grids = new();
|
||||||
|
|
||||||
|
public CrewMonitoringShuttleControl() : base(64f, 256f, 256f)
|
||||||
|
{
|
||||||
|
_shuttles = EntManager.System<SharedShuttleSystem>();
|
||||||
|
_transform = EntManager.System<SharedTransformSystem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(DrawingHandleScreen handle)
|
||||||
|
{
|
||||||
|
base.Draw(handle);
|
||||||
|
|
||||||
|
DrawBacking(handle);
|
||||||
|
DrawCircles(handle);
|
||||||
|
|
||||||
|
if (Owner is not { } owner || NavMap is not { } navMap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var xformQuery = EntManager.GetEntityQuery<TransformComponent>();
|
||||||
|
var fixturesQuery = EntManager.GetEntityQuery<FixturesComponent>();
|
||||||
|
var bodyQuery = EntManager.GetEntityQuery<PhysicsComponent>();
|
||||||
|
var iffQuery = EntManager.GetEntityQuery<IFFComponent>();
|
||||||
|
|
||||||
|
if (!xformQuery.TryGetComponent(owner, out var xform)
|
||||||
|
|| xform.MapID == MapId.Nullspace)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var coordinates = new EntityCoordinates(owner, Vector2.Zero);
|
||||||
|
|
||||||
|
var mapPos = _transform.ToMapCoordinates(coordinates);
|
||||||
|
var ourEntRot = Angle.Zero;
|
||||||
|
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
|
||||||
|
Matrix3x2.Invert(ourEntMatrix, out var worldToShuttle);
|
||||||
|
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
|
||||||
|
var worldToView = worldToShuttle * shuttleToView;
|
||||||
|
|
||||||
|
var viewBounds = new Box2Rotated(new Box2(-WorldRange, -WorldRange, WorldRange, WorldRange).Translated(mapPos.Position), ourEntRot, mapPos.Position);
|
||||||
|
var viewAABB = viewBounds.CalcBoundingBox();
|
||||||
|
|
||||||
|
_grids.Clear();
|
||||||
|
_mapManager.FindGridsIntersecting(xform.MapID, new Box2(mapPos.Position - MaxRadarRangeVector, mapPos.Position + MaxRadarRangeVector), ref _grids, approx: true, includeMap: false);
|
||||||
|
|
||||||
|
foreach (var grid in _grids)
|
||||||
|
{
|
||||||
|
if (!fixturesQuery.HasComponent(grid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var body = bodyQuery.GetComponent(grid);
|
||||||
|
iffQuery.TryComp(grid, out var iff);
|
||||||
|
|
||||||
|
if (!_shuttles.CanDraw(grid, body, iff))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var curGridToWorld = _transform.GetWorldMatrix(grid);
|
||||||
|
var curGridToView = curGridToWorld * worldToShuttle * shuttleToView;
|
||||||
|
|
||||||
|
var gridAABB = curGridToWorld.TransformBox(grid.Comp.LocalAABB);
|
||||||
|
if (!gridAABB.Intersects(viewAABB))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var labelColor = _shuttles.GetIFFColor(grid, self: false, iff);
|
||||||
|
DrawGrid(handle, curGridToView, grid, labelColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
var curTime = Timing.RealTime;
|
||||||
|
var blinkFrequency = 1f / 1f;
|
||||||
|
var lit = curTime.TotalSeconds % blinkFrequency > blinkFrequency / 2f;
|
||||||
|
var box = SizeBox;
|
||||||
|
|
||||||
|
foreach (var (coord, value) in navMap.TrackedCoordinates)
|
||||||
|
{
|
||||||
|
if (!lit || !value.Visible)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var blipPos = _transform.ToMapCoordinates(coord);
|
||||||
|
|
||||||
|
if (mapPos.MapId == MapId.Nullspace)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
handle.DrawCircle(Vector2.Clamp(Vector2.Transform(blipPos.Position, worldToView), box.TopLeft, box.BottomRight), float.Sqrt(MinimapScale) * 2f, value.Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var blip in navMap.TrackedEntities.Values)
|
||||||
|
{
|
||||||
|
if (blip.Blinks && !lit)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var blipPos = _transform.ToMapCoordinates(blip.Coordinates);
|
||||||
|
|
||||||
|
if (mapPos.MapId == MapId.Nullspace)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
handle.DrawCircle(Vector2.Clamp(Vector2.Transform(blipPos.Position, worldToView), box.TopLeft, box.BottomRight), float.Sqrt(MinimapScale) * 2f, blip.Color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||||
|
{
|
||||||
|
base.KeyBindUp(args);
|
||||||
|
|
||||||
|
if (Owner is not { } owner || NavMap is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!EntManager.TryGetComponent<TransformComponent>(owner, out var xform) || xform.MapID == MapId.Nullspace)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ourEntRot = Angle.Zero;
|
||||||
|
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
|
||||||
|
Matrix3x2.Invert(ourEntMatrix, out var worldToShuttle);
|
||||||
|
var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
|
||||||
|
var worldToView = worldToShuttle * shuttleToView;
|
||||||
|
Matrix3x2.Invert(worldToView, out var viewToWorld);
|
||||||
|
|
||||||
|
if (args.Function == EngineKeyFunctions.UIClick)
|
||||||
|
{
|
||||||
|
if (NavMap.TrackedEntities.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((StartDragPosition - args.PointerLocation.Position).Length() > NavMap.MinDragDistance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var localPosition = args.PointerLocation.Position - GlobalPixelPosition;
|
||||||
|
var worldPosition = Vector2.Transform(localPosition, viewToWorld);
|
||||||
|
|
||||||
|
var closestEntity = NetEntity.Invalid;
|
||||||
|
var closestDistance = float.PositiveInfinity;
|
||||||
|
|
||||||
|
foreach (var (currentEntity, blip) in NavMap.TrackedEntities)
|
||||||
|
{
|
||||||
|
if (!blip.Selectable)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var currentDistance = (_transform.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
|
||||||
|
|
||||||
|
if (closestDistance < currentDistance || currentDistance * MinimapScale > NavMap.MaxSelectableDistance)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
closestEntity = currentEntity;
|
||||||
|
closestDistance = currentDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closestDistance > NavMap.MaxSelectableDistance || !closestEntity.IsValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
NavMap.TrackEntity(closestEntity);
|
||||||
|
}
|
||||||
|
else if (args.Function == EngineKeyFunctions.UIRightClick)
|
||||||
|
{
|
||||||
|
NavMap.TrackEntity(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<tabs:RadioTab xmlns="https://spacestation14.io"
|
||||||
|
xmlns:tabs="clr-namespace:Content.Client._DV.Options.UI.Tabs"
|
||||||
|
xmlns:ui="clr-namespace:Content.Client.Options.UI">
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<ScrollContainer VerticalExpand="True" HScrollEnabled="False">
|
||||||
|
<BoxContainer Orientation="Vertical" Margin="8">
|
||||||
|
<!-- Common -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-common'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioCommonColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-common'}"/>
|
||||||
|
<Button Name="ResetCommonColorButton" Text="{Loc 'ui-options-radio-reset-common'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- Command -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-command'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioCommandColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-command'}"/>
|
||||||
|
<Button Name="ResetCommandColorButton" Text="{Loc 'ui-options-radio-reset-command'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- Security -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-security'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioSecurityColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-security'}"/>
|
||||||
|
<Button Name="ResetSecurityColorButton" Text="{Loc 'ui-options-radio-reset-security'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- Medical -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-medical'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioMedicalColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-medical'}"/>
|
||||||
|
<Button Name="ResetMedicalColorButton" Text="{Loc 'ui-options-radio-reset-medical'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- Engineering -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-engineering'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioEngineeringColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-engineering'}"/>
|
||||||
|
<Button Name="ResetEngineeringColorButton" Text="{Loc 'ui-options-radio-reset-engineering'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- Science -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-science'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioScienceColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-science'}"/>
|
||||||
|
<Button Name="ResetScienceColorButton" Text="{Loc 'ui-options-radio-reset-science'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- Cargo -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-cargo'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioCargoColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-cargo'}"/>
|
||||||
|
<Button Name="ResetCargoColorButton" Text="{Loc 'ui-options-radio-reset-cargo'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- Service -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-service'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioServiceColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-service'}"/>
|
||||||
|
<Button Name="ResetServiceColorButton" Text="{Loc 'ui-options-radio-reset-service'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- Binary -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-binary'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioBinaryColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-binary'}"/>
|
||||||
|
<Button Name="ResetBinaryColorButton" Text="{Loc 'ui-options-radio-reset-binary'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- DeltaV - Justice -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-justice'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioJusticeColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-justice'}"/>
|
||||||
|
<Button Name="ResetJusticeColorButton" Text="{Loc 'ui-options-radio-reset-justice'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
|
||||||
|
<!-- DeltaV - Prison -->
|
||||||
|
<Label Text="{Loc 'ui-options-radio-header-prison'}"
|
||||||
|
StyleClasses="LabelKeyText"/>
|
||||||
|
<ui:OptionColorSlider Name="RadioPrisonColorSlider"
|
||||||
|
Example="{Loc 'ui-options-radio-example-prison'}"/>
|
||||||
|
<Button Name="ResetPrisonColorButton" Text="{Loc 'ui-options-radio-reset-prison'}" MaxWidth="300" Margin="0 4 0 0" />
|
||||||
|
</BoxContainer>
|
||||||
|
</ScrollContainer>
|
||||||
|
<ui:OptionsTabControlRow Name="Control" Access="Public" />
|
||||||
|
</BoxContainer>
|
||||||
|
</tabs:RadioTab>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
using Content.Client.Options.UI;
|
||||||
|
using Content.Shared._DV.CCVars;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.Options.UI.Tabs;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class RadioTab : Control
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _config = default!; // DeltaV
|
||||||
|
|
||||||
|
public RadioTab()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
// Having so many color sliders on one page is a lot. We should only really try
|
||||||
|
// to have common/departmental channels here, or it just becomes WAAAAY too much.
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorCommon, RadioCommonColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorCommand, RadioCommandColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorSecurity, RadioSecurityColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorMedical, RadioMedicalColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorEngineering, RadioEngineeringColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorScience, RadioScienceColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorCargo, RadioCargoColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorService, RadioServiceColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorBinary, RadioBinaryColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorJustice, RadioJusticeColorSlider);
|
||||||
|
Control.AddOptionColorSlider(DCCVars.RadioColorPrison, RadioPrisonColorSlider);
|
||||||
|
|
||||||
|
ResetCommonColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorCommon);
|
||||||
|
ResetCommandColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorCommand);
|
||||||
|
ResetSecurityColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorSecurity);
|
||||||
|
ResetMedicalColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorMedical);
|
||||||
|
ResetEngineeringColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorEngineering);
|
||||||
|
ResetScienceColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorScience);
|
||||||
|
ResetCargoColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorCargo);
|
||||||
|
ResetServiceColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorService);
|
||||||
|
ResetBinaryColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorBinary);
|
||||||
|
ResetJusticeColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorJustice);
|
||||||
|
ResetPrisonColorButton.OnPressed += _ => OnResetColorButton(DCCVars.RadioColorPrison);
|
||||||
|
|
||||||
|
Control.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnResetColorButton(CVarDef<string> cvar)
|
||||||
|
{
|
||||||
|
_config.SetCVar(cvar, cvar.DefaultValue);
|
||||||
|
Control.ReloadValues();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
using Content.Shared._DV.Psionics.Systems.PsionicPowers;
|
|
||||||
|
|
||||||
namespace Content.Client._DV.Psionics.Systems.PsionicPowers;
|
|
||||||
|
|
||||||
// This does nothing here. The code is all in the shared/server version.
|
|
||||||
public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem;
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Content.Shared._DV.CCVars;
|
||||||
|
using Content.Shared.Radio;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.Radio;
|
||||||
|
|
||||||
|
public sealed partial class RadioChannelColorSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
|
||||||
|
// Feels like there should be a better way to do this but I'd rather not add a client-side cvar ID or reference
|
||||||
|
// to a prototype in Content.Shared, since I don't want people thinking they can ever use it on the server-side.
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> CommonChannelProto = "Common";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> CommandChannelProto = "Command";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> SecurityChannelProto = "Security";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> MedicalChannelProto = "Medical";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> EngineeringChannelProto = "Engineering";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> ScienceChannelProto = "Science";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> CargoChannelProto = "Supply";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> ServiceChannelProto = "Service";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> BinaryChannelProto = "Binary";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> JusticeChannelProto = "Justice";
|
||||||
|
private static readonly ProtoId<RadioChannelPrototype> PrisonChannelProto = "Prison";
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorCommon, (value) => { SetChannelColor(CommonChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorCommand, (value) => { SetChannelColor(CommandChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorSecurity, (value) => { SetChannelColor(SecurityChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorMedical, (value) => { SetChannelColor(MedicalChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorEngineering, (value) => { SetChannelColor(EngineeringChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorScience, (value) => { SetChannelColor(ScienceChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorCargo, (value) => { SetChannelColor(CargoChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorService, (value) => { SetChannelColor(ServiceChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorBinary, (value) => { SetChannelColor(BinaryChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorJustice, (value) => { SetChannelColor(JusticeChannelProto, value); }, true);
|
||||||
|
_config.OnValueChanged(DCCVars.RadioColorPrison, (value) => { SetChannelColor(PrisonChannelProto, value); }, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetChannelColor(ProtoId<RadioChannelPrototype> channel, string newColor)
|
||||||
|
{
|
||||||
|
if (Color.TryParse(newColor, out var color))
|
||||||
|
_proto.Index(channel).Color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Content.Shared.Labels.Components;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.Silicons;
|
||||||
|
|
||||||
|
public static class NameHelpers
|
||||||
|
{
|
||||||
|
extension(IEntityManager self)
|
||||||
|
{
|
||||||
|
public string PaperName(EntityUid paper)
|
||||||
|
{
|
||||||
|
if (self.TryGetComponent<LabelComponent>(paper, out var label) && label.CurrentLabel is { } labelText)
|
||||||
|
return labelText;
|
||||||
|
|
||||||
|
return self.GetComponent<MetaDataComponent>(paper).EntityName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Content.Shared._DV.Silicons;
|
||||||
|
using Content.Shared.Fax;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.Silicons;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class StationAiFaxBoundUserInterface(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IFileDialogManager _fileDialogManager = default!;
|
||||||
|
|
||||||
|
private StationAiFaxWindow? _window;
|
||||||
|
private List<StationAiFaxPageWindow> _windows = new();
|
||||||
|
|
||||||
|
private bool _dialogIsOpen;
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
base.Open();
|
||||||
|
|
||||||
|
_window = this.CreateWindow<StationAiFaxWindow>();
|
||||||
|
_window.FileButtonPressed += OnFileButtonPressed;
|
||||||
|
_window.BlankButtonPressed += OnBlankButtonPressed;
|
||||||
|
_window.OpenSendWindowPressed += OnOpenSendWindowPressed;
|
||||||
|
_window.ExaminePressed += OnExaminePressed;
|
||||||
|
_window.ShredPressed += OnShredPressed;
|
||||||
|
_window.DuplicatePressed += OnDuplicatePressed;
|
||||||
|
|
||||||
|
UpdateFaxes(EntMan.System<SharedContainerSystem>().EnsureContainer<Container>(Owner, StationAiFaxComponent.Container, out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnFileButtonPressed()
|
||||||
|
{
|
||||||
|
if (_dialogIsOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_dialogIsOpen = true;
|
||||||
|
var filters = new FileDialogFilters(new FileDialogFilters.Group("txt"));
|
||||||
|
await using var file = await _fileDialogManager.OpenFile(filters, FileAccess.Read);
|
||||||
|
_dialogIsOpen = false;
|
||||||
|
|
||||||
|
if (_window == null || _window.Disposed || file == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var reader = new StreamReader(file);
|
||||||
|
|
||||||
|
var firstLine = await reader.ReadLineAsync();
|
||||||
|
string? label = null;
|
||||||
|
var content = await reader.ReadToEndAsync();
|
||||||
|
|
||||||
|
if (firstLine is { })
|
||||||
|
{
|
||||||
|
if (firstLine.StartsWith('#'))
|
||||||
|
{
|
||||||
|
label = firstLine[1..].Trim();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
content = firstLine + "\n" + content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SendMessage(new FaxFileMessage(
|
||||||
|
label?[..Math.Min(label.Length, FaxFileMessageValidation.MaxLabelSize)],
|
||||||
|
content[..Math.Min(content.Length, FaxFileMessageValidation.MaxContentSize)],
|
||||||
|
false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBlankButtonPressed()
|
||||||
|
{
|
||||||
|
SendMessage(new FaxFileMessage(null, string.Empty, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnOpenSendWindowPressed(EntityUid entity)
|
||||||
|
{
|
||||||
|
var window = new StationAiFaxPageWindow(entity, EntMan);
|
||||||
|
_windows.Add(window);
|
||||||
|
|
||||||
|
window.RefreshButtonPressed += OnRefreshButtonPressed;
|
||||||
|
window.PeerSelected += OnPeerSelected;
|
||||||
|
window.SendButtonPressed += OnSendDocument;
|
||||||
|
|
||||||
|
if (UiSystem.TryGetUiState<FaxUiState>(Owner, UiKey, out var state))
|
||||||
|
{
|
||||||
|
window.UpdateState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.OpenCentered();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSendDocument(EntityUid entity)
|
||||||
|
{
|
||||||
|
SendMessage(new StationAiFaxSendMessage(EntMan.GetNetEntity(entity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnExaminePressed(EntityUid entity)
|
||||||
|
{
|
||||||
|
SendPredictedMessage(new StationAiFaxExamineMessage(EntMan.GetNetEntity(entity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnShredPressed(EntityUid entity)
|
||||||
|
{
|
||||||
|
SendPredictedMessage(new StationAiFaxShredMessage(EntMan.GetNetEntity(entity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDuplicatePressed(EntityUid entity)
|
||||||
|
{
|
||||||
|
SendPredictedMessage(new StationAiFaxDuplicateMessage(EntMan.GetNetEntity(entity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRefreshButtonPressed()
|
||||||
|
{
|
||||||
|
SendMessage(new FaxRefreshMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPeerSelected(string address)
|
||||||
|
{
|
||||||
|
SendMessage(new FaxDestinationMessage(address));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (state is not FaxUiState cast)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_window?.UpdateState(cast);
|
||||||
|
foreach (var window in _windows)
|
||||||
|
{
|
||||||
|
window.UpdateState(cast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateFaxes(Container container)
|
||||||
|
{
|
||||||
|
_window?.UpdateFaxes(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<PanelContainer xmlns="https://spacestation14.io"
|
||||||
|
xmlns:ui="clr-namespace:Content.Client._DV.UserInterfaces"
|
||||||
|
xmlns:xe="clr-namespace:Content.Client.UserInterface.XamlExtensions"
|
||||||
|
StyleClasses="PanelLight"
|
||||||
|
HorizontalExpand="True">
|
||||||
|
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="8">
|
||||||
|
<Label Name="PaperName" HorizontalExpand="True" />
|
||||||
|
|
||||||
|
<ui:IconButton Name="Shred" StyleClasses="OpenRight" Icon="{xe:Tex '/Textures/Interface/shredder.svg.192dpi.png'}" />
|
||||||
|
<ui:IconButton Name="Duplicate" StyleClasses="OpenBoth" Icon="{xe:Tex '/Textures/Interface/files.svg.192dpi.png'}" />
|
||||||
|
<ui:IconButton Name="Examine" StyleClasses="OpenBoth" Icon="{xe:Tex '/Textures/Interface/eye.svg.192dpi.png'}" />
|
||||||
|
<ui:IconButton Name="Send" StyleClasses="OpenLeft" Icon="{xe:Tex '/Textures/Interface/send.svg.192dpi.png'}" />
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.Silicons;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class StationAiFaxItem : PanelContainer
|
||||||
|
{
|
||||||
|
public string? Text { get => PaperName.Text; set => PaperName.Text = value; }
|
||||||
|
public bool CanSend { get => !Duplicate.Disabled; set => Duplicate.Disabled = !value; }
|
||||||
|
|
||||||
|
public event Action? ShredPressed;
|
||||||
|
public event Action? DuplicatePressed;
|
||||||
|
public event Action? ExaminePressed;
|
||||||
|
public event Action? SendPressed;
|
||||||
|
|
||||||
|
public StationAiFaxItem()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
Shred.OnPressed += _ => ShredPressed?.Invoke();
|
||||||
|
Duplicate.OnPressed += _ => DuplicatePressed?.Invoke();
|
||||||
|
Examine.OnPressed += _ => ExaminePressed?.Invoke();
|
||||||
|
Send.OnPressed += _ => SendPressed?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<controls:FancyWindow
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
Title="{Loc 'fax-machine-ui-page-window'}"
|
||||||
|
MinSize="380 220">
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True" VerticalExpand="True">
|
||||||
|
<PanelContainer StyleClasses="PanelDark" HorizontalExpand="True" VerticalExpand="True">
|
||||||
|
<BoxContainer Orientation="Vertical" Margin="8">
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Text="{Loc 'fax-machine-ui-sending'}"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalExpand="True" />
|
||||||
|
|
||||||
|
<Label Name="SendingLabel"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Align="Right"/>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Text="{Loc 'fax-machine-ui-from'}"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalExpand="True" />
|
||||||
|
|
||||||
|
<Label Name="FromLabel"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Align="Right"/>
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<BoxContainer
|
||||||
|
Orientation="Horizontal">
|
||||||
|
|
||||||
|
<Label Text="{Loc 'fax-machine-ui-to'}"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalExpand="True"/>
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<OptionButton Name="PeerSelector"
|
||||||
|
StyleClasses="OpenRight" />
|
||||||
|
|
||||||
|
<Button StyleClasses="OpenLeft"
|
||||||
|
Name="RefreshButton"
|
||||||
|
ToolTip="{Loc 'fax-machine-ui-refresh-button'}">
|
||||||
|
<TextureButton
|
||||||
|
MouseFilter="Ignore"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Scale="0.9 0.9"
|
||||||
|
StyleClasses="RefreshButton" />
|
||||||
|
</Button>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
<BoxContainer Orientation="Horizontal" SeparationOverride="8" Margin="8">
|
||||||
|
<Button Name="SendButton"
|
||||||
|
Text="{Loc 'fax-machine-ui-send-button'}"
|
||||||
|
HorizontalExpand="True"
|
||||||
|
Disabled="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</controls:FancyWindow>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Content.Shared.Fax;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.Silicons;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class StationAiFaxPageWindow : FancyWindow
|
||||||
|
{
|
||||||
|
public event Action? RefreshButtonPressed;
|
||||||
|
public event Action<string>? PeerSelected;
|
||||||
|
public event Action<EntityUid>? SendButtonPressed;
|
||||||
|
|
||||||
|
public StationAiFaxPageWindow(EntityUid uid, IEntityManager entityManager)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
SendingLabel.Text = entityManager.PaperName(uid);
|
||||||
|
|
||||||
|
RefreshButton.OnPressed += _ => RefreshButtonPressed?.Invoke();
|
||||||
|
SendButton.OnPressed += _ => SendButtonPressed?.Invoke(uid);
|
||||||
|
PeerSelector.OnItemSelected += args =>
|
||||||
|
PeerSelected?.Invoke((string) args.Button.GetItemMetadata(args.Id)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(FaxUiState state)
|
||||||
|
{
|
||||||
|
SendButton.Disabled = !state.CanSendCooldown;
|
||||||
|
FromLabel.Text = state.DeviceName;
|
||||||
|
|
||||||
|
if (state.AvailablePeers.Count == 0)
|
||||||
|
{
|
||||||
|
PeerSelector.AddItem(Loc.GetString("fax-machine-ui-no-peers"));
|
||||||
|
PeerSelector.Disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PeerSelector.Disabled && state.AvailablePeers.Count != 0)
|
||||||
|
{
|
||||||
|
PeerSelector.Clear();
|
||||||
|
PeerSelector.Disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// always must be selected destination
|
||||||
|
if (string.IsNullOrEmpty(state.DestinationAddress) && state.AvailablePeers.Count != 0)
|
||||||
|
{
|
||||||
|
PeerSelected?.Invoke(state.AvailablePeers.First().Key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.AvailablePeers.Count != 0)
|
||||||
|
{
|
||||||
|
PeerSelector.Clear();
|
||||||
|
|
||||||
|
foreach (var (address, name) in state.AvailablePeers)
|
||||||
|
{
|
||||||
|
var id = AddPeerSelect(name, address);
|
||||||
|
if (address == state.DestinationAddress)
|
||||||
|
PeerSelector.Select(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int AddPeerSelect(string name, string address)
|
||||||
|
{
|
||||||
|
PeerSelector.AddItem(name);
|
||||||
|
PeerSelector.SetItemMetadata(PeerSelector.ItemCount - 1, address);
|
||||||
|
return PeerSelector.ItemCount - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using Content.Shared._DV.Silicons;
|
||||||
|
using Content.Shared.Fax;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Containers;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.Silicons;
|
||||||
|
|
||||||
|
public sealed class StationAiFaxSystem : SharedStationAiFaxSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly UserInterfaceSystem _userInterface = default!;
|
||||||
|
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<StationAiFaxComponent, EntRemovedFromContainerMessage>(OnRemoved);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInserted(Entity<StationAiFaxComponent> ent, ref EntInsertedIntoContainerMessage args)
|
||||||
|
{
|
||||||
|
base.OnInserted(ent, ref args);
|
||||||
|
|
||||||
|
if (!_userInterface.TryGetOpenUi<StationAiFaxBoundUserInterface>(ent.Owner, FaxUiKey.Key, out var bui))
|
||||||
|
return;
|
||||||
|
|
||||||
|
bui.UpdateFaxes(_container.EnsureContainer<Container>(ent, StationAiFaxComponent.Container, out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRemoved(Entity<StationAiFaxComponent> ent, ref EntRemovedFromContainerMessage args)
|
||||||
|
{
|
||||||
|
if (!_userInterface.TryGetOpenUi<StationAiFaxBoundUserInterface>(ent.Owner, FaxUiKey.Key, out var bui))
|
||||||
|
return;
|
||||||
|
|
||||||
|
bui.UpdateFaxes(_container.EnsureContainer<Container>(ent, StationAiFaxComponent.Container, out _));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<controls:FancyWindow
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
Title="{Loc 'fax-machine-ui-window'}"
|
||||||
|
MinSize="500 600">
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Vertical" Name="NoFaxes" HorizontalAlignment="Center" VerticalAlignment="Center" SeparationOverride="8">
|
||||||
|
<Label StyleClasses="PlaceholderText" Text="{Loc 'fax-machine-ui-no-faxes'}" Align="Center" />
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Vertical" HorizontalAlignment="Center">
|
||||||
|
<Button Name="NoFaxesBlank" Text="{Loc 'fax-machine-ui-add-blank-file'}" HorizontalExpand="True" StyleClasses="OpenLeft" />
|
||||||
|
<Button Name="NoFaxesUpload" Text="{Loc 'fax-machine-ui-upload-file'}" HorizontalExpand="True" StyleClasses="OpenRight" />
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
</BoxContainer>
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Vertical" Name="YesFaxes" HorizontalExpand="True" VerticalExpand="True">
|
||||||
|
<PanelContainer StyleClasses="PanelDark" VerticalExpand="True" HorizontalExpand="True">
|
||||||
|
<ScrollContainer VerticalExpand="True" HorizontalExpand="True">
|
||||||
|
<BoxContainer Name="FaxList" Margin="8" VerticalAlignment="Top" HorizontalExpand="True" SeparationOverride="8" Orientation="Vertical" />
|
||||||
|
</ScrollContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
|
||||||
|
<BoxContainer Orientation="Horizontal" SeparationOverride="8" Margin="8">
|
||||||
|
<Button Name="YesFaxesBlank" Text="{Loc 'fax-machine-ui-add-blank-file'}" HorizontalAlignment="Center" />
|
||||||
|
<Button Name="YesFaxesUpload" Text="{Loc 'fax-machine-ui-upload-file'}" HorizontalAlignment="Center" />
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</controls:FancyWindow>
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
using Content.Client.UserInterface.Controls;
|
||||||
|
using Content.Shared.Fax;
|
||||||
|
using Content.Shared.Labels.Components;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.Silicons;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class StationAiFaxWindow : FancyWindow
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
|
public event Action? BlankButtonPressed;
|
||||||
|
public event Action? FileButtonPressed;
|
||||||
|
public event Action<EntityUid>? OpenSendWindowPressed;
|
||||||
|
public event Action<EntityUid>? ExaminePressed;
|
||||||
|
public event Action<EntityUid>? ShredPressed;
|
||||||
|
public event Action<EntityUid>? DuplicatePressed;
|
||||||
|
|
||||||
|
public bool CanSend
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (field == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
field = value;
|
||||||
|
foreach (var item in FaxList.Children)
|
||||||
|
{
|
||||||
|
var castItem = (StationAiFaxItem)item;
|
||||||
|
castItem.CanSend = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
NoFaxesBlank.Disabled = !value;
|
||||||
|
NoFaxesUpload.Disabled = !value;
|
||||||
|
YesFaxesBlank.Disabled = !value;
|
||||||
|
YesFaxesUpload.Disabled = !value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StationAiFaxWindow()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
NoFaxesBlank.OnPressed += _ => BlankButtonPressed?.Invoke();
|
||||||
|
NoFaxesUpload.OnPressed += _ => FileButtonPressed?.Invoke();
|
||||||
|
|
||||||
|
YesFaxesBlank.OnPressed += _ => BlankButtonPressed?.Invoke();
|
||||||
|
YesFaxesUpload.OnPressed += _ => FileButtonPressed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateFaxes(Robust.Shared.Containers.Container container)
|
||||||
|
{
|
||||||
|
FaxList.RemoveAllChildren();
|
||||||
|
foreach (var item in container.ContainedEntities)
|
||||||
|
{
|
||||||
|
var page = new StationAiFaxItem
|
||||||
|
{
|
||||||
|
Text = _entityManager.TryGetComponent<LabelComponent>(item, out var label) &&
|
||||||
|
label.CurrentLabel is { } labelText
|
||||||
|
? labelText
|
||||||
|
: _entityManager.GetComponent<MetaDataComponent>(item).EntityName,
|
||||||
|
CanSend = CanSend,
|
||||||
|
};
|
||||||
|
FaxList.AddChild(page);
|
||||||
|
|
||||||
|
page.ExaminePressed += () => ExaminePressed?.Invoke(item);
|
||||||
|
page.ShredPressed += () => ShredPressed?.Invoke(item);
|
||||||
|
page.DuplicatePressed += () => DuplicatePressed?.Invoke(item);
|
||||||
|
page.SendPressed += () => OpenSendWindowPressed?.Invoke(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (container.ContainedEntities.Count == 0)
|
||||||
|
{
|
||||||
|
YesFaxes.Visible = false;
|
||||||
|
NoFaxes.Visible = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
YesFaxes.Visible = true;
|
||||||
|
NoFaxes.Visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(FaxUiState state)
|
||||||
|
{
|
||||||
|
CanSend = state.CanSendCooldown;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -108,6 +108,70 @@ public sealed partial class TraitCategory : BoxContainer
|
|||||||
{
|
{
|
||||||
CategoryPointsLabel.Visible = false;
|
CategoryPointsLabel.Visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock unselected entries whenever the category cap is reached so the
|
||||||
|
// player gets a clear visual cue instead of silently failing to equip.
|
||||||
|
UpdateCategoryFullState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Propagates category-cap lock state to every entry.
|
||||||
|
/// <para>
|
||||||
|
/// Selected entries are never locked - they are the traits filling the cap
|
||||||
|
/// and must stay interactive so the player can deselect them.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Unselected entries are locked when:
|
||||||
|
/// <list type="bullet">
|
||||||
|
/// <item><see cref="TraitCategoryPrototype.MaxTraits"/> is defined and already reached, OR</item>
|
||||||
|
/// <item><see cref="TraitCategoryPrototype.MaxPoints"/> is defined and this specific
|
||||||
|
/// trait's cost would push spending over the limit.</item>
|
||||||
|
/// </list>
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateCategoryFullState()
|
||||||
|
{
|
||||||
|
var traitCapReached = _category.MaxTraits.HasValue && SelectedCount >= _category.MaxTraits.Value;
|
||||||
|
|
||||||
|
foreach (var (_, entry) in _traitEntries)
|
||||||
|
{
|
||||||
|
if (entry.IsSelected)
|
||||||
|
{
|
||||||
|
// Selected traits are never locked by category state.
|
||||||
|
entry.SetLockedByCategory(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var locked = traitCapReached;
|
||||||
|
|
||||||
|
// Even when the trait count cap hasn't been hit, lock this specific entry
|
||||||
|
// if adding its cost would exceed the category's point budget.
|
||||||
|
if (!locked && _category.MaxPoints.HasValue)
|
||||||
|
locked = PointsSpent + entry.TraitCost > _category.MaxPoints.Value;
|
||||||
|
|
||||||
|
entry.SetLockedByCategory(locked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by <see cref="TraitsTab"/> whenever the player's global point budget changes.
|
||||||
|
/// Locks any unselected entry whose cost the player can no longer afford.
|
||||||
|
/// <para>
|
||||||
|
/// Free (0-cost) and negative-cost traits are never locked - they cost nothing or
|
||||||
|
/// actually give points back. Selected traits are also never locked here; they
|
||||||
|
/// were affordable when picked and must stay interactive to be deselected.
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public void UpdateGlobalPointsLock(int remainingPoints)
|
||||||
|
{
|
||||||
|
foreach (var (_, entry) in _traitEntries)
|
||||||
|
{
|
||||||
|
// A selected trait is always unlocked here - same reasoning as category cap:
|
||||||
|
// it was paid for at selection time and must stay interactive.
|
||||||
|
// Traits that cost nothing or give points back are never locked by budget.
|
||||||
|
var shouldLock = !entry.IsSelected && entry.TraitCost > remainingPoints;
|
||||||
|
entry.SetLockedByPoints(shouldLock);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetTraitSelected(ProtoId<TraitPrototype> traitId, bool selected)
|
public void SetTraitSelected(ProtoId<TraitPrototype> traitId, bool selected)
|
||||||
@@ -171,11 +235,11 @@ public sealed partial class TraitCategory : BoxContainer
|
|||||||
/// Updates condition states for all trait entries based on current job/species.
|
/// Updates condition states for all trait entries based on current job/species.
|
||||||
/// Traits that don't meet conditions are disabled but still visible.
|
/// Traits that don't meet conditions are disabled but still visible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateConditions(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
|
public void UpdateConditions(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences, IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
|
||||||
{
|
{
|
||||||
foreach (var (_, entry) in _traitEntries)
|
foreach (var (_, entry) in _traitEntries)
|
||||||
{
|
{
|
||||||
entry.UpdateConditionsMet(jobId, speciesId, antagPreferences);
|
entry.UpdateConditionsMet(jobId, speciesId, antagPreferences, selectedTraits);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update stats since some traits may have been deselected
|
// Update stats since some traits may have been deselected
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ public sealed partial class TraitEntry : PanelContainer
|
|||||||
private bool _isUpdating;
|
private bool _isUpdating;
|
||||||
private readonly List<string> _failedConditionTooltips = new();
|
private readonly List<string> _failedConditionTooltips = new();
|
||||||
|
|
||||||
|
private bool _isLockedByCategory;
|
||||||
|
private bool _isLockedByPoints;
|
||||||
|
|
||||||
public TraitEntry(TraitPrototype trait)
|
public TraitEntry(TraitPrototype trait)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
@@ -59,9 +62,17 @@ public sealed partial class TraitEntry : PanelContainer
|
|||||||
|
|
||||||
foreach (var condition in _trait.Conditions)
|
foreach (var condition in _trait.Conditions)
|
||||||
{
|
{
|
||||||
var tooltip = condition.GetTooltip(_prototype, _loc);
|
if (condition is TraitDependencyCondition depCond)
|
||||||
if (!string.IsNullOrEmpty(tooltip))
|
{
|
||||||
tooltips.Add(tooltip);
|
// Use GetTooltips() to get individual lines without duplication
|
||||||
|
tooltips.AddRange(depCond.GetTooltips(_prototype, _loc));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var tooltip = condition.GetTooltip(_prototype, _loc);
|
||||||
|
if (!string.IsNullOrEmpty(tooltip))
|
||||||
|
tooltips.Add(tooltip);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tooltips.Count > 0)
|
if (tooltips.Count > 0)
|
||||||
@@ -91,39 +102,92 @@ public sealed partial class TraitEntry : PanelContainer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates whether conditions are met based on current job/species.
|
/// Updates whether conditions are met based on current job/species.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateConditionsMet(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
|
public void UpdateConditionsMet(
|
||||||
|
ProtoId<JobPrototype>? jobId,
|
||||||
|
ProtoId<SpeciesPrototype>? speciesId,
|
||||||
|
IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences,
|
||||||
|
IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
|
||||||
{
|
{
|
||||||
_failedConditionTooltips.Clear();
|
_failedConditionTooltips.Clear();
|
||||||
MeetsConditions = true;
|
MeetsConditions = true;
|
||||||
|
|
||||||
foreach (var condition in _trait.Conditions)
|
foreach (var condition in _trait.Conditions)
|
||||||
{
|
{
|
||||||
var result = condition switch
|
bool result;
|
||||||
{
|
|
||||||
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
|
|
||||||
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
|
|
||||||
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
|
|
||||||
HasCompCondition compCond => !compCond.Invert, // can't check in lobby but screws with the inversion logic
|
|
||||||
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
|
|
||||||
AnyOfCondition anyOfCond => CheckAnyOfCondition(anyOfCond, jobId, speciesId, antagPreferences),
|
|
||||||
_ => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply inversion
|
if (condition is TraitDependencyCondition depCond)
|
||||||
result ^= condition.Invert;
|
{
|
||||||
|
result = CheckDependencyCondition(depCond, selectedTraits);
|
||||||
|
// CheckDependencyCondition adds its own tooltips directly
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = condition switch
|
||||||
|
{
|
||||||
|
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
|
||||||
|
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
|
||||||
|
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
|
||||||
|
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
|
||||||
|
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
|
||||||
|
AnyOfCondition anyOfCond => CheckAnyOfCondition(anyOfCond, jobId, speciesId, antagPreferences, selectedTraits),
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply inversion for non-dependency conditions
|
||||||
|
result ^= condition.Invert;
|
||||||
|
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
var tooltip = condition.GetTooltip(_prototype, _loc);
|
||||||
|
if (!string.IsNullOrEmpty(tooltip))
|
||||||
|
_failedConditionTooltips.Add(tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!result)
|
if (!result)
|
||||||
{
|
|
||||||
MeetsConditions = false;
|
MeetsConditions = false;
|
||||||
var tooltip = condition.GetTooltip(_prototype, _loc);
|
|
||||||
if (!string.IsNullOrEmpty(tooltip))
|
|
||||||
_failedConditionTooltips.Add(tooltip);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateDisabledState();
|
UpdateDisabledState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks a TraitDependencyCondition and adds failure tooltips directly.
|
||||||
|
/// Returns true if the condition passes.
|
||||||
|
/// </summary>
|
||||||
|
private bool CheckDependencyCondition(TraitDependencyCondition condition, IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
|
||||||
|
{
|
||||||
|
var passed = true;
|
||||||
|
|
||||||
|
foreach (var conflict in condition.Conflicts)
|
||||||
|
{
|
||||||
|
if (selectedTraits == null || !selectedTraits.Contains(conflict))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (_prototype.TryIndex(conflict, out var conflictProto))
|
||||||
|
{
|
||||||
|
_failedConditionTooltips.Add(_loc.GetString("trait-condition-trait-conflict",
|
||||||
|
("trait", _loc.GetString(conflictProto.Name))));
|
||||||
|
}
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var required in condition.Requires)
|
||||||
|
{
|
||||||
|
if (selectedTraits != null && selectedTraits.Contains(required))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (_prototype.TryIndex(required, out var requiredProto))
|
||||||
|
{
|
||||||
|
_failedConditionTooltips.Add(_loc.GetString("trait-condition-trait-required",
|
||||||
|
("trait", _loc.GetString(requiredProto.Name))));
|
||||||
|
}
|
||||||
|
passed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
|
|
||||||
private bool CheckSpeciesCondition(IsSpeciesCondition condition, ProtoId<SpeciesPrototype>? speciesId)
|
private bool CheckSpeciesCondition(IsSpeciesCondition condition, ProtoId<SpeciesPrototype>? speciesId)
|
||||||
{
|
{
|
||||||
if (!_prototype.TryIndex(speciesId, out var species))
|
if (!_prototype.TryIndex(speciesId, out var species))
|
||||||
@@ -159,7 +223,12 @@ public sealed partial class TraitEntry : PanelContainer
|
|||||||
return antagPreferences.Contains(condition.Antag);
|
return antagPreferences.Contains(condition.Antag);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckAnyOfCondition(AnyOfCondition condition, ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId, IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences)
|
private bool CheckAnyOfCondition(
|
||||||
|
AnyOfCondition condition,
|
||||||
|
ProtoId<JobPrototype>? jobId,
|
||||||
|
ProtoId<SpeciesPrototype>? speciesId,
|
||||||
|
IReadOnlySet<ProtoId<AntagPrototype>>? antagPreferences,
|
||||||
|
IReadOnlySet<ProtoId<TraitPrototype>>? selectedTraits)
|
||||||
{
|
{
|
||||||
if (condition.Conditions.Count == 0)
|
if (condition.Conditions.Count == 0)
|
||||||
return false;
|
return false;
|
||||||
@@ -167,19 +236,27 @@ public sealed partial class TraitEntry : PanelContainer
|
|||||||
// Return true if ANY child condition evaluates to true
|
// Return true if ANY child condition evaluates to true
|
||||||
foreach (var childCondition in condition.Conditions)
|
foreach (var childCondition in condition.Conditions)
|
||||||
{
|
{
|
||||||
var result = childCondition switch
|
bool result;
|
||||||
{
|
|
||||||
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
|
|
||||||
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
|
|
||||||
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
|
|
||||||
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
|
|
||||||
AnyOfCondition nestedAnyOf => CheckAnyOfCondition(nestedAnyOf, jobId, speciesId, antagPreferences), // Recursive!
|
|
||||||
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
|
|
||||||
_ => true,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Apply child's inversion
|
if (childCondition is TraitDependencyCondition depCond)
|
||||||
result ^= childCondition.Invert;
|
{
|
||||||
|
result = CheckDependencyCondition(depCond, selectedTraits);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = childCondition switch
|
||||||
|
{
|
||||||
|
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
|
||||||
|
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
|
||||||
|
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
|
||||||
|
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
|
||||||
|
IsAntagEligibleCondition antagEligibleCond => CheckAntagEligibleCondition(antagEligibleCond, antagPreferences),
|
||||||
|
AnyOfCondition nestedAnyOf => CheckAnyOfCondition(nestedAnyOf, jobId, speciesId, antagPreferences, selectedTraits),
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
result ^= childCondition.Invert;
|
||||||
|
}
|
||||||
|
|
||||||
// If any child passes, the AnyOf passes
|
// If any child passes, the AnyOf passes
|
||||||
if (result)
|
if (result)
|
||||||
@@ -190,16 +267,50 @@ public sealed partial class TraitEntry : PanelContainer
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by <see cref="TraitCategory"/> whenever the category's trait or
|
||||||
|
/// points-cap state changes. Locking only applies to <em>unselected</em>
|
||||||
|
/// entries - the selected traits are the ones filling the cap and must
|
||||||
|
/// remain interactive so the player can deselect them.
|
||||||
|
/// </summary>
|
||||||
|
public void SetLockedByCategory(bool locked)
|
||||||
|
{
|
||||||
|
if (_isLockedByCategory == locked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isLockedByCategory = locked;
|
||||||
|
UpdateDisabledState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called by <see cref="TraitCategory"/> whenever the player's global point budget
|
||||||
|
/// changes. Locks unselected entries whose cost the player can no longer afford.
|
||||||
|
/// Free (0-cost) and negative-cost traits are never locked by this.
|
||||||
|
/// </summary>
|
||||||
|
public void SetLockedByPoints(bool locked)
|
||||||
|
{
|
||||||
|
if (_isLockedByPoints == locked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_isLockedByPoints = locked;
|
||||||
|
UpdateDisabledState();
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateDisabledState()
|
private void UpdateDisabledState()
|
||||||
{
|
{
|
||||||
if (!MeetsConditions)
|
var isSelected = TraitCheckbox.Pressed;
|
||||||
|
var conditionLocked = !MeetsConditions;
|
||||||
|
var categoryLocked = _isLockedByCategory && !isSelected;
|
||||||
|
var pointsLocked = _isLockedByPoints && !isSelected;
|
||||||
|
var isDisabled = conditionLocked || categoryLocked || pointsLocked;
|
||||||
|
|
||||||
|
if (isDisabled)
|
||||||
{
|
{
|
||||||
// Hide checkbox, show lock icon
|
// Hide checkbox, show lock icon
|
||||||
TraitCheckbox.Visible = false;
|
TraitCheckbox.Visible = false;
|
||||||
LockIcon.Visible = true;
|
LockIcon.Visible = true;
|
||||||
|
|
||||||
// Deselect if conditions no longer met
|
if (isSelected && conditionLocked)
|
||||||
if (TraitCheckbox.Pressed)
|
|
||||||
{
|
{
|
||||||
_isUpdating = true;
|
_isUpdating = true;
|
||||||
TraitCheckbox.Pressed = false;
|
TraitCheckbox.Pressed = false;
|
||||||
@@ -211,14 +322,24 @@ public sealed partial class TraitEntry : PanelContainer
|
|||||||
// Add disabled styling
|
// Add disabled styling
|
||||||
AddStyleClass("TraitsEntryDisabled");
|
AddStyleClass("TraitsEntryDisabled");
|
||||||
|
|
||||||
// Update tooltip to show failed conditions
|
// Tooltip priority for conditions: condition failures > category full > insufficient points.
|
||||||
if (_failedConditionTooltips.Count > 0)
|
if (conditionLocked && _failedConditionTooltips.Count > 0)
|
||||||
{
|
{
|
||||||
var tooltipText = Loc.GetString("trait-conditions-not-met-tooltip",
|
var tooltipText = Loc.GetString("trait-conditions-not-met-tooltip",
|
||||||
("requirements", string.Join("\n", _failedConditionTooltips)));
|
("requirements", string.Join("\n", _failedConditionTooltips)));
|
||||||
|
|
||||||
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
|
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
|
||||||
}
|
}
|
||||||
|
else if (categoryLocked)
|
||||||
|
{
|
||||||
|
var tooltipText = Loc.GetString("trait-category-full-tooltip");
|
||||||
|
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
|
||||||
|
}
|
||||||
|
else if (pointsLocked)
|
||||||
|
{
|
||||||
|
var tooltipText = Loc.GetString("trait-insufficient-points-tooltip");
|
||||||
|
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -154,14 +154,18 @@ public sealed partial class TraitsTab : BoxContainer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check conflicts
|
// Check conflicts via TraitDependencyCondition
|
||||||
foreach (var conflict in trait.Conflicts)
|
var depCondition = trait.Conditions.OfType<Content.Shared._DV.Traits.Conditions.TraitDependencyCondition>().FirstOrDefault();
|
||||||
|
if (depCondition != null)
|
||||||
{
|
{
|
||||||
if (!_selectedTraits.Contains(conflict))
|
foreach (var conflict in depCondition.Conflicts)
|
||||||
continue;
|
{
|
||||||
|
if (!_selectedTraits.Contains(conflict))
|
||||||
|
continue;
|
||||||
|
|
||||||
RevertTraitToggle(traitId);
|
RevertTraitToggle(traitId);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectedTraits.Add(traitId);
|
_selectedTraits.Add(traitId);
|
||||||
@@ -176,6 +180,7 @@ public sealed partial class TraitsTab : BoxContainer
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdateGlobalStats();
|
UpdateGlobalStats();
|
||||||
|
UpdateAllConditions();
|
||||||
UpdateCategoryStats(trait.Category);
|
UpdateCategoryStats(trait.Category);
|
||||||
OnTraitsChanged?.Invoke(_selectedTraits);
|
OnTraitsChanged?.Invoke(_selectedTraits);
|
||||||
}
|
}
|
||||||
@@ -211,7 +216,7 @@ public sealed partial class TraitsTab : BoxContainer
|
|||||||
// If parent width is 0 (not laid out yet), defer until layout happens
|
// If parent width is 0 (not laid out yet), defer until layout happens
|
||||||
if (parentWidth > 0)
|
if (parentWidth > 0)
|
||||||
{
|
{
|
||||||
GlobalPointsBar.SetWidth = (int)((parentWidth - 2 ) * percentage);
|
GlobalPointsBar.SetWidth = (int)((parentWidth - 2) * percentage);
|
||||||
_awaitingLayoutUpdate = false;
|
_awaitingLayoutUpdate = false;
|
||||||
}
|
}
|
||||||
else if (!_awaitingLayoutUpdate)
|
else if (!_awaitingLayoutUpdate)
|
||||||
@@ -235,6 +240,11 @@ public sealed partial class TraitsTab : BoxContainer
|
|||||||
> 0f => "TraitsProgressBarLow",
|
> 0f => "TraitsProgressBarLow",
|
||||||
_ => "TraitsProgressBarEmpty"
|
_ => "TraitsProgressBarEmpty"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
foreach (var (_, categoryUi) in _categoryUis)
|
||||||
|
{
|
||||||
|
categoryUi.UpdateGlobalPointsLock(remainingPoints);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnProgressBarParentResized()
|
private void OnProgressBarParentResized()
|
||||||
@@ -281,7 +291,7 @@ public sealed partial class TraitsTab : BoxContainer
|
|||||||
foreach (var (_, categoryUi) in _categoryUis)
|
foreach (var (_, categoryUi) in _categoryUis)
|
||||||
{
|
{
|
||||||
// If some fork wants to use the top selected job as well, just add that to the UpdateConditions method in the editor
|
// If some fork wants to use the top selected job as well, just add that to the UpdateConditions method in the editor
|
||||||
categoryUi.UpdateConditions(null, _profile?.Species, _profile?.AntagPreferences);
|
categoryUi.UpdateConditions(null, _profile?.Species, _profile?.AntagPreferences, _selectedTraits);
|
||||||
}
|
}
|
||||||
|
|
||||||
RecalculateStats();
|
RecalculateStats();
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<Button xmlns="https://spacestation14.io">
|
||||||
|
<TextureRect Name="TextureRect"
|
||||||
|
TextureScale="0.5 0.5"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Stretch="KeepCentered"/>
|
||||||
|
</Button>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client._DV.UserInterfaces;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class IconButton : Button
|
||||||
|
{
|
||||||
|
public Texture? Icon { get => TextureRect.Texture; set => TextureRect.Texture = value; }
|
||||||
|
|
||||||
|
public IconButton()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,11 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
|||||||
|
|
||||||
private const string AnimateKey = "particle-animation";
|
private const string AnimateKey = "particle-animation";
|
||||||
|
|
||||||
private static readonly EntProtoId InteractionParticleId = "StellarInteractionParticle";
|
private static readonly Dictionary<StellarInteractionParticleType, EntProtoId> InteractionParticleIds = new ()
|
||||||
|
{
|
||||||
|
{ StellarInteractionParticleType.Use, "StellarInteractionParticleUse" },
|
||||||
|
{ StellarInteractionParticleType.Pull, "StellarInteractionParticlePull" },
|
||||||
|
};
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -38,6 +42,11 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
|||||||
if (!Exists(performer) || !Exists(target))
|
if (!Exists(performer) || !Exists(target))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (ev.Type == StellarInteractionParticleType.Pull)
|
||||||
|
{
|
||||||
|
(performer, target) = (target, performer);
|
||||||
|
}
|
||||||
|
|
||||||
var performerXform = Transform(performer);
|
var performerXform = Transform(performer);
|
||||||
var targetXform = Transform(target);
|
var targetXform = Transform(target);
|
||||||
if (performerXform.MapID == MapId.Nullspace || targetXform.MapID == MapId.Nullspace)
|
if (performerXform.MapID == MapId.Nullspace || targetXform.MapID == MapId.Nullspace)
|
||||||
@@ -47,7 +56,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var performerTargetDelta = targetXform.LocalPosition - performerXform.LocalPosition;
|
var performerTargetDelta = targetXform.LocalPosition - performerXform.LocalPosition;
|
||||||
var particle = Spawn(InteractionParticleId, performerXform.Coordinates);
|
var particle = Spawn(InteractionParticleIds[ev.Type], performerXform.Coordinates);
|
||||||
|
|
||||||
if (used is { } usedEntity && Exists(usedEntity) && TryComp<SpriteComponent>(usedEntity, out var usedSprite))
|
if (used is { } usedEntity && Exists(usedEntity) && TryComp<SpriteComponent>(usedEntity, out var usedSprite))
|
||||||
{
|
{
|
||||||
@@ -58,21 +67,27 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
var spriteColor = Comp<SpriteComponent>(particle).Color;
|
var spriteColor = Comp<SpriteComponent>(particle).Color;
|
||||||
_animation.Play(particle, GetAnimation(performerTargetDelta, spriteColor), AnimateKey);
|
var animation = ev.Type switch
|
||||||
|
{
|
||||||
|
StellarInteractionParticleType.Use => GetUseAnimation(performerTargetDelta, spriteColor),
|
||||||
|
StellarInteractionParticleType.Pull => GetPullAnimation(performerTargetDelta, spriteColor),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(ev), $"Interaction particle event has unknown particle type {ev.Type}"),
|
||||||
|
};
|
||||||
|
_animation.Play(particle, animation, AnimateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Animation GetAnimation(Vector2 endOffset, Color color)
|
private Animation GetUseAnimation(Vector2 endOffset, Color color)
|
||||||
{
|
{
|
||||||
var startRotation = _random.NextAngle(Angle.FromDegrees(-80), Angle.FromDegrees(80));
|
var startRotation = _random.NextAngle(Angle.FromDegrees(-40), Angle.FromDegrees(40));
|
||||||
var endRotation = Angle.Zero;
|
var endRotation = Angle.Zero;
|
||||||
var startScale = new Vector2(0.3f, 0.3f);
|
var startScale = new Vector2(0.3f, 0.3f);
|
||||||
var endScale = new Vector2(1f, 1f);
|
var endScale = new Vector2(1f, 1f);
|
||||||
var rotationLength = TimeSpan.FromMilliseconds(600);
|
var rotationLength = TimeSpan.FromMilliseconds(600);
|
||||||
|
|
||||||
var startOffset = new Vector2();
|
var startOffset = new Vector2();
|
||||||
var offsetLength = TimeSpan.FromMilliseconds(200);
|
var offsetLength = TimeSpan.FromMilliseconds(250);
|
||||||
|
|
||||||
var startColor = color.WithAlpha(color.A * 0.9f);
|
var startColor = color.WithAlpha(color.A * 0.7f);
|
||||||
var endColor = color.WithAlpha(0f);
|
var endColor = color.WithAlpha(0f);
|
||||||
var colorLength = rotationLength + offsetLength;
|
var colorLength = rotationLength + offsetLength;
|
||||||
|
|
||||||
@@ -89,7 +104,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
|||||||
KeyFrames =
|
KeyFrames =
|
||||||
{
|
{
|
||||||
new AnimationTrackProperty.KeyFrame(startRotation, 0f),
|
new AnimationTrackProperty.KeyFrame(startRotation, 0f),
|
||||||
new AnimationTrackProperty.KeyFrame(endRotation, (float)rotationLength.TotalSeconds, Easings.OutBounce),
|
new AnimationTrackProperty.KeyFrame(endRotation, (float)rotationLength.TotalSeconds, Easings.OutBack),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new AnimationTrackComponentProperty()
|
new AnimationTrackComponentProperty()
|
||||||
@@ -99,7 +114,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
|||||||
KeyFrames =
|
KeyFrames =
|
||||||
{
|
{
|
||||||
new AnimationTrackProperty.KeyFrame(startScale, 0f),
|
new AnimationTrackProperty.KeyFrame(startScale, 0f),
|
||||||
new AnimationTrackProperty.KeyFrame(endScale, (float)rotationLength.TotalSeconds, Easings.OutBounce),
|
new AnimationTrackProperty.KeyFrame(endScale, (float)rotationLength.TotalSeconds, Easings.OutBack),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new AnimationTrackComponentProperty()
|
new AnimationTrackComponentProperty()
|
||||||
@@ -109,7 +124,7 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
|||||||
KeyFrames =
|
KeyFrames =
|
||||||
{
|
{
|
||||||
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
|
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
|
||||||
new AnimationTrackProperty.KeyFrame(endOffset, (float)offsetLength.TotalSeconds, Easings.OutBounce),
|
new AnimationTrackProperty.KeyFrame(endOffset, (float)offsetLength.TotalSeconds, Easings.OutBack),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new AnimationTrackComponentProperty()
|
new AnimationTrackComponentProperty()
|
||||||
@@ -126,4 +141,44 @@ public sealed class StellarInteractionParticleSystem : EntitySystem
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Animation GetPullAnimation(Vector2 endOffset, Color color)
|
||||||
|
{
|
||||||
|
var rotationLength = TimeSpan.FromMilliseconds(8f * (1000f / 12f));
|
||||||
|
|
||||||
|
var startOffset = new Vector2();
|
||||||
|
var offsetLength = TimeSpan.FromMilliseconds(4f * (1000f / 12f));
|
||||||
|
|
||||||
|
var endColor = color.WithAlpha(0f);
|
||||||
|
|
||||||
|
return new Animation
|
||||||
|
{
|
||||||
|
Length = rotationLength,
|
||||||
|
|
||||||
|
AnimationTracks =
|
||||||
|
{
|
||||||
|
new AnimationTrackComponentProperty()
|
||||||
|
{
|
||||||
|
ComponentType = typeof(SpriteComponent),
|
||||||
|
Property = nameof(SpriteComponent.Offset),
|
||||||
|
KeyFrames =
|
||||||
|
{
|
||||||
|
new AnimationTrackProperty.KeyFrame(startOffset, 0f),
|
||||||
|
new AnimationTrackProperty.KeyFrame(endOffset, (float)rotationLength.TotalSeconds, Easings.InOutCirc),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
new AnimationTrackComponentProperty()
|
||||||
|
{
|
||||||
|
ComponentType = typeof(SpriteComponent),
|
||||||
|
Property = nameof(SpriteComponent.Color),
|
||||||
|
KeyFrames =
|
||||||
|
{
|
||||||
|
new AnimationTrackProperty.KeyFrame(color, 0f),
|
||||||
|
new AnimationTrackProperty.KeyFrame(color, (float)offsetLength.TotalSeconds),
|
||||||
|
new AnimationTrackProperty.KeyFrame(endColor, (float)rotationLength.TotalSeconds, Easings.InOutCirc),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ namespace Content.IntegrationTests.Tests
|
|||||||
"TimedDespawnDetailed", // DeltaV
|
"TimedDespawnDetailed", // DeltaV
|
||||||
// makes an announcement on mapInit.
|
// makes an announcement on mapInit.
|
||||||
"AnnounceOnSpawn",
|
"AnnounceOnSpawn",
|
||||||
|
"ESTimedDespawn" // DeltaV
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.That(server.CfgMan.GetCVar(CVars.NetPVS), Is.False);
|
Assert.That(server.CfgMan.GetCVar(CVars.NetPVS), Is.False);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public sealed class WizdenContentFreeze
|
|||||||
var protoMan = server.ProtoMan;
|
var protoMan = server.ProtoMan;
|
||||||
|
|
||||||
var recipesCount = protoMan.Count<FoodRecipePrototype>();
|
var recipesCount = protoMan.Count<FoodRecipePrototype>();
|
||||||
var recipesLimit = 269; // DeltaV - was 218
|
var recipesLimit = 266; // DeltaV - was 218
|
||||||
|
|
||||||
if (recipesCount > recipesLimit)
|
if (recipesCount > recipesLimit)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Robust.Shared.Audio;
|
|||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Player; // DeltaV
|
||||||
|
|
||||||
namespace Content.Server.AlertLevel;
|
namespace Content.Server.AlertLevel;
|
||||||
|
|
||||||
@@ -191,7 +192,8 @@ public sealed class AlertLevelSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
if (detail.Sound != null)
|
if (detail.Sound != null)
|
||||||
{
|
{
|
||||||
var filter = _stationSystem.GetInOwningStation(station);
|
//var filter = _stationSystem.GetInOwningStation(station); // DeltaV - Global Annoucements
|
||||||
|
var filter = Filter.Empty().AddAllPlayers(); // DeltaV - Global Annoucements
|
||||||
_audio.PlayGlobal(detail.Sound, filter, true, detail.Sound.Params);
|
_audio.PlayGlobal(detail.Sound, filter, true, detail.Sound.Params);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -202,7 +204,7 @@ public sealed class AlertLevelSystem : EntitySystem
|
|||||||
|
|
||||||
if (announce)
|
if (announce)
|
||||||
{
|
{
|
||||||
_chatSystem.DispatchStationAnnouncement(station, announcementFull, playDefaultSound: playDefault,
|
_chatSystem.DispatchGlobalAnnouncement(announcementFull, playSound: playDefault, // DeltaV - Global Annoucements
|
||||||
colorOverride: detail.Color, sender: stationName);
|
colorOverride: detail.Color, sender: stationName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
if (anomaly.Comp.CurrentBehavior is not null)
|
if (anomaly.Comp.CurrentBehavior is not null)
|
||||||
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
|
RemoveBehavior(anomaly, anomaly.Comp.CurrentBehavior.Value);
|
||||||
|
|
||||||
EndAnomaly(anomaly, spawnCore: false);
|
EndAnomaly(anomaly, spawnCore: false, forced: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnStartCollide(Entity<AnomalyComponent> anomaly, ref StartCollideEvent args)
|
private void OnStartCollide(Entity<AnomalyComponent> anomaly, ref StartCollideEvent args)
|
||||||
@@ -143,7 +143,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var multiplier = 1f;
|
var multiplier = 1f;
|
||||||
if (component.Stability > component.GrowthThreshold)
|
if (component.AlwaysGrow || component.Stability > component.GrowthThreshold) // DeltaV - Add AlwaysGrow
|
||||||
multiplier = component.GrowingPointMultiplier; //more points for unstable
|
multiplier = component.GrowingPointMultiplier; //more points for unstable
|
||||||
|
|
||||||
//penalty of up to 50% based on health
|
//penalty of up to 50% based on health
|
||||||
@@ -253,7 +253,11 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
string stateLoc;
|
string stateLoc;
|
||||||
if (anomalyComp.Stability < anomalyComp.DecayThreshold)
|
// DeltaV - Colossus Additions START
|
||||||
|
if (anomalyComp.AlwaysGrow)
|
||||||
|
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
||||||
|
// DeltaV - Colossus Additions END
|
||||||
|
else if (anomalyComp.Stability < anomalyComp.DecayThreshold) // DeltaV - Add else
|
||||||
stateLoc = Loc.GetString("anomaly-scanner-stability-low");
|
stateLoc = Loc.GetString("anomaly-scanner-stability-low");
|
||||||
else if (anomalyComp.Stability > anomalyComp.GrowthThreshold)
|
else if (anomalyComp.Stability > anomalyComp.GrowthThreshold)
|
||||||
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
stateLoc = Loc.GetString("anomaly-scanner-stability-high");
|
||||||
@@ -345,6 +349,15 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-unknown"));
|
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-unknown"));
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// DeltaV - Colossus Additions START
|
||||||
|
if (anomalyComp.AlwaysGrow)
|
||||||
|
{
|
||||||
|
msg.AddMarkupOrThrow("- " + Loc.GetString("anomaly-behavior-always-grow"));
|
||||||
|
if (anomalyComp.CurrentBehavior != null)
|
||||||
|
msg.PushNewline();
|
||||||
|
}
|
||||||
|
// DeltaV - Colossus Additions END
|
||||||
|
|
||||||
if (anomalyComp.CurrentBehavior != null)
|
if (anomalyComp.CurrentBehavior != null)
|
||||||
{
|
{
|
||||||
var behavior = _prototype.Index(anomalyComp.CurrentBehavior.Value);
|
var behavior = _prototype.Index(anomalyComp.CurrentBehavior.Value);
|
||||||
@@ -354,7 +367,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
|||||||
var mod = Math.Floor((behavior.EarnPointModifier) * 100);
|
var mod = Math.Floor((behavior.EarnPointModifier) * 100);
|
||||||
msg.AddMarkupOrThrow("- " + Loc.GetString("anomaly-behavior-point", ("mod", mod)));
|
msg.AddMarkupOrThrow("- " + Loc.GetString("anomaly-behavior-point", ("mod", mod)));
|
||||||
}
|
}
|
||||||
else
|
else if(!anomalyComp.AlwaysGrow) // DeltaV - Add condition, previously regular else
|
||||||
{
|
{
|
||||||
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-balanced"));
|
msg.AddMarkupOrThrow(Loc.GetString("anomaly-behavior-balanced"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ using Robust.Shared.Player;
|
|||||||
|
|
||||||
namespace Content.Server.Audio;
|
namespace Content.Server.Audio;
|
||||||
|
|
||||||
public sealed class ServerGlobalSoundSystem : SharedGlobalSoundSystem
|
public sealed partial class ServerGlobalSoundSystem : SharedGlobalSoundSystem // DeltaV - Made Partial
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||||
|
|||||||
@@ -184,6 +184,11 @@ public sealed class MetabolizerSystem : SharedMetabolizerSystem
|
|||||||
|
|
||||||
var rate = entry.MetabolismRate * group.MetabolismRateModifier;
|
var rate = entry.MetabolismRate * group.MetabolismRateModifier;
|
||||||
|
|
||||||
|
// BEGIN DeltaV - fix FixedPoint2 truncation flooring low rateModifiers to zero
|
||||||
|
if (rate == FixedPoint2.Zero && entry.MetabolismRate > FixedPoint2.Zero && group.MetabolismRateModifier > 0f)
|
||||||
|
rate = FixedPoint2.Epsilon;
|
||||||
|
// END DeltaV
|
||||||
|
|
||||||
// Remove $rate, as long as there's enough reagent there to actually remove that much
|
// Remove $rate, as long as there's enough reagent there to actually remove that much
|
||||||
mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
|
mostToRemove = FixedPoint2.Clamp(rate, 0, quantity);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Server.Administration.Logs;
|
|||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
using Content.Server.Administration.Systems;
|
using Content.Server.Administration.Systems;
|
||||||
using Content.Server.Discord.DiscordLink;
|
using Content.Server.Discord.DiscordLink;
|
||||||
|
using Content.Server.Ghost;
|
||||||
using Content.Server.Players.RateLimiting;
|
using Content.Server.Players.RateLimiting;
|
||||||
using Content.Server.Preferences.Managers;
|
using Content.Server.Preferences.Managers;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
@@ -15,6 +16,7 @@ using Content.Shared.Mind;
|
|||||||
using Content.Shared.Players; // DeltaV - OOC muting
|
using Content.Shared.Players; // DeltaV - OOC muting
|
||||||
using Content.Shared.Players.RateLimiting;
|
using Content.Shared.Players.RateLimiting;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Replays;
|
using Robust.Shared.Replays;
|
||||||
@@ -47,6 +49,7 @@ internal sealed partial class ChatManager : IChatManager
|
|||||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||||
[Dependency] private readonly DiscordChatLink _discordLink = default!;
|
[Dependency] private readonly DiscordChatLink _discordLink = default!;
|
||||||
[Dependency] private readonly ILogManager _logManager = default!;
|
[Dependency] private readonly ILogManager _logManager = default!;
|
||||||
|
[Dependency] private readonly ILocalizationManager _localizationManager = default!;
|
||||||
|
|
||||||
private ISawmill _sawmill = default!;
|
private ISawmill _sawmill = default!;
|
||||||
|
|
||||||
@@ -342,12 +345,35 @@ internal sealed partial class ChatManager : IChatManager
|
|||||||
|
|
||||||
#region Utility
|
#region Utility
|
||||||
|
|
||||||
|
private bool IsValidWarpDestination(EntityUid source)
|
||||||
|
{
|
||||||
|
if (!source.Valid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!_entityManager.TryGetComponent(source, out TransformComponent? transform))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return transform.MapID != MapId.Nullspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PrependFollowButtonIfAppropriate(string wrappedMessage, EntityUid source, INetChannel recipient)
|
||||||
|
{
|
||||||
|
if (IsValidWarpDestination(source) && ShouldShowFollowButton(recipient))
|
||||||
|
{
|
||||||
|
var btnText = _localizationManager.GetString("chat-manager-follow-button");
|
||||||
|
return $"[cmdlink=\"{btnText}\" command=\"{GhostFollowEntityCommand.CommandName} {_entityManager.GetNetEntity(source)}\" /] " + wrappedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrappedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
public void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
|
public void ChatMessageToOne(ChatChannel channel, string message, string wrappedMessage, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null, bool recordReplay = false, string? audioPath = null, float audioVolume = 0, NetUserId? author = null)
|
||||||
{
|
{
|
||||||
var user = author == null ? null : EnsurePlayer(author);
|
var user = author == null ? null : EnsurePlayer(author);
|
||||||
var netSource = _entityManager.GetNetEntity(source);
|
var netSource = _entityManager.GetNetEntity(source);
|
||||||
user?.AddEntity(netSource);
|
user?.AddEntity(netSource);
|
||||||
|
|
||||||
|
wrappedMessage = PrependFollowButtonIfAppropriate(wrappedMessage, source, client);
|
||||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||||
_netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client);
|
_netManager.ServerSendMessage(new MsgChatMessage() { Message = msg }, client);
|
||||||
|
|
||||||
@@ -370,8 +396,12 @@ internal sealed partial class ChatManager : IChatManager
|
|||||||
var netSource = _entityManager.GetNetEntity(source);
|
var netSource = _entityManager.GetNetEntity(source);
|
||||||
user?.AddEntity(netSource);
|
user?.AddEntity(netSource);
|
||||||
|
|
||||||
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
foreach (var client in clients)
|
||||||
_netManager.ServerSendToMany(new MsgChatMessage() { Message = msg }, clients);
|
{
|
||||||
|
var customWrapMessage = PrependFollowButtonIfAppropriate(wrappedMessage, source, client);
|
||||||
|
var msg = new ChatMessage(channel, message, customWrapMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||||
|
_netManager.ServerSendMessage(new MsgChatMessage { Message = msg }, client);
|
||||||
|
}
|
||||||
|
|
||||||
if (!recordReplay)
|
if (!recordReplay)
|
||||||
return;
|
return;
|
||||||
@@ -379,6 +409,7 @@ internal sealed partial class ChatManager : IChatManager
|
|||||||
if ((channel & ChatChannel.AdminRelated) == 0 ||
|
if ((channel & ChatChannel.AdminRelated) == 0 ||
|
||||||
_configurationManager.GetCVar(CCVars.ReplayRecordAdminChat))
|
_configurationManager.GetCVar(CCVars.ReplayRecordAdminChat))
|
||||||
{
|
{
|
||||||
|
var msg = new ChatMessage(channel, message, wrappedMessage, netSource, user?.Key, hideChat, colorOverride, audioPath, audioVolume);
|
||||||
_replay.RecordServerMessage(msg);
|
_replay.RecordServerMessage(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -439,6 +470,22 @@ internal sealed partial class ChatManager : IChatManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private bool ShouldShowFollowButton(INetChannel recipient)
|
||||||
|
{
|
||||||
|
if (!_player.TryGetSessionByChannel(recipient, out var session))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_entityManager.TrySystem(out GhostSystem? ghost))
|
||||||
|
{
|
||||||
|
if (!ghost.CanGhostWarp(session, out _))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _netConfigManager.GetClientCVar(recipient, CCVars.InterfaceChatFollowButton);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum OOCChatType : byte
|
public enum OOCChatType : byte
|
||||||
|
|||||||
@@ -93,7 +93,6 @@ public sealed class ChatSanitizationManager : IChatSanitizationManager
|
|||||||
Entry("('=", "chatsan-tearfully-smiles"),
|
Entry("('=", "chatsan-tearfully-smiles"),
|
||||||
Entry("['=", "chatsan-tearfully-smiles"),
|
Entry("['=", "chatsan-tearfully-smiles"),
|
||||||
Entry("?", "chatsan-confused"), //DeltaV
|
Entry("?", "chatsan-confused"), //DeltaV
|
||||||
Entry("!", "chatsan-surprised"), //DeltaV
|
|
||||||
Entry("…", "chatsan-sighs"), //DeltaV (note that ... doesn't work as that turns into a radio message)
|
Entry("…", "chatsan-sighs"), //DeltaV (note that ... doesn't work as that turns into a radio message)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -49,5 +49,7 @@ namespace Content.Server.Chat.Managers
|
|||||||
/// <param name="player">The player sending a chat message.</param>
|
/// <param name="player">The player sending a chat message.</param>
|
||||||
/// <returns>False if the player has violated rate limits and should be blocked from sending further messages.</returns>
|
/// <returns>False if the player has violated rate limits and should be blocked from sending further messages.</returns>
|
||||||
RateLimitStatus HandleRateLimit(ICommonSession player);
|
RateLimitStatus HandleRateLimit(ICommonSession player);
|
||||||
|
|
||||||
|
string PrependFollowButtonIfAppropriate(string wrappedMessage, EntityUid source, INetChannel recipient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Content.Server.GameTicking;
|
|||||||
using Content.Server.Speech.EntitySystems;
|
using Content.Server.Speech.EntitySystems;
|
||||||
using Content.Shared.Speech.Hushing; // DeltaV
|
using Content.Shared.Speech.Hushing; // DeltaV
|
||||||
using Content.Server.Nyanotrasen.Chat;
|
using Content.Server.Nyanotrasen.Chat;
|
||||||
|
using Content.Server.Speech.Components; // DeltaV
|
||||||
using Content.Server.Speech.Prototypes;
|
using Content.Server.Speech.Prototypes;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
using Content.Shared.ActionBlocker;
|
using Content.Shared.ActionBlocker;
|
||||||
@@ -35,6 +36,7 @@ using Robust.Shared.Prototypes;
|
|||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Replays;
|
using Robust.Shared.Replays;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
using Content.Shared._DV.Chat; // DeltaV - chat enhancements
|
||||||
|
|
||||||
namespace Content.Server.Chat.Systems;
|
namespace Content.Server.Chat.Systems;
|
||||||
|
|
||||||
@@ -215,7 +217,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
// Was there an emote in the message? If so, send it.
|
// Was there an emote in the message? If so, send it.
|
||||||
if (player != null && emoteStr != message && emoteStr != null)
|
if (player != null && emoteStr != message && emoteStr != null)
|
||||||
{
|
{
|
||||||
SendEntityEmote(source, emoteStr, range, nameOverride, ignoreActionBlocker);
|
SendEntityEmote(source, emoteStr, range, nameOverride, null, ignoreActionBlocker); // DeltaV - Had to change up for SendEntityEmote
|
||||||
}
|
}
|
||||||
|
|
||||||
// This can happen if the entire string is sanitized out.
|
// This can happen if the entire string is sanitized out.
|
||||||
@@ -252,9 +254,18 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
case InGameICChatType.Whisper:
|
case InGameICChatType.Whisper:
|
||||||
SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker);
|
SendEntityWhisper(source, message, range, null, nameOverride, hideLog, ignoreActionBlocker);
|
||||||
break;
|
break;
|
||||||
case InGameICChatType.Emote:
|
case InGameICChatType.Emote: // DeltaV - Emote now has different types of emotes.
|
||||||
SendEntityEmote(source, message, range, nameOverride, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
var type = ProcessEmoteMessage(source, message, out var modMessage);
|
||||||
break;
|
if (type == EmoteType.Audible || type == EmoteType.AudiblePossessive)
|
||||||
|
{
|
||||||
|
if (checkRadioPrefix && TryProcessRadioMessage(source, modMessage, out var outputMessage, out var channel, capitalize: false))
|
||||||
|
SendAudibleEntityEmote(source, outputMessage, range, nameOverride, channel, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||||
|
else
|
||||||
|
SendAudibleEntityEmote(source, modMessage, range, nameOverride, null, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
SendEntityEmote(source, modMessage, range, nameOverride, type, hideLog: hideLog, ignoreActionBlocker: ignoreActionBlocker);
|
||||||
|
break; // DeltaV - End
|
||||||
//Nyano - Summary: case adds the telepathic chat sending ability.
|
//Nyano - Summary: case adds the telepathic chat sending ability.
|
||||||
case InGameICChatType.Telepathic:
|
case InGameICChatType.Telepathic:
|
||||||
_nyanoChatSystem.SendTelepathicChat(source, message, range == ChatTransmitRange.HideChat);
|
_nyanoChatSystem.SendTelepathicChat(source, message, range == ChatTransmitRange.HideChat);
|
||||||
@@ -629,6 +640,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
string action,
|
string action,
|
||||||
ChatTransmitRange range,
|
ChatTransmitRange range,
|
||||||
string? nameOverride,
|
string? nameOverride,
|
||||||
|
EmoteType? emoteType, // DeltaV
|
||||||
bool hideLog = false,
|
bool hideLog = false,
|
||||||
bool checkEmote = true,
|
bool checkEmote = true,
|
||||||
bool ignoreActionBlocker = false,
|
bool ignoreActionBlocker = false,
|
||||||
@@ -642,11 +654,21 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
var ent = Identity.Entity(source, EntityManager);
|
var ent = Identity.Entity(source, EntityManager);
|
||||||
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
|
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
|
||||||
|
|
||||||
|
// Begin DeltaV
|
||||||
|
string wrappedMessage;
|
||||||
|
|
||||||
// Emotes use Identity.Name, since it doesn't actually involve your voice at all.
|
// Emotes use Identity.Name, since it doesn't actually involve your voice at all.
|
||||||
var wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
|
if (emoteType == EmoteType.Possessive) // DeltaV - Emote types now get checked.
|
||||||
("entityName", name),
|
wrappedMessage = Loc.GetString("chat-manager-entity-me-possessive-wrap-message",
|
||||||
("entity", ent),
|
("entityName", name),
|
||||||
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
("entity", ent),
|
||||||
|
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||||
|
else
|
||||||
|
wrappedMessage = Loc.GetString("chat-manager-entity-me-wrap-message",
|
||||||
|
("entityName", name),
|
||||||
|
("entity", ent),
|
||||||
|
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||||
|
// End DeltaV
|
||||||
|
|
||||||
if (checkEmote &&
|
if (checkEmote &&
|
||||||
!TryEmoteChatInput(source, action))
|
!TryEmoteChatInput(source, action))
|
||||||
@@ -660,6 +682,59 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source}: {action}");
|
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source}: {action}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeltaV - Added this to differentiate between emotes that can be heard over radio and those which can't.
|
||||||
|
protected override void SendAudibleEntityEmote(
|
||||||
|
EntityUid source,
|
||||||
|
string action,
|
||||||
|
ChatTransmitRange range,
|
||||||
|
string? nameOverride,
|
||||||
|
RadioChannelPrototype? channel,
|
||||||
|
EmoteType? emoteType,
|
||||||
|
bool hideLog = false,
|
||||||
|
bool checkEmote = true,
|
||||||
|
bool ignoreActionBlocker = false,
|
||||||
|
NetUserId? author = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (!_actionBlocker.CanSpeak(source) && !ignoreActionBlocker)
|
||||||
|
return;
|
||||||
|
|
||||||
|
EmoteType type = emoteType ?? EmoteType.Audible;
|
||||||
|
|
||||||
|
// get the entity's apparent name (if no override provided).
|
||||||
|
var ent = Identity.Entity(source, EntityManager);
|
||||||
|
string name = FormattedMessage.EscapeText(nameOverride ?? Name(ent));
|
||||||
|
|
||||||
|
string wrappedMessage;
|
||||||
|
|
||||||
|
// Audible emotes use Identity.Name, since that is the status quo. Emotes like scream doesn't currently reveal identities so this won't either. [This may be changed with feedback as you can audibly emote over radios]
|
||||||
|
if (emoteType == EmoteType.AudiblePossessive)
|
||||||
|
wrappedMessage = Loc.GetString("chat-manager-entity-me-audible-possessive-wrap-message",
|
||||||
|
("entityName", name),
|
||||||
|
("entity", ent),
|
||||||
|
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||||
|
else
|
||||||
|
wrappedMessage = Loc.GetString("chat-manager-entity-me-audible-wrap-message",
|
||||||
|
("entityName", name),
|
||||||
|
("entity", ent),
|
||||||
|
("message", FormattedMessage.RemoveMarkupOrThrow(action)));
|
||||||
|
|
||||||
|
if (checkEmote &&
|
||||||
|
!TryEmoteChatInput(source, action))
|
||||||
|
return;
|
||||||
|
|
||||||
|
SendInVoiceRange(ChatChannel.Emotes, action, wrappedMessage, source, range, author);
|
||||||
|
|
||||||
|
var ev = new EntityAudiblyEmotedEvent(source, action, channel, type);
|
||||||
|
RaiseLocalEvent(source, ref ev, true);
|
||||||
|
if (!hideLog)
|
||||||
|
if (name != Name(source))
|
||||||
|
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source} as {name}: {action}");
|
||||||
|
else
|
||||||
|
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Emote from {source}: {action}");
|
||||||
|
}
|
||||||
|
// DeltaV - End
|
||||||
|
|
||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
private void SendLOOC(EntityUid source, ICommonSession player, string message, bool hideChat)
|
private void SendLOOC(EntityUid source, ICommonSession player, string message, bool hideChat)
|
||||||
{
|
{
|
||||||
@@ -889,6 +964,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
var recipients = new Dictionary<ICommonSession, ICChatRecipientData>();
|
var recipients = new Dictionary<ICommonSession, ICChatRecipientData>();
|
||||||
var ghostHearing = GetEntityQuery<GhostHearingComponent>();
|
var ghostHearing = GetEntityQuery<GhostHearingComponent>();
|
||||||
var xforms = GetEntityQuery<TransformComponent>();
|
var xforms = GetEntityQuery<TransformComponent>();
|
||||||
|
var blockListening = GetEntityQuery<BlockListeningComponent>(); // DeltaV - block listening
|
||||||
|
|
||||||
var transformSource = xforms.GetComponent(source);
|
var transformSource = xforms.GetComponent(source);
|
||||||
var sourceMapId = transformSource.MapID;
|
var sourceMapId = transformSource.MapID;
|
||||||
@@ -904,6 +980,11 @@ public sealed partial class ChatSystem : SharedChatSystem
|
|||||||
if (transformEntity.MapID != sourceMapId)
|
if (transformEntity.MapID != sourceMapId)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Begin DeltaV - block listening
|
||||||
|
if (blockListening.HasComponent(playerEntity))
|
||||||
|
continue;
|
||||||
|
// End DeltaV - block listening
|
||||||
|
|
||||||
var observer = ghostHearing.HasComponent(playerEntity);
|
var observer = ghostHearing.HasComponent(playerEntity);
|
||||||
|
|
||||||
// even if they are a ghost hearer, in some situations we still need the range
|
// even if they are a ghost hearer, in some situations we still need the range
|
||||||
@@ -971,4 +1052,4 @@ public sealed class CheckTargetedSpeechEvent : EntityEventArgs
|
|||||||
{
|
{
|
||||||
public List<EntityUid> Targets = new List<EntityUid>();
|
public List<EntityUid> Targets = new List<EntityUid>();
|
||||||
}
|
}
|
||||||
// END Mono
|
// END Mono
|
||||||
|
|||||||
@@ -382,11 +382,6 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||||||
|
|
||||||
var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
|
var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
|
||||||
|
|
||||||
// camera shake
|
|
||||||
// ES START
|
|
||||||
// CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
|
|
||||||
// ES END
|
|
||||||
|
|
||||||
//For whatever bloody reason, sound system requires ENTITY coordinates.
|
//For whatever bloody reason, sound system requires ENTITY coordinates.
|
||||||
var mapEntityCoords = _transformSystem.ToCoordinates(_map.GetMap(pos.MapId), pos);
|
var mapEntityCoords = _transformSystem.ToCoordinates(_map.GetMap(pos.MapId), pos);
|
||||||
|
|
||||||
@@ -416,12 +411,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||||||
? queued.Proto.SmallSoundFar
|
? queued.Proto.SmallSoundFar
|
||||||
: queued.Proto.SoundFar;
|
: queued.Proto.SoundFar;
|
||||||
|
|
||||||
// ES START
|
CameraShake(iterationIntensity, pos, queued); // Starlight
|
||||||
var farTranslationShake = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
|
|
||||||
? new ESScreenshakeParameters() { Trauma = 0.4f, DecayRate = 0.2f, Frequency = 0.014f }
|
|
||||||
: new ESScreenshakeParameters() { Trauma = 0.6f, DecayRate = 0.05f, Frequency = 0.014f };
|
|
||||||
_shake.Screenshake(filter, farTranslationShake, null);
|
|
||||||
// ES END
|
|
||||||
|
|
||||||
_audio.PlayGlobal(farSound, farFilter, true, farSound.Params);
|
_audio.PlayGlobal(farSound, farFilter, true, farSound.Params);
|
||||||
|
|
||||||
@@ -444,8 +434,13 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||||||
_damageableSystem);
|
_damageableSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CameraShake(float range, MapCoordinates epicenter, float totalIntensity)
|
private void CameraShake(List<float> rangeList, MapCoordinates epicenter, QueuedExplosion queued) // Starlight - replace range with rangeList, totalIntensity with queued
|
||||||
{
|
{
|
||||||
|
// Starlight BEGIN
|
||||||
|
var range = rangeList.Count * 4f;
|
||||||
|
var totalIntensity = queued.TotalIntensity * 10f;
|
||||||
|
// Starlight END
|
||||||
|
|
||||||
var players = Filter.Empty();
|
var players = Filter.Empty();
|
||||||
players.AddInRange(epicenter, range, _playerManager, EntityManager);
|
players.AddInRange(epicenter, range, _playerManager, EntityManager);
|
||||||
|
|
||||||
@@ -463,7 +458,40 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||||||
var distance = delta.Length();
|
var distance = delta.Length();
|
||||||
var effect = 5 * MathF.Pow(totalIntensity, 0.5f) * (1 - distance / range);
|
var effect = 5 * MathF.Pow(totalIntensity, 0.5f) * (1 - distance / range);
|
||||||
if (effect > 0.01f)
|
if (effect > 0.01f)
|
||||||
_recoilSystem.KickCamera(uid, -delta.Normalized() * effect);
|
{
|
||||||
|
// DeltaV - Camera kick falloff START
|
||||||
|
// _recoilSystem.KickCamera(uid, -delta.Normalized() * effect);
|
||||||
|
|
||||||
|
// Exponential decay: N(t) = N₀ ⋅ e^(-λ ⋅ t)
|
||||||
|
// In this case, N = effect and we aren't decaying over time but with increasing distance from the epicenter,
|
||||||
|
// so t = distance / range. Higher values of λ lead to faster decay, lower values to slower decay.
|
||||||
|
//
|
||||||
|
// severity is a fixed factor used to decrease the overall severity of the camera kick,
|
||||||
|
// as the values were so high by default that decay was only becoming really noticeable near range.
|
||||||
|
//
|
||||||
|
// these changes are made here instead of directly assigning to the effect variable
|
||||||
|
// above to maintain the same overall effect range as before.
|
||||||
|
const float severity = 0.033f;
|
||||||
|
const float lambda = 4f;
|
||||||
|
_recoilSystem.KickCamera(uid, -delta.Normalized() * effect * severity * MathF.Exp(-lambda * (distance / range)));
|
||||||
|
// DeltaV END
|
||||||
|
|
||||||
|
// Starlight START
|
||||||
|
var shakeParams = rangeList.Count < queued.Proto.SmallSoundIterationThreshold
|
||||||
|
? new ESScreenshakeParameters() { Trauma = 0.4f, DecayRate = 0.2f, Frequency = 0.014f }
|
||||||
|
: new ESScreenshakeParameters() { Trauma = 0.6f, DecayRate = 0.05f, Frequency = 0.014f };
|
||||||
|
|
||||||
|
// DeltaV - Screenshake falloff START
|
||||||
|
// Linear falloff with increasing distance from epicenter,
|
||||||
|
// capped at certain values to avoid diminishing the effect completely near range.
|
||||||
|
shakeParams.DecayRate *= MathF.Min(1 + distance / range, 1.75f);
|
||||||
|
shakeParams.Frequency *= MathF.Max(1 - distance / range, 0.33f);
|
||||||
|
shakeParams.Trauma *= MathF.Max(1 - distance / range, 0.33f);
|
||||||
|
// DeltaV END
|
||||||
|
|
||||||
|
_shake.Screenshake(players, shakeParams, null);
|
||||||
|
// Starlight END
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Content.Server.DeviceNetwork.Systems;
|
|||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
using Content.Server.Tools;
|
using Content.Server.Tools;
|
||||||
|
using Content.Shared._DV.Fax; // DeltaV - fax
|
||||||
using Content.Shared._DV.Pager; // DeltaV - pagers
|
using Content.Shared._DV.Pager; // DeltaV - pagers
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Shared.Containers.ItemSlots;
|
using Content.Shared.Containers.ItemSlots;
|
||||||
@@ -12,6 +13,7 @@ using Content.Shared.Database;
|
|||||||
using Content.Shared.DeviceNetwork;
|
using Content.Shared.DeviceNetwork;
|
||||||
using Content.Shared.DeviceNetwork.Components;
|
using Content.Shared.DeviceNetwork.Components;
|
||||||
using Content.Shared.DeviceNetwork.Events;
|
using Content.Shared.DeviceNetwork.Events;
|
||||||
|
using Content.Shared.DeviceNetwork.Systems; // DeltaV - map init ordering
|
||||||
using Content.Shared.Emag.Systems;
|
using Content.Shared.Emag.Systems;
|
||||||
using Content.Shared.Fax;
|
using Content.Shared.Fax;
|
||||||
using Content.Shared.Fax.Components;
|
using Content.Shared.Fax.Components;
|
||||||
@@ -64,7 +66,7 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
SubscribeLocalEvent<FaxMachineComponent, ComponentInit>(OnComponentInit);
|
SubscribeLocalEvent<FaxMachineComponent, ComponentInit>(OnComponentInit);
|
||||||
SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit);
|
SubscribeLocalEvent<FaxMachineComponent, MapInitEvent>(OnMapInit, after: [typeof(SharedDeviceNetworkSystem)]); // DeltaV - map init order, we need address assigned first
|
||||||
SubscribeLocalEvent<FaxMachineComponent, ComponentRemove>(OnComponentRemove);
|
SubscribeLocalEvent<FaxMachineComponent, ComponentRemove>(OnComponentRemove);
|
||||||
|
|
||||||
SubscribeLocalEvent<FaxMachineComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
|
SubscribeLocalEvent<FaxMachineComponent, EntInsertedIntoContainerMessage>(OnItemSlotChanged);
|
||||||
@@ -89,10 +91,10 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
base.Update(frameTime);
|
base.Update(frameTime);
|
||||||
|
|
||||||
var query = EntityQueryEnumerator<FaxMachineComponent, ApcPowerReceiverComponent>();
|
var query = EntityQueryEnumerator<FaxMachineComponent>();
|
||||||
while (query.MoveNext(out var uid, out var fax, out var receiver))
|
while (query.MoveNext(out var uid, out var fax)) // DeltaV - faxes aren't necessarily powered
|
||||||
{
|
{
|
||||||
if (!receiver.Powered)
|
if (TryComp<ApcPowerReceiverComponent>(uid, out var receiver) && !receiver.Powered) // DeltaV - faxes aren't necessarily powered
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ProcessPrintingAnimation(uid, frameTime, fax);
|
ProcessPrintingAnimation(uid, frameTime, fax);
|
||||||
@@ -301,6 +303,11 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
if (!args.Data.TryGetValue(FaxConstants.FaxPaperNameData, out string? name) ||
|
if (!args.Data.TryGetValue(FaxConstants.FaxPaperNameData, out string? name) ||
|
||||||
!args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content))
|
!args.Data.TryGetValue(FaxConstants.FaxPaperContentData, out string? content))
|
||||||
return;
|
return;
|
||||||
|
// Begin DeltaV - we removed the power requirement from device network but we still don't want
|
||||||
|
// unpowered faxes to happen
|
||||||
|
if (TryComp<ApcPowerReceiverComponent>(uid, out var receiver) && !receiver.Powered)
|
||||||
|
return;
|
||||||
|
// End DeltaV
|
||||||
|
|
||||||
args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label);
|
args.Data.TryGetValue(FaxConstants.FaxPaperLabelData, out string? label);
|
||||||
args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
|
args.Data.TryGetValue(FaxConstants.FaxPaperStampStateData, out string? stampState);
|
||||||
@@ -386,7 +393,7 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
var canCopy = isPaperInserted &&
|
var canCopy = isPaperInserted &&
|
||||||
component.SendTimeoutRemaining <= 0 &&
|
component.SendTimeoutRemaining <= 0 &&
|
||||||
component.InsertingTimeRemaining <= 0;
|
component.InsertingTimeRemaining <= 0;
|
||||||
var state = new FaxUiState(component.FaxName, component.KnownFaxes, canSend, canCopy, isPaperInserted, component.DestinationFaxAddress);
|
var state = new FaxUiState(component.FaxName, component.KnownFaxes, canSend, canCopy, isPaperInserted, component.DestinationFaxAddress, component.SendTimeoutRemaining <= 0); // DeltaV - AI fax
|
||||||
_userInterface.SetUiState(uid, FaxUiKey.Key, state);
|
_userInterface.SetUiState(uid, FaxUiKey.Key, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,7 +462,7 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
/// Copies the paper in the fax. A timeout is set after copying,
|
/// Copies the paper in the fax. A timeout is set after copying,
|
||||||
/// which is shared by the send button.
|
/// which is shared by the send button.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Copy(EntityUid uid, FaxMachineComponent? component, FaxCopyMessage args)
|
public void Copy(EntityUid uid, FaxMachineComponent? component, BoundUserInterfaceMessage args, EntityUid? item = null) // DeltaV - station AI fax
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
if (!Resolve(uid, ref component))
|
||||||
return;
|
return;
|
||||||
@@ -463,7 +470,7 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
if (component.SendTimeoutRemaining > 0)
|
if (component.SendTimeoutRemaining > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var sendEntity = component.PaperSlot.Item;
|
var sendEntity = item ?? component.PaperSlot.Item; // DeltaV - station AI fax
|
||||||
if (sendEntity == null)
|
if (sendEntity == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -505,7 +512,7 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
/// Sends message to addressee if paper is set and a known fax is selected
|
/// Sends message to addressee if paper is set and a known fax is selected
|
||||||
/// A timeout is set after sending, which is shared by the copy button.
|
/// A timeout is set after sending, which is shared by the copy button.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Send(EntityUid uid, FaxMachineComponent? component, FaxSendMessage args)
|
public void Send(EntityUid uid, FaxMachineComponent? component, BoundUserInterfaceMessage args, EntityUid? item = null) // DeltaV - station AI fax
|
||||||
{
|
{
|
||||||
if (!Resolve(uid, ref component))
|
if (!Resolve(uid, ref component))
|
||||||
return;
|
return;
|
||||||
@@ -513,7 +520,7 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
if (component.SendTimeoutRemaining > 0)
|
if (component.SendTimeoutRemaining > 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var sendEntity = component.PaperSlot.Item;
|
var sendEntity = item ?? component.PaperSlot.Item; // DeltaV - station AI fax
|
||||||
if (sendEntity == null)
|
if (sendEntity == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -629,6 +636,10 @@ public sealed class FaxSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
_adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid):tool} printed {ToPrettyString(printed):subject}: {printout.Content}");
|
_adminLogger.Add(LogType.Action, LogImpact.Low, $"\"{component.FaxName}\" {ToPrettyString(uid):tool} printed {ToPrettyString(printed):subject}: {printout.Content}");
|
||||||
|
// Begin DeltaV - notify on fax printing
|
||||||
|
var evt = new FaxPrintedEvent(printed);
|
||||||
|
RaiseLocalEvent(uid, ref evt);
|
||||||
|
// End DeltaV - notify on fax printing
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NotifyAdmins(string faxName)
|
private void NotifyAdmins(string faxName)
|
||||||
|
|||||||
@@ -35,17 +35,17 @@ public sealed partial class PuddleSystem
|
|||||||
if (!entity.Comp.SpillWhenThrown || Openable.IsClosed(entity.Owner))
|
if (!entity.Comp.SpillWhenThrown || Openable.IsClosed(entity.Owner))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// DeltaV - Beer Goggles Safe Throw
|
||||||
|
if ( args.User is { } user && _safeSolutionThrower.GetSafeThrow(user))
|
||||||
|
{
|
||||||
|
_physics.SetAngularVelocity(entity, 0);
|
||||||
|
Transform(entity).LocalRotation = Angle.Zero;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// END DeltaV
|
||||||
|
|
||||||
if (TrySplashSpillAt(entity.Owner, Transform(entity).Coordinates, out _, out var solution) && args.User != null)
|
if (TrySplashSpillAt(entity.Owner, Transform(entity).Coordinates, out _, out var solution) && args.User != null)
|
||||||
{
|
{
|
||||||
// DeltaV - Beer Goggles Safe Throw
|
|
||||||
if (_safeSolutionThrower.GetSafeThrow(args.User.Value))
|
|
||||||
{
|
|
||||||
_physics.SetAngularVelocity(entity, 0);
|
|
||||||
Transform(entity).LocalRotation = Angle.Zero;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// END DeltaV
|
|
||||||
|
|
||||||
AdminLogger.Add(LogType.Landed,
|
AdminLogger.Add(LogType.Landed,
|
||||||
$"{ToPrettyString(entity.Owner):entity} spilled a solution {SharedSolutionContainerSystem.ToPrettyString(solution):solution} on landing");
|
$"{ToPrettyString(entity.Owner):entity} spilled a solution {SharedSolutionContainerSystem.ToPrettyString(solution):solution} on landing");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Server.RoundEnd; // DeltaV
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -25,4 +26,29 @@ public sealed partial class ZombieRuleComponent : Component
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float ZombieShuttleCallPercentage = 0.7f;
|
public float ZombieShuttleCallPercentage = 0.7f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - The behavior of the round if all zombies are defeated.
|
||||||
|
/// </summary>
|
||||||
|
public RoundEndBehavior ZombieRoundEndBehavior = RoundEndBehavior.ShuttleCall;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - The amount of time before the evac shuttle will arrive if the ZombieRoundEndBehavior is set to ShuttleCall.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - The announcement sender when calling the shuttle.
|
||||||
|
/// </summary>
|
||||||
|
public LocId RoundEndTextSender = "comms-console-announcement-title-centcom";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - The announcement text when calling a shuttle.
|
||||||
|
/// </summary>
|
||||||
|
public LocId RoundEndTextShuttleCall = "zombie-no-more-threat-announcement-shuttle-call";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - The announcement text when a shuttle is already called.
|
||||||
|
/// </summary>
|
||||||
|
public LocId RoundEndTextAnnouncement = "zombie-no-more-threat-announcement";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ using System.Linq;
|
|||||||
using Content.Shared.Station.Components;
|
using Content.Shared.Station.Components;
|
||||||
using Content.Shared.Store.Components;
|
using Content.Shared.Store.Components;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Content.Shared.Cuffs.Components; // DeltaV
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
@@ -500,12 +501,23 @@ public sealed class NukeopsRuleSystem : GameRuleSystem<NukeopsRuleComponent>
|
|||||||
// Check if there are nuke operatives still alive on the same map as the shuttle,
|
// Check if there are nuke operatives still alive on the same map as the shuttle,
|
||||||
// or on the same map as the station.
|
// or on the same map as the station.
|
||||||
// If there are, the round can continue.
|
// If there are, the round can continue.
|
||||||
var operatives = EntityQuery<NukeOperativeComponent, MobStateComponent, TransformComponent>(true);
|
|
||||||
|
// BEGIN DeltaV - End Rounds Better
|
||||||
|
// We need to use a EntityQueryEnumerator instead of a EntityQuery, because we need the uid to check for cuffs
|
||||||
|
var operatives = new List<Entity<NukeOperativeComponent, MobStateComponent, TransformComponent>>();
|
||||||
|
var operativesEnumerator = EntityQueryEnumerator<NukeOperativeComponent, MobStateComponent, TransformComponent>();
|
||||||
|
while (operativesEnumerator.MoveNext(out var uid, out var nukeOp, out var mobState, out var transform))
|
||||||
|
{
|
||||||
|
operatives.Add((uid, nukeOp, mobState, transform));
|
||||||
|
}
|
||||||
|
|
||||||
var operativesAlive = operatives
|
var operativesAlive = operatives
|
||||||
.Where(op =>
|
.Where(op =>
|
||||||
op.Item3.MapID == shuttleMapId
|
op.Comp3.MapID == shuttleMapId
|
||||||
|| op.Item3.MapID == targetStationMap)
|
|| op.Comp3.MapID == targetStationMap)
|
||||||
.Any(op => op.Item2.CurrentState == MobState.Alive && op.Item1.Running);
|
.Any(op => op.Comp2.CurrentState == MobState.Alive && op.Comp1.Running &&
|
||||||
|
!TryComp<CuffableComponent>(op, out _)); // in the case the crew keeps one alive
|
||||||
|
// END DeltaV
|
||||||
|
|
||||||
if (operativesAlive)
|
if (operativesAlive)
|
||||||
return; // There are living operatives than can access the shuttle, or are still on the station's map.
|
return; // There are living operatives than can access the shuttle, or are still on the station's map.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using Content.Server.Chat.Systems;
|
|||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Server.Roles;
|
using Content.Server.Roles;
|
||||||
using Content.Server.RoundEnd;
|
using Content.Server.RoundEnd; // DeltaV
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Station.Systems;
|
||||||
using Content.Server.Zombies;
|
using Content.Server.Zombies;
|
||||||
using Content.Shared.GameTicking.Components;
|
using Content.Shared.GameTicking.Components;
|
||||||
@@ -114,11 +114,31 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void CheckRoundEnd(ZombieRuleComponent zombieRuleComponent)
|
private void CheckRoundEnd(ZombieRuleComponent zombieRuleComponent)
|
||||||
{
|
{
|
||||||
|
// BEGIN DeltaV - End Rounds Better
|
||||||
|
// Zombies have failed, time to go home
|
||||||
|
if (zombieRuleComponent.ZombieRoundEndBehavior == RoundEndBehavior.Nothing)
|
||||||
|
return;
|
||||||
|
// END DeltaV
|
||||||
|
|
||||||
var healthy = GetHealthyHumans();
|
var healthy = GetHealthyHumans();
|
||||||
if (healthy.Count == 1) // Only one human left. spooky
|
if (healthy.Count == 1) // Only one human left. spooky
|
||||||
_popup.PopupEntity(Loc.GetString("zombie-alone"), healthy[0], healthy[0]);
|
_popup.PopupEntity(Loc.GetString("zombie-alone"), healthy[0], healthy[0]);
|
||||||
|
|
||||||
if (GetInfectedFraction(false) > zombieRuleComponent.ZombieShuttleCallPercentage && !_roundEnd.IsRoundEndRequested())
|
|
||||||
|
// BEGIN DeltaV - End Rounds Better
|
||||||
|
var infectedPercent = GetInfectedFraction(false);
|
||||||
|
if (Math.Round(infectedPercent, 0) == 0) // All zombies defeated
|
||||||
|
{
|
||||||
|
_roundEnd.DoRoundEndBehavior(zombieRuleComponent.ZombieRoundEndBehavior,
|
||||||
|
zombieRuleComponent.EvacShuttleTime,
|
||||||
|
zombieRuleComponent.RoundEndTextSender,
|
||||||
|
zombieRuleComponent.RoundEndTextShuttleCall,
|
||||||
|
zombieRuleComponent.RoundEndTextAnnouncement);
|
||||||
|
zombieRuleComponent.ZombieRoundEndBehavior = RoundEndBehavior.Nothing; // stop this check in the future
|
||||||
|
}
|
||||||
|
// END DeltaV
|
||||||
|
|
||||||
|
if (infectedPercent > zombieRuleComponent.ZombieShuttleCallPercentage && !_roundEnd.IsRoundEndRequested()) // DeltaV - Move GetInfectedFraction to var
|
||||||
{
|
{
|
||||||
foreach (var station in _station.GetStations())
|
foreach (var station in _station.GetStations())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Content.Shared.Administration;
|
||||||
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
|
namespace Content.Server.Ghost;
|
||||||
|
|
||||||
|
[AnyCommand]
|
||||||
|
internal sealed partial class GhostFollowEntityCommand : LocalizedEntityCommands
|
||||||
|
{
|
||||||
|
public const string CommandName = "ghost_follow_entity";
|
||||||
|
|
||||||
|
[Dependency] private GhostSystem _ghost = null!;
|
||||||
|
|
||||||
|
public override string Command => CommandName;
|
||||||
|
|
||||||
|
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length != 1 || shell.Player is not { } player)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var target = args[0];
|
||||||
|
if (!NetEntity.TryParse(target, out var targetEnt))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_ghost.GhostWarpRequest(player, targetEnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -299,10 +299,22 @@ namespace Content.Server.Ghost
|
|||||||
|
|
||||||
#region Warp
|
#region Warp
|
||||||
|
|
||||||
|
public bool CanGhostWarp(ICommonSession session, out EntityUid entity)
|
||||||
|
{
|
||||||
|
if (session.AttachedEntity is not { Valid: true } sessionEntity
|
||||||
|
|| !_ghostQuery.HasComp(sessionEntity))
|
||||||
|
{
|
||||||
|
entity = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity = sessionEntity;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnGhostWarpsRequest(GhostWarpsRequestEvent msg, EntitySessionEventArgs args)
|
private void OnGhostWarpsRequest(GhostWarpsRequestEvent msg, EntitySessionEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.SenderSession.AttachedEntity is not {Valid: true} entity
|
if (!CanGhostWarp(args.SenderSession, out var entity))
|
||||||
|| !_ghostQuery.HasComp(entity))
|
|
||||||
{
|
{
|
||||||
Log.Warning($"User {args.SenderSession.Name} sent a {nameof(GhostWarpsRequestEvent)} without being a ghost.");
|
Log.Warning($"User {args.SenderSession.Name} sent a {nameof(GhostWarpsRequestEvent)} without being a ghost.");
|
||||||
return;
|
return;
|
||||||
@@ -312,30 +324,33 @@ namespace Content.Server.Ghost
|
|||||||
RaiseNetworkEvent(response, args.SenderSession.Channel);
|
RaiseNetworkEvent(response, args.SenderSession.Channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void GhostWarpRequest(ICommonSession player, NetEntity target)
|
||||||
|
{
|
||||||
|
if (!CanGhostWarp(player, out var attached))
|
||||||
|
{
|
||||||
|
Log.Warning($"User {player.Name} tried to warp to {target} without being a ghost.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var realTarget = GetEntity(target);
|
||||||
|
|
||||||
|
if (!Exists(realTarget))
|
||||||
|
{
|
||||||
|
Log.Warning($"User {player.Name} tried to warp to an invalid entity id: {target}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WarpTo(attached, realTarget);
|
||||||
|
}
|
||||||
|
|
||||||
private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, EntitySessionEventArgs args)
|
private void OnGhostWarpToTargetRequest(GhostWarpToTargetRequestEvent msg, EntitySessionEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.SenderSession.AttachedEntity is not {Valid: true} attached
|
GhostWarpRequest(args.SenderSession, msg.Target);
|
||||||
|| !_ghostQuery.HasComp(attached))
|
|
||||||
{
|
|
||||||
Log.Warning($"User {args.SenderSession.Name} tried to warp to {msg.Target} without being a ghost.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var target = GetEntity(msg.Target);
|
|
||||||
|
|
||||||
if (!Exists(target))
|
|
||||||
{
|
|
||||||
Log.Warning($"User {args.SenderSession.Name} tried to warp to an invalid entity id: {msg.Target}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WarpTo(attached, target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnGhostnadoRequest(GhostnadoRequestEvent msg, EntitySessionEventArgs args)
|
private void OnGhostnadoRequest(GhostnadoRequestEvent msg, EntitySessionEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.SenderSession.AttachedEntity is not {} uid
|
if (CanGhostWarp(args.SenderSession, out var uid))
|
||||||
|| !_ghostQuery.HasComp(uid))
|
|
||||||
{
|
{
|
||||||
Log.Warning($"User {args.SenderSession.Name} tried to ghostnado without being a ghost.");
|
Log.Warning($"User {args.SenderSession.Name} tried to ghostnado without being a ghost.");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ namespace Content.Server.Medical.BiomassReclaimer
|
|||||||
_solution.ResolveSolution(toProcess, stream.BloodSolutionName, ref stream.BloodSolution, out var solution))
|
_solution.ResolveSolution(toProcess, stream.BloodSolutionName, ref stream.BloodSolution, out var solution))
|
||||||
{
|
{
|
||||||
component.BloodReagents = solution.Clone();
|
component.BloodReagents = solution.Clone();
|
||||||
component.BloodReagents.ScaleSolution(50 / component.BloodReagents.Volume);
|
//component.BloodReagents.ScaleSolution(50 / component.BloodReagents.Volume); // Delta V - This doesn't need to be here. It just always makes the solution ~50u but also might divide by 0. Just use the current blood level so more blood = more mess.
|
||||||
}
|
}
|
||||||
if (TryComp<ButcherableComponent>(toProcess, out var butcherableComponent))
|
if (TryComp<ButcherableComponent>(toProcess, out var butcherableComponent))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -327,15 +327,15 @@ public sealed class NukeSystem : EntitySystem
|
|||||||
// should play
|
// should play
|
||||||
if (nuke.RemainingTime <= _nukeSongLength + nuke.AlertSoundTime + NukeSongBuffer && !nuke.PlayedNukeSong && !ResolvedSoundSpecifier.IsNullOrEmpty(_selectedNukeSong))
|
if (nuke.RemainingTime <= _nukeSongLength + nuke.AlertSoundTime + NukeSongBuffer && !nuke.PlayedNukeSong && !ResolvedSoundSpecifier.IsNullOrEmpty(_selectedNukeSong))
|
||||||
{
|
{
|
||||||
_sound.DispatchStationEventMusic(uid, _selectedNukeSong, StationEventMusicType.Nuke);
|
_sound.DispatchGlobalEventMusic(_selectedNukeSong, StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
|
||||||
nuke.PlayedNukeSong = true;
|
nuke.PlayedNukeSong = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// play alert sound if time is running out
|
// play alert sound if time is running out
|
||||||
if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound)
|
if (nuke.RemainingTime <= nuke.AlertSoundTime && !nuke.PlayedAlertSound)
|
||||||
{
|
{
|
||||||
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(nuke.AlertSound), new AudioParams{Volume = -5f});
|
_sound.PlayGlobal(_audio.ResolveSound(nuke.AlertSound), new AudioParams { Volume = -5f }); // DeltaV - Global Nuke SFX
|
||||||
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
|
_sound.StopGlobalEventMusic(StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
|
||||||
nuke.PlayedAlertSound = true;
|
nuke.PlayedAlertSound = true;
|
||||||
UpdateAppearance(uid, nuke);
|
UpdateAppearance(uid, nuke);
|
||||||
}
|
}
|
||||||
@@ -504,9 +504,9 @@ public sealed class NukeSystem : EntitySystem
|
|||||||
("time", (int) component.RemainingTime),
|
("time", (int) component.RemainingTime),
|
||||||
("location", FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((uid, nukeXform)))));
|
("location", FormattedMessage.RemoveMarkupOrThrow(_navMap.GetNearestBeaconString((uid, nukeXform)))));
|
||||||
var sender = Loc.GetString("nuke-component-announcement-sender");
|
var sender = Loc.GetString("nuke-component-announcement-sender");
|
||||||
_chatSystem.DispatchStationAnnouncement(stationUid ?? uid, announcement, sender, false, null, Color.Red);
|
_chatSystem.DispatchGlobalAnnouncement(announcement, sender, false, null, Color.Red); // DeltaV - Global Nuke Annoucement
|
||||||
|
|
||||||
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.ArmSound));
|
_sound.PlayGlobal(_audio.ResolveSound(component.ArmSound)); // DeltaV - Global Nuke Music
|
||||||
_nukeSongLength = (float) _audio.GetAudioLength(_selectedNukeSong).TotalSeconds;
|
_nukeSongLength = (float) _audio.GetAudioLength(_selectedNukeSong).TotalSeconds;
|
||||||
|
|
||||||
// turn on the spinny light
|
// turn on the spinny light
|
||||||
@@ -544,11 +544,11 @@ public sealed class NukeSystem : EntitySystem
|
|||||||
// warn a crew
|
// warn a crew
|
||||||
var announcement = Loc.GetString("nuke-component-announcement-unarmed");
|
var announcement = Loc.GetString("nuke-component-announcement-unarmed");
|
||||||
var sender = Loc.GetString("nuke-component-announcement-sender");
|
var sender = Loc.GetString("nuke-component-announcement-sender");
|
||||||
_chatSystem.DispatchStationAnnouncement(uid, announcement, sender, false);
|
_chatSystem.DispatchGlobalAnnouncement(announcement, sender, false); // DeltaV - Global Nuke Announcement
|
||||||
|
|
||||||
component.PlayedNukeSong = false;
|
component.PlayedNukeSong = false;
|
||||||
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(component.DisarmSound));
|
_sound.PlayGlobal(_audio.ResolveSound(component.DisarmSound)); // DeltaV - Global Nuke SFX
|
||||||
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
|
_sound.StopGlobalEventMusic(StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
|
||||||
|
|
||||||
// reset nuke remaining time to either itself or the minimum time, whichever is higher
|
// reset nuke remaining time to either itself or the minimum time, whichever is higher
|
||||||
component.RemainingTime = Math.Max(component.RemainingTime, component.MinimumTime);
|
component.RemainingTime = Math.Max(component.RemainingTime, component.MinimumTime);
|
||||||
@@ -610,7 +610,7 @@ public sealed class NukeSystem : EntitySystem
|
|||||||
OwningStation = transform.GridUid,
|
OwningStation = transform.GridUid,
|
||||||
});
|
});
|
||||||
|
|
||||||
_sound.StopStationEventMusic(uid, StationEventMusicType.Nuke);
|
_sound.StopGlobalEventMusic(StationEventMusicType.Nuke); // DeltaV - Global Nuke Music
|
||||||
Del(uid);
|
Del(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Content.Shared.Radio.Components;
|
|||||||
using Content.Shared.Radio.EntitySystems;
|
using Content.Shared.Radio.EntitySystems;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
|
using Content.Shared._DV.Chat;
|
||||||
|
|
||||||
namespace Content.Server.Radio.EntitySystems;
|
namespace Content.Server.Radio.EntitySystems;
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
|
|||||||
SubscribeLocalEvent<HeadsetComponent, EncryptionChannelsChangedEvent>(OnKeysChanged);
|
SubscribeLocalEvent<HeadsetComponent, EncryptionChannelsChangedEvent>(OnKeysChanged);
|
||||||
|
|
||||||
SubscribeLocalEvent<WearingHeadsetComponent, EntitySpokeEvent>(OnSpeak);
|
SubscribeLocalEvent<WearingHeadsetComponent, EntitySpokeEvent>(OnSpeak);
|
||||||
|
SubscribeLocalEvent<WearingHeadsetComponent, EntityAudiblyEmotedEvent>(OnAudibleEmote); // DeltaV
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnKeysChanged(EntityUid uid, HeadsetComponent component, EncryptionChannelsChangedEvent args)
|
private void OnKeysChanged(EntityUid uid, HeadsetComponent component, EncryptionChannelsChangedEvent args)
|
||||||
@@ -48,22 +50,43 @@ public sealed class HeadsetSystem : SharedHeadsetSystem
|
|||||||
EnsureComp<ActiveRadioComponent>(uid).Channels = new(keyHolder.Channels);
|
EnsureComp<ActiveRadioComponent>(uid).Channels = new(keyHolder.Channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args)
|
// DeltaV
|
||||||
|
// WARNING - Be very careful when modifying this method.
|
||||||
|
// The implementation right now has null forgiving operatiors in OnSpeak() and OnAudibleEmote() since this only returns true if channel is not null
|
||||||
|
private bool CheckRadioCapable(EntityUid uid, WearingHeadsetComponent headset, RadioChannelPrototype? channel)
|
||||||
{
|
{
|
||||||
if (args.Channel != null
|
if (channel != null
|
||||||
&& TryComp(component.Headset, out EncryptionKeyHolderComponent? keys)
|
&& TryComp(headset.Headset, out EncryptionKeyHolderComponent? keys)
|
||||||
&& keys.Channels.Contains(args.Channel.ID))
|
&& keys.Channels.Contains(channel.ID))
|
||||||
{
|
{
|
||||||
// Begin DeltaV Additions: No using headsets if you lost your hands or are cuffed
|
|
||||||
if (!TryComp<HandsComponent>(uid, out var hands) || hands.Count < 1 ||
|
if (!TryComp<HandsComponent>(uid, out var hands) || hands.Count < 1 ||
|
||||||
TryComp<CuffableComponent>(uid, out var cuffable) && _cuffable.IsCuffed((uid, cuffable)))
|
TryComp<CuffableComponent>(uid, out var cuffable) && _cuffable.IsCuffed((uid, cuffable)))
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString("headset-cant-reach"), uid, uid, PopupType.SmallCaution);
|
_popup.PopupEntity(Loc.GetString("headset-cant-reach"), uid, uid, PopupType.SmallCaution);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
// End DeltaV Additions
|
|
||||||
|
|
||||||
_radio.SendRadioMessage(uid, args.Message, args.Channel, component.Headset);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAudibleEmote(EntityUid uid, WearingHeadsetComponent component, EntityAudiblyEmotedEvent args)
|
||||||
|
{
|
||||||
|
if (CheckRadioCapable(uid, component, args.Channel))
|
||||||
|
{
|
||||||
|
_radio.SendRadioMessage(uid, args.Message, args.Channel!, component.Headset, emType: args.Type);
|
||||||
|
args.Channel = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DeltaV - End
|
||||||
|
|
||||||
|
private void OnSpeak(EntityUid uid, WearingHeadsetComponent component, EntitySpokeEvent args)
|
||||||
|
{
|
||||||
|
if (CheckRadioCapable(uid, component, args.Channel)) // DeltaV - Put all the radio checks into the CheckRadioCapable() method.
|
||||||
|
{
|
||||||
|
_radio.SendRadioMessage(uid, args.Message, args.Channel!, component.Headset); // DeltaV - Made the args.Channel null ignorant as CheckRadioCapable() guarantees it not null.
|
||||||
args.Channel = null; // prevent duplicate messages from other listeners.
|
args.Channel = null; // prevent duplicate messages from other listeners.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
|
using Content.Server.Ghost;
|
||||||
using Content.Server.Power.Components;
|
using Content.Server.Power.Components;
|
||||||
|
using Content.Shared._DV.Chat;
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Radio;
|
using Content.Shared.Radio;
|
||||||
@@ -27,6 +30,8 @@ public sealed class RadioSystem : EntitySystem
|
|||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
[Dependency] private readonly ChatSystem _chat = default!;
|
[Dependency] private readonly ChatSystem _chat = default!;
|
||||||
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
|
[Dependency] private readonly GhostSystem _ghost = default!;
|
||||||
|
|
||||||
// set used to prevent radio feedback loops.
|
// set used to prevent radio feedback loops.
|
||||||
private readonly HashSet<string> _messages = new();
|
private readonly HashSet<string> _messages = new();
|
||||||
@@ -38,6 +43,7 @@ public sealed class RadioSystem : EntitySystem
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
|
SubscribeLocalEvent<IntrinsicRadioReceiverComponent, RadioReceiveEvent>(OnIntrinsicReceive);
|
||||||
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);
|
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntitySpokeEvent>(OnIntrinsicSpeak);
|
||||||
|
SubscribeLocalEvent<IntrinsicRadioTransmitterComponent, EntityAudiblyEmotedEvent>(OnIntrinsicAudibleEmote); // DeltaV - Robots should be allowed to emote over radio.
|
||||||
|
|
||||||
_exemptQuery = GetEntityQuery<TelecomExemptComponent>();
|
_exemptQuery = GetEntityQuery<TelecomExemptComponent>();
|
||||||
}
|
}
|
||||||
@@ -53,16 +59,43 @@ public sealed class RadioSystem : EntitySystem
|
|||||||
|
|
||||||
private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args)
|
private void OnIntrinsicReceive(EntityUid uid, IntrinsicRadioReceiverComponent component, ref RadioReceiveEvent args)
|
||||||
{
|
{
|
||||||
if (TryComp(uid, out ActorComponent? actor))
|
if (!TryComp(uid, out ActorComponent? actor))
|
||||||
_netMan.ServerSendMessage(args.ChatMsg, actor.PlayerSession.Channel);
|
return;
|
||||||
|
|
||||||
|
var msg = args.ChatMsg;
|
||||||
|
if (_ghost.CanGhostWarp(actor.PlayerSession, out _))
|
||||||
|
{
|
||||||
|
msg = new MsgChatMessage
|
||||||
|
{
|
||||||
|
Message = new ChatMessage(args.ChatMsg.Message)
|
||||||
|
{
|
||||||
|
WrappedMessage = _chatManager.PrependFollowButtonIfAppropriate(
|
||||||
|
args.ChatMsg.Message.WrappedMessage,
|
||||||
|
args.MessageSource,
|
||||||
|
actor.PlayerSession.Channel),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_netMan.ServerSendMessage(msg, actor.PlayerSession.Channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeltaV
|
||||||
|
private void OnIntrinsicAudibleEmote(EntityUid uid, IntrinsicRadioTransmitterComponent component, EntityAudiblyEmotedEvent args)
|
||||||
|
{
|
||||||
|
if (args.Channel != null && component.Channels.Contains(args.Channel.ID))
|
||||||
|
{
|
||||||
|
SendRadioMessage(uid, args.Message, args.Channel, uid, emType: args.Type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// DeltaV - End
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send radio message to all active radio listeners
|
/// Send radio message to all active radio listeners
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true)
|
public void SendRadioMessage(EntityUid messageSource, string message, ProtoId<RadioChannelPrototype> channel, EntityUid radioSource, bool escapeMarkup = true, EmoteType? emType = null) // DeltaV - EmoteType? added.
|
||||||
{
|
{
|
||||||
SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup);
|
SendRadioMessage(messageSource, message, _prototype.Index(channel), radioSource, escapeMarkup: escapeMarkup, emType: emType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -70,7 +103,7 @@ public sealed class RadioSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="messageSource">Entity that spoke the message</param>
|
/// <param name="messageSource">Entity that spoke the message</param>
|
||||||
/// <param name="radioSource">Entity that picked up the message and will send it, e.g. headset</param>
|
/// <param name="radioSource">Entity that picked up the message and will send it, e.g. headset</param>
|
||||||
public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true)
|
public void SendRadioMessage(EntityUid messageSource, string message, RadioChannelPrototype channel, EntityUid radioSource, bool escapeMarkup = true, EmoteType? emType = null) // DeltaV - EmoteType? added.
|
||||||
{
|
{
|
||||||
// TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this.
|
// TODO if radios ever garble / modify messages, feedback-prevention needs to be handled better than this.
|
||||||
if (!_messages.Add(message))
|
if (!_messages.Add(message))
|
||||||
@@ -92,14 +125,31 @@ public sealed class RadioSystem : EntitySystem
|
|||||||
? FormattedMessage.EscapeText(message)
|
? FormattedMessage.EscapeText(message)
|
||||||
: message;
|
: message;
|
||||||
|
|
||||||
var wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
|
// DeltaV - This change is to change up how the messages are wrapped up. Basically changing the formatting depending on the emote type.
|
||||||
("color", channel.Color),
|
string wrappedMessage;
|
||||||
("fontType", speech.FontId),
|
|
||||||
("fontSize", speech.FontSize),
|
if (emType == EmoteType.Audible)
|
||||||
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
|
wrappedMessage = Loc.GetString("chat-radio-message-audible-emote-wrap",
|
||||||
("channel", $"\\[{channel.LocalizedName}\\]"),
|
("color", channel.Color),
|
||||||
("name", name),
|
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||||
("message", content));
|
("name", name),
|
||||||
|
("message", content));
|
||||||
|
else if (emType == EmoteType.AudiblePossessive)
|
||||||
|
wrappedMessage = Loc.GetString("chat-radio-message-audible-possessive-emote-wrap",
|
||||||
|
("color", channel.Color),
|
||||||
|
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||||
|
("name", name),
|
||||||
|
("message", content));
|
||||||
|
else
|
||||||
|
wrappedMessage = Loc.GetString(speech.Bold ? "chat-radio-message-wrap-bold" : "chat-radio-message-wrap",
|
||||||
|
("color", channel.Color),
|
||||||
|
("fontType", speech.FontId),
|
||||||
|
("fontSize", speech.FontSize),
|
||||||
|
("verb", Loc.GetString(_random.Pick(speech.SpeechVerbStrings))),
|
||||||
|
("channel", $"\\[{channel.LocalizedName}\\]"),
|
||||||
|
("name", name),
|
||||||
|
("message", content));
|
||||||
|
// DeltaV - End
|
||||||
|
|
||||||
// most radios are relayed to chat, so lets parse the chat message beforehand
|
// most radios are relayed to chat, so lets parse the chat message beforehand
|
||||||
var chat = new ChatMessage(
|
var chat = new ChatMessage(
|
||||||
@@ -107,7 +157,8 @@ public sealed class RadioSystem : EntitySystem
|
|||||||
message,
|
message,
|
||||||
wrappedMessage,
|
wrappedMessage,
|
||||||
NetEntity.Invalid,
|
NetEntity.Invalid,
|
||||||
null);
|
null,
|
||||||
|
radioChannelProto: channel.ID); // DeltaV - Add RadioChannel for committing sins
|
||||||
var chatMsg = new MsgChatMessage { Message = chat };
|
var chatMsg = new MsgChatMessage { Message = chat };
|
||||||
var ev = new RadioReceiveEvent(message, messageSource, channel, radioSource, chatMsg);
|
var ev = new RadioReceiveEvent(message, messageSource, channel, radioSource, chatMsg);
|
||||||
|
|
||||||
@@ -159,7 +210,7 @@ public sealed class RadioSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="TelecomServerComponent"/>
|
/// <inheritdoc cref="TelecomServerComponent"/>
|
||||||
private bool HasActiveServer(MapId mapId, string channelId)
|
public bool HasActiveServer(MapId mapId, string channelId) // DeltaV - we need this
|
||||||
{
|
{
|
||||||
var servers = EntityQuery<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent, TransformComponent>();
|
var servers = EntityQuery<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent, TransformComponent>();
|
||||||
foreach (var (_, keys, power, transform) in servers)
|
foreach (var (_, keys, power, transform) in servers)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Linq; // DeltaV
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Content.Server.Atmos.EntitySystems;
|
using Content.Server.Atmos.EntitySystems;
|
||||||
using Content.Server.Chat.Systems;
|
using Content.Server.Chat.Systems;
|
||||||
@@ -8,6 +9,8 @@ using Content.Server.Popups;
|
|||||||
using Content.Shared.Atmos;
|
using Content.Shared.Atmos;
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Content.Shared.Dataset;
|
using Content.Shared.Dataset;
|
||||||
|
using Content.Shared.Mobs; // DeltaV
|
||||||
|
using Content.Shared.Mobs.Components; // DeltaV
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
using Content.Shared.Nutrition.EntitySystems;
|
using Content.Shared.Nutrition.EntitySystems;
|
||||||
using Content.Shared.Pointing;
|
using Content.Shared.Pointing;
|
||||||
@@ -47,14 +50,33 @@ namespace Content.Server.RatKing
|
|||||||
if (!TryComp<HungerComponent>(uid, out var hunger))
|
if (!TryComp<HungerComponent>(uid, out var hunger))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// DeltaV - modify cost of Raise Army based on the amount of alive servants
|
||||||
|
// Check on how many servants are alive to calculate the cost of a new servant
|
||||||
|
var aliveServants = component.Servants.Count(servant
|
||||||
|
=> TryComp<MobStateComponent>(servant, out var mobState) && mobState.CurrentState == MobState.Alive);
|
||||||
|
|
||||||
|
// calculate the cost multiplier
|
||||||
|
var multiplier = aliveServants switch
|
||||||
|
{
|
||||||
|
< 5 => 1.0f, // 1-5 servants: 10 hunger
|
||||||
|
< 10 => 1.5f, // 6-10 servants: 15 hunger
|
||||||
|
< 15 => 2.5f, // 11-15 servants: 25 hunger
|
||||||
|
< 20 => 5.0f, // 16-20 servants: 50 hunger
|
||||||
|
< 25 => 10.0f, // 21-25 servants: 100 hunger
|
||||||
|
_ => 15.0f, // Above 25 servants: 150 hunger
|
||||||
|
};
|
||||||
|
var hungerPerArmyUseAdjusted = component.HungerPerArmyUse * multiplier;
|
||||||
|
|
||||||
//make sure the hunger doesn't go into the negatives
|
//make sure the hunger doesn't go into the negatives
|
||||||
if (_hunger.GetHunger(hunger) < component.HungerPerArmyUse)
|
if (_hunger.GetHunger(hunger) < hungerPerArmyUseAdjusted)
|
||||||
{
|
{
|
||||||
_popup.PopupEntity(Loc.GetString("rat-king-too-hungry"), uid, uid);
|
_popup.PopupEntity(Loc.GetString("rat-king-too-hungry"), uid, uid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
args.Handled = true;
|
args.Handled = true;
|
||||||
_hunger.ModifyHunger(uid, -component.HungerPerArmyUse, hunger);
|
_hunger.ModifyHunger(uid, - hungerPerArmyUseAdjusted, hunger);
|
||||||
|
// DeltaV - end to the modified cost of Raise Army
|
||||||
|
|
||||||
var servant = Spawn(component.ArmyMobSpawnId, Transform(uid).Coordinates);
|
var servant = Spawn(component.ArmyMobSpawnId, Transform(uid).Coordinates);
|
||||||
var comp = EnsureComp<RatKingServantComponent>(servant);
|
var comp = EnsureComp<RatKingServantComponent>(servant);
|
||||||
comp.King = uid;
|
comp.King = uid;
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ namespace Content.Server.RoundEnd
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
RequestRoundEnd(time, null, false, textCall,
|
RequestRoundEnd(time, null, false, textCall,
|
||||||
Loc.GetString(sender));
|
Loc.GetString(sender), cantRecall: true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ public sealed partial class BorgSystem
|
|||||||
var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>();
|
var query = EntityQueryEnumerator<BorgTransponderComponent, BorgChassisComponent, DeviceNetworkComponent, MetaDataComponent>();
|
||||||
while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta))
|
while (query.MoveNext(out var uid, out var comp, out var chassis, out var device, out var meta))
|
||||||
{
|
{
|
||||||
|
// DeltaV Begin
|
||||||
|
if (!comp.Active)
|
||||||
|
return;
|
||||||
|
// DeltaV End
|
||||||
|
|
||||||
if (comp.NextDisable is { } nextDisable && now >= nextDisable)
|
if (comp.NextDisable is { } nextDisable && now >= nextDisable)
|
||||||
DoDisable((uid, comp, chassis, meta));
|
DoDisable((uid, comp, chassis, meta));
|
||||||
|
|
||||||
@@ -83,6 +88,11 @@ public sealed partial class BorgSystem
|
|||||||
|
|
||||||
private void OnPacketReceived(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent args)
|
private void OnPacketReceived(Entity<BorgTransponderComponent> ent, ref DeviceNetworkPacketEvent args)
|
||||||
{
|
{
|
||||||
|
// DeltaV Begin
|
||||||
|
if (!ent.Comp.Active)
|
||||||
|
return;
|
||||||
|
// DeltaV End
|
||||||
|
|
||||||
var payload = args.Data;
|
var payload = args.Data;
|
||||||
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
if (!payload.TryGetValue(DeviceNetworkConstants.Command, out string? command))
|
||||||
return;
|
return;
|
||||||
@@ -197,4 +207,17 @@ public sealed partial class BorgSystem
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeltaV Begin
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - sets if the borg transponder is active
|
||||||
|
/// </summary>
|
||||||
|
public void SetTransponderActive(Entity<BorgTransponderComponent?> ent, bool active)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ent.Comp.Active = active;
|
||||||
|
}
|
||||||
|
// DeltaV End
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,14 @@ public sealed partial class SpeechWireAction : ComponentWireAction<SpeechCompone
|
|||||||
public override bool Cut(EntityUid user, Wire wire, SpeechComponent component)
|
public override bool Cut(EntityUid user, Wire wire, SpeechComponent component)
|
||||||
{
|
{
|
||||||
_speech.SetSpeech(wire.Owner, false, component);
|
_speech.SetSpeech(wire.Owner, false, component);
|
||||||
|
EntityManager.GetComponentOrNull<Components.UnblockableSpeechComponent>(user)?.Active = false; // DeltaV - we need this to override unblockable speech
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Mend(EntityUid user, Wire wire, SpeechComponent component)
|
public override bool Mend(EntityUid user, Wire wire, SpeechComponent component)
|
||||||
{
|
{
|
||||||
_speech.SetSpeech(wire.Owner, true, component);
|
_speech.SetSpeech(wire.Owner, true, component);
|
||||||
|
EntityManager.GetComponentOrNull<Components.UnblockableSpeechComponent>(user)?.Active = true; // DeltaV - we need this to override unblockable speech
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,9 @@ namespace Content.Server.Speech.Components
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class UnblockableSpeechComponent : Component
|
public sealed partial class UnblockableSpeechComponent : Component
|
||||||
{
|
{
|
||||||
|
// Begin DeltaV
|
||||||
|
[DataField]
|
||||||
|
public bool Active = true;
|
||||||
|
// End DeltaV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Content.Server.Speech.EntitySystems
|
|||||||
|
|
||||||
private void OnCheck(EntityUid uid, UnblockableSpeechComponent component, CheckIgnoreSpeechBlockerEvent args)
|
private void OnCheck(EntityUid uid, UnblockableSpeechComponent component, CheckIgnoreSpeechBlockerEvent args)
|
||||||
{
|
{
|
||||||
args.IgnoreBlocker = true;
|
args.IgnoreBlocker = component.Active; // DeltaV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,41 @@ namespace Content.Server.StationEvents.Components;
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class IonStormRuleComponent : Component
|
public sealed partial class IonStormRuleComponent : Component
|
||||||
{
|
{
|
||||||
|
// DV start - Synthetic tweaks
|
||||||
|
/// <summary>
|
||||||
|
/// The chance that a synth gets electrocuted by the ion storm.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float SynthElectrocutionChance = 0.6f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum delay before a synth affected by the ion storm gets electrocuted. (in seconds)
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int SynthElectrocutionDelayMin = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum delay before a synth affected by the ion storm gets electrocuted. (in seconds)
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int SynthElectrocutionDelayMax = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The minimum damage dealt by the synth electrocution. (shock damage)
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int SynthElectrocutionDamageMin = 5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The maximum damage dealt by the synth electrocution. (shock damage)
|
||||||
|
/// </summary>8
|
||||||
|
[DataField]
|
||||||
|
public int SynthElectrocutionDamageMax = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long the synth is stunned for by the electrocution. (in seconds)
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int SynthElectrocutionStunDuration = 3;
|
||||||
|
// DV end - synthetic tweaks
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,12 @@ using Content.Shared.Silicons.Laws.Components;
|
|||||||
using Content.Shared.Station.Components;
|
using Content.Shared.Station.Components;
|
||||||
// CD - start synth trait
|
// CD - start synth trait
|
||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Chat.Managers;
|
||||||
|
using Content.Server.Electrocution; // DV - added for synth trait
|
||||||
using Content.Shared.Chat;
|
using Content.Shared.Chat;
|
||||||
using Robust.Shared.Player;
|
using Robust.Shared.Player;
|
||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
|
using Content.Shared._ES.Sparks; // Delta-V change - added for synth trait (where they get affected by ion storms)
|
||||||
|
using Timer = Robust.Shared.Timing.Timer; // DV - used for synth trait delayed damage
|
||||||
// CD - end synth trait
|
// CD - end synth trait
|
||||||
|
|
||||||
namespace Content.Server.StationEvents.Events;
|
namespace Content.Server.StationEvents.Events;
|
||||||
@@ -17,6 +20,8 @@ public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IonStormSystem _ionStorm = default!;
|
[Dependency] private readonly IonStormSystem _ionStorm = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!; // CD - Used for synth trait
|
[Dependency] private readonly IChatManager _chatManager = default!; // CD - Used for synth trait
|
||||||
|
[Dependency] private readonly ElectrocutionSystem _electrocution = default!; // DV - used for synth trait
|
||||||
|
[Dependency] private readonly ESSparksSystem _esSparks = default!; // DV - used for synth trait
|
||||||
|
|
||||||
protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
protected override void Started(EntityUid uid, IonStormRuleComponent comp, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
||||||
{
|
{
|
||||||
@@ -26,18 +31,32 @@ public sealed class IonStormRule : StationEventSystem<IonStormRuleComponent>
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// CD - Go through everyone with the SynthComponent and inform them a storm is happening.
|
// CD - Go through everyone with the SynthComponent and inform them a storm is happening.
|
||||||
|
// DV - buuuuuut only if IonStormAffected is present so that not all synthetics are affected by this
|
||||||
|
// but they are still notified about incoming ion storms regardless.
|
||||||
var synthQuery = EntityQueryEnumerator<SynthComponent>();
|
var synthQuery = EntityQueryEnumerator<SynthComponent>();
|
||||||
while (synthQuery.MoveNext(out var ent, out var synthComp))
|
while (synthQuery.MoveNext(out var ent, out var synthComp))
|
||||||
{
|
{
|
||||||
if (RobustRandom.Prob(synthComp.AlertChance))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!TryComp<ActorComponent>(ent, out var actor))
|
if (!TryComp<ActorComponent>(ent, out var actor))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var msg = Loc.GetString("station-event-ion-storm-synth");
|
if (RobustRandom.Prob(synthComp.AlertChance))
|
||||||
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
|
{
|
||||||
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Yellow);
|
var msg = Loc.GetString("station-event-ion-storm-synth");
|
||||||
|
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
|
||||||
|
_chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMessage, default, false, actor.PlayerSession.Channel, colorOverride: Color.Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DV start - super duper fucked up thing for synths
|
||||||
|
if (synthComp.IonStormAffected && RobustRandom.Prob(comp.SynthElectrocutionChance))
|
||||||
|
{
|
||||||
|
var delay = RobustRandom.Next(TimeSpan.FromSeconds(comp.SynthElectrocutionDelayMin), TimeSpan.FromSeconds(comp.SynthElectrocutionDelayMax)); // they'll never know when it'll hit them
|
||||||
|
Timer.Spawn(delay, () =>
|
||||||
|
{
|
||||||
|
_electrocution.TryDoElectrocution(ent, null, RobustRandom.Next(comp.SynthElectrocutionDamageMin, comp.SynthElectrocutionDamageMax), TimeSpan.FromSeconds(comp.SynthElectrocutionStunDuration), true, ignoreInsulation: true);
|
||||||
|
_esSparks.DoSparks(ent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// DV end
|
||||||
}
|
}
|
||||||
// CD - End of synth trait
|
// CD - End of synth trait
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Content.Server._DV.Psionics.Systems;
|
|
||||||
using Content.Server.Administration.Managers;
|
using Content.Server.Administration.Managers;
|
||||||
using Content.Server.Atmos.Components;
|
using Content.Server.Atmos.Components;
|
||||||
using Content.Server.Body.Components;
|
using Content.Server.Body.Components;
|
||||||
@@ -14,7 +13,6 @@ using Content.Server.NPC.HTN;
|
|||||||
using Content.Server.NPC.Systems;
|
using Content.Server.NPC.Systems;
|
||||||
using Content.Server.StationEvents.Components;
|
using Content.Server.StationEvents.Components;
|
||||||
using Content.Server.Speech.Components;
|
using Content.Server.Speech.Components;
|
||||||
using Content.Shared._DV.Psionics.Components; // DeltaV
|
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
using Content.Shared.CombatMode;
|
using Content.Shared.CombatMode;
|
||||||
using Content.Shared.CombatMode.Pacification;
|
using Content.Shared.CombatMode.Pacification;
|
||||||
@@ -27,7 +25,6 @@ using Content.Shared.Mobs.Components;
|
|||||||
using Content.Shared.Movement.Pulling.Components;
|
using Content.Shared.Movement.Pulling.Components;
|
||||||
using Content.Shared.Movement.Systems;
|
using Content.Shared.Movement.Systems;
|
||||||
using Content.Shared.NameModifier.EntitySystems;
|
using Content.Shared.NameModifier.EntitySystems;
|
||||||
using Content.Shared.NPC.Components; // DeltaV
|
|
||||||
using Content.Shared.NPC.Systems;
|
using Content.Shared.NPC.Systems;
|
||||||
using Content.Shared.Nutrition.AnimalHusbandry;
|
using Content.Shared.Nutrition.AnimalHusbandry;
|
||||||
using Content.Shared.Nutrition.Components;
|
using Content.Shared.Nutrition.Components;
|
||||||
@@ -72,7 +69,6 @@ public sealed partial class ZombieSystem
|
|||||||
[Dependency] private readonly NPCSystem _npc = default!;
|
[Dependency] private readonly NPCSystem _npc = default!;
|
||||||
[Dependency] private readonly TagSystem _tag = default!;
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||||
[Dependency] private readonly PsionicSystem _psionic = default!; // DeltaV
|
|
||||||
|
|
||||||
private static readonly ProtoId<TagPrototype> InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell";
|
private static readonly ProtoId<TagPrototype> InvalidForGlobalSpawnSpellTag = "InvalidForGlobalSpawnSpell";
|
||||||
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
|
private static readonly ProtoId<TagPrototype> CannotSuicideTag = "CannotSuicide";
|
||||||
@@ -134,6 +130,9 @@ public sealed partial class ZombieSystem
|
|||||||
//you're a real zombie now, son.
|
//you're a real zombie now, son.
|
||||||
var zombiecomp = AddComp<ZombieComponent>(target);
|
var zombiecomp = AddComp<ZombieComponent>(target);
|
||||||
|
|
||||||
|
// DeltaV - Save factions, psionics, etc. before modifications are made.
|
||||||
|
PreserveEntityComponentState((target, zombiecomp));
|
||||||
|
|
||||||
//we need to basically remove all of these because zombies shouldn't
|
//we need to basically remove all of these because zombies shouldn't
|
||||||
//get diseases, breath, be thirst, be hungry, die in space, get double sentience, have offspring or be paraplegic.
|
//get diseases, breath, be thirst, be hungry, die in space, get double sentience, have offspring or be paraplegic.
|
||||||
RemComp<RespiratorComponent>(target);
|
RemComp<RespiratorComponent>(target);
|
||||||
@@ -146,12 +145,6 @@ public sealed partial class ZombieSystem
|
|||||||
RemComp<ComplexInteractionComponent>(target);
|
RemComp<ComplexInteractionComponent>(target);
|
||||||
RemComp<SentienceTargetComponent>(target);
|
RemComp<SentienceTargetComponent>(target);
|
||||||
|
|
||||||
// DeltaV Start - Prevent Psionic Zombies
|
|
||||||
RemComp<PotentialPsionicComponent>(target);
|
|
||||||
if (HasComp<PsionicComponent>(target))
|
|
||||||
_psionic.MindBreakEntity(target, false, true);
|
|
||||||
// DeltaV End - Prevent Psionic Zombies
|
|
||||||
|
|
||||||
//funny voice
|
//funny voice
|
||||||
var accentType = "zombie";
|
var accentType = "zombie";
|
||||||
if (TryComp<ZombieAccentOverrideComponent>(target, out var accent))
|
if (TryComp<ZombieAccentOverrideComponent>(target, out var accent))
|
||||||
@@ -245,6 +238,9 @@ public sealed partial class ZombieSystem
|
|||||||
//Should prevent instances of zombies using comms for information they shouldnt be able to have.
|
//Should prevent instances of zombies using comms for information they shouldnt be able to have.
|
||||||
_inventory.TryUnequip(target, "ears", true, true);
|
_inventory.TryUnequip(target, "ears", true, true);
|
||||||
|
|
||||||
|
// DeltaV - Extra zombification removals, etc.
|
||||||
|
ZombifyEntityDV((target, zombiecomp), mobState);
|
||||||
|
|
||||||
//popup
|
//popup
|
||||||
_popup.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, PopupType.LargeCaution);
|
_popup.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, PopupType.LargeCaution);
|
||||||
|
|
||||||
@@ -261,7 +257,6 @@ public sealed partial class ZombieSystem
|
|||||||
|
|
||||||
_faction.ClearFactions(target, dirty: false);
|
_faction.ClearFactions(target, dirty: false);
|
||||||
_faction.AddFaction(target, ZombieFaction);
|
_faction.AddFaction(target, ZombieFaction);
|
||||||
EnsureComp<NoFriendlyFireComponent>(target); // DeltaV - prevent shitters biting other zombies
|
|
||||||
|
|
||||||
//gives it the funny "Zombie ___" name.
|
//gives it the funny "Zombie ___" name.
|
||||||
_nameMod.RefreshNameModifiers(target);
|
_nameMod.RefreshNameModifiers(target);
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ namespace Content.Server.Zombies
|
|||||||
SubscribeLocalEvent<IncurableZombieComponent, MapInitEvent>(OnPendingMapInit);
|
SubscribeLocalEvent<IncurableZombieComponent, MapInitEvent>(OnPendingMapInit);
|
||||||
|
|
||||||
SubscribeLocalEvent<ZombifyOnDeathComponent, MobStateChangedEvent>(OnDamageChanged);
|
SubscribeLocalEvent<ZombifyOnDeathComponent, MobStateChangedEvent>(OnDamageChanged);
|
||||||
|
|
||||||
|
InitializeDV(); // DeltaV - Additional Subscriptions for ZombieComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBeforeRemoveAnomalyOnDeath(Entity<PendingZombieComponent> ent, ref BeforeRemoveAnomalyOnDeathEvent args)
|
private void OnBeforeRemoveAnomalyOnDeath(Entity<PendingZombieComponent> ent, ref BeforeRemoveAnomalyOnDeathEvent args)
|
||||||
@@ -325,7 +327,7 @@ namespace Content.Server.Zombies
|
|||||||
// Remove the role when getting cloned, getting gibbed and borged, or leaving the body via any other method.
|
// Remove the role when getting cloned, getting gibbed and borged, or leaving the body via any other method.
|
||||||
private void OnMindRemoved(Entity<ZombieComponent> ent, ref MindRemovedMessage args)
|
private void OnMindRemoved(Entity<ZombieComponent> ent, ref MindRemovedMessage args)
|
||||||
{
|
{
|
||||||
_role.MindRemoveRole<ZombieRoleComponent>((args.Mind.Owner, args.Mind.Comp));
|
_role.MindRemoveRole<ZombieRoleComponent>((args.Mind.Owner, args.Mind.Comp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,16 @@ namespace Content.Server._CD.Traits;
|
|||||||
public sealed partial class SynthComponent : Component
|
public sealed partial class SynthComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chance that the synth is alerted of an ion storm
|
/// Whether this synthetic is affected by ion storms (electrocution/sparks and
|
||||||
|
/// alerts when one occurs). False by default. set true by the
|
||||||
|
/// "Susceptible to Ion Storms" meta trait.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public bool IonStormAffected;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The chance that the synth is alerted of an ion storm. Only relevant if
|
||||||
|
/// <see cref="IonStormAffected"/> is true.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public float AlertChance = 0.3f;
|
public float AlertChance = 0.3f;
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
using Content.Server.Body.Systems;
|
using Content.Server.Body.Systems;
|
||||||
using Content.Server.Database;
|
using Content.Server.Database;
|
||||||
using Content.Shared.Body.Components;
|
using Content.Shared.Body.Components;
|
||||||
|
using Content.Shared.Chat.Prototypes;
|
||||||
using Content.Shared.Chat.TypingIndicator;
|
using Content.Shared.Chat.TypingIndicator;
|
||||||
using Content.Shared.Chemistry.Reagent;
|
using Content.Shared.Chemistry.Reagent;
|
||||||
|
using Content.Shared.Speech.Components;
|
||||||
|
using Content.Shared.Tag;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Server._CD.Traits;
|
namespace Content.Server._CD.Traits;
|
||||||
@@ -12,9 +15,13 @@ public sealed class SynthSystem : EntitySystem
|
|||||||
// Begin DeltaV - make strings static readonly
|
// Begin DeltaV - make strings static readonly
|
||||||
private static readonly ProtoId<TypingIndicatorPrototype> RobotTypingIndicator = "robot";
|
private static readonly ProtoId<TypingIndicatorPrototype> RobotTypingIndicator = "robot";
|
||||||
private static readonly ProtoId<ReagentPrototype> SynthBloodReagent = "SynthBlood";
|
private static readonly ProtoId<ReagentPrototype> SynthBloodReagent = "SynthBlood";
|
||||||
|
private static readonly ProtoId<TagPrototype> SyntheticEmotesTag = "SyntheticEmotes";
|
||||||
|
private static readonly ProtoId<EmoteSoundsPrototype> SyntheticEmoteSounds = "SyntheticEmoteSounds";
|
||||||
// End DeltaV
|
// End DeltaV
|
||||||
|
|
||||||
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
|
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
|
||||||
|
[Dependency] private readonly TagSystem _tag = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -38,5 +45,21 @@ public sealed class SynthSystem : EntitySystem
|
|||||||
_bloodstream.ChangeBloodReagents((uid, bloodstream), new([new(SynthBloodReagent, bloodstream.BloodReferenceSolution.Volume)]));
|
_bloodstream.ChangeBloodReagents((uid, bloodstream), new([new(SynthBloodReagent, bloodstream.BloodReferenceSolution.Volume)]));
|
||||||
}
|
}
|
||||||
// End DeltaV
|
// End DeltaV
|
||||||
|
|
||||||
|
// Begin DeltaV - Add SyntheticEmotes tag and merge synthetic emote sounds into species sounds
|
||||||
|
_tag.AddTag(uid, SyntheticEmotesTag);
|
||||||
|
|
||||||
|
if (TryComp<VocalComponent>(uid, out var vocal)
|
||||||
|
&& _proto.TryIndex(SyntheticEmoteSounds, out var synthSounds)
|
||||||
|
&& vocal.EmoteSounds is { } currentSoundsId
|
||||||
|
&& _proto.TryIndex(currentSoundsId, out var currentSounds))
|
||||||
|
{
|
||||||
|
foreach (var (emoteId, sound) in synthSounds.Sounds)
|
||||||
|
{
|
||||||
|
currentSounds.Sounds.TryAdd(emoteId, sound);
|
||||||
|
}
|
||||||
|
Dirty(uid, vocal);
|
||||||
|
}
|
||||||
|
// End DeltaV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using Content.Shared.Audio;
|
||||||
|
using Robust.Shared.Audio;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
|
||||||
|
namespace Content.Server.Audio;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extends upstream's Content.Server/Audio/ServerGlobalSoundSystem.cs.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class ServerGlobalSoundSystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - Plays a sound globally for all players, no matter where they are.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="specifier"></param>
|
||||||
|
/// <param name="audioParams"></param>
|
||||||
|
public void PlayGlobal(ResolvedSoundSpecifier specifier, AudioParams? audioParams = null)
|
||||||
|
{
|
||||||
|
var msg = new GameGlobalSoundEvent(specifier, audioParams);
|
||||||
|
RaiseNetworkEvent(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopGlobalEventMusic(StationEventMusicType type)
|
||||||
|
{
|
||||||
|
var msg = new StopStationEventMusic(type);
|
||||||
|
RaiseNetworkEvent(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DispatchGlobalEventMusic(SoundSpecifier sound, StationEventMusicType type)
|
||||||
|
{
|
||||||
|
DispatchGlobalEventMusic(_audio.ResolveSound(sound), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DispatchGlobalEventMusic(ResolvedSoundSpecifier specifier, StationEventMusicType type)
|
||||||
|
{
|
||||||
|
var audio = AudioParams.Default.WithVolume(-8);
|
||||||
|
var msg = new StationEventMusicEvent(specifier, type, audio);
|
||||||
|
RaiseNetworkEvent(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DeltaV - Plays a sound globally for all players on a specified Map.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="specifier"></param>
|
||||||
|
/// <param name="audioParams"></param>
|
||||||
|
public void PlayGlobalOnMap(MapId map, ResolvedSoundSpecifier specifier, AudioParams? audioParams = null)
|
||||||
|
{
|
||||||
|
var msg = new GameGlobalSoundEvent(specifier, audioParams);
|
||||||
|
var filter = Filter.Empty().AddInMap(map);
|
||||||
|
RaiseNetworkEvent(msg, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StopMapEventMusic(MapId map, StationEventMusicType type)
|
||||||
|
{
|
||||||
|
var msg = new StopStationEventMusic(type);
|
||||||
|
var filter = Filter.Empty().AddInMap(map);
|
||||||
|
RaiseNetworkEvent(msg, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DispatchMapEventMusic(MapId map, SoundSpecifier sound, StationEventMusicType type)
|
||||||
|
{
|
||||||
|
DispatchMapEventMusic(map, _audio.ResolveSound(sound), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DispatchMapEventMusic(MapId map, ResolvedSoundSpecifier specifier, StationEventMusicType type)
|
||||||
|
{
|
||||||
|
var audio = AudioParams.Default.WithVolume(-8);
|
||||||
|
var msg = new StationEventMusicEvent(specifier, type, audio);
|
||||||
|
var filter = Filter.Empty().AddInMap(map);
|
||||||
|
RaiseNetworkEvent(msg, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,12 +6,6 @@ namespace Content.Server._DV.CartridgeLoader.Cartridges;
|
|||||||
[RegisterComponent, Access(typeof(NanoChatCartridgeSystem))]
|
[RegisterComponent, Access(typeof(NanoChatCartridgeSystem))]
|
||||||
public sealed partial class NanoChatCartridgeComponent : Component
|
public sealed partial class NanoChatCartridgeComponent : Component
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Station entity to keep track of.
|
|
||||||
/// </summary>
|
|
||||||
[DataField]
|
|
||||||
public EntityUid? Station;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The NanoChat card to keep track of.
|
/// The NanoChat card to keep track of.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Content.Server.Administration.Logs;
|
using Content.Server.Administration.Logs;
|
||||||
using Content.Server.CartridgeLoader;
|
using Content.Server.CartridgeLoader;
|
||||||
using Content.Server.Power.Components;
|
|
||||||
using Content.Server.Radio;
|
using Content.Server.Radio;
|
||||||
using Content.Server.Station.Systems;
|
using Content.Server.Radio.EntitySystems;
|
||||||
using Content.Shared.Access.Components;
|
using Content.Shared.Access.Components;
|
||||||
using Content.Shared.CartridgeLoader;
|
using Content.Shared.CartridgeLoader;
|
||||||
using Content.Shared.CCVar;
|
using Content.Shared.CCVar;
|
||||||
@@ -30,7 +29,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
|||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
[Dependency] private readonly SharedNanoChatSystem _nanoChat = default!;
|
[Dependency] private readonly SharedNanoChatSystem _nanoChat = default!;
|
||||||
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
[Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
|
||||||
[Dependency] private readonly StationSystem _station = default!;
|
[Dependency] private readonly RadioSystem _radio = default!;
|
||||||
|
|
||||||
private EntityQuery<PdaComponent> _pdaQuery;
|
private EntityQuery<PdaComponent> _pdaQuery;
|
||||||
private EntityQuery<NanoChatCardComponent> _cardQuery;
|
private EntityQuery<NanoChatCardComponent> _cardQuery;
|
||||||
@@ -456,9 +455,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
// First verify we can send from this device
|
// First verify we can send from this device
|
||||||
var channel = _prototype.Index(sender.Comp.RadioChannel);
|
var channel = _prototype.Index(sender.Comp.RadioChannel);
|
||||||
var sendAttemptEvent = new RadioSendAttemptEvent(channel, sender);
|
if (!CanSend(sender))
|
||||||
RaiseLocalEvent(ref sendAttemptEvent);
|
|
||||||
if (sendAttemptEvent.Cancelled)
|
|
||||||
return (true, new List<Entity<NanoChatCardComponent>>());
|
return (true, new List<Entity<NanoChatCardComponent>>());
|
||||||
|
|
||||||
var foundRecipients = new List<Entity<NanoChatCardComponent>>();
|
var foundRecipients = new List<Entity<NanoChatCardComponent>>();
|
||||||
@@ -487,26 +484,19 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
|||||||
if (receiverCart.Card != recipient.Owner)
|
if (receiverCart.Card != recipient.Owner)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check if devices are on same station/map
|
var receiverMapId = Transform(receiverUid).MapID;
|
||||||
var recipientStation = _station.GetOwningStation(receiverUid);
|
var senderMapId = Transform(sender).MapID;
|
||||||
var senderStation = _station.GetOwningStation(sender);
|
|
||||||
|
|
||||||
// Both entities must be on a station
|
// Must be on the same map unless long range is allowed.
|
||||||
if (recipientStation == null || senderStation == null)
|
if (!channel.LongRange && receiverMapId != senderMapId)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Must be on same map/station unless long range allowed
|
// Check if telecommunications are active on both ends
|
||||||
if (!channel.LongRange && recipientStation != senderStation)
|
if (!_radio.HasActiveServer(receiverMapId, receiverCart.RadioChannel) || !_radio.HasActiveServer(senderMapId, sender.Comp.RadioChannel))
|
||||||
continue;
|
|
||||||
|
|
||||||
// Needs telecomms
|
|
||||||
if (!HasActiveServer(senderStation.Value) || !HasActiveServer(recipientStation.Value))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check if recipient can receive
|
// Check if recipient can receive
|
||||||
var receiveAttemptEv = new RadioReceiveAttemptEvent(channel, sender, receiverUid);
|
if (!CanReceive(sender, receiverUid))
|
||||||
RaiseLocalEvent(ref receiveAttemptEv);
|
|
||||||
if (receiveAttemptEv.Cancelled)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Found valid cartridge that can receive
|
// Found valid cartridge that can receive
|
||||||
@@ -519,21 +509,26 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if there are any active telecomms servers on the given station
|
/// Tests if a NanoChat cartridge can send messages
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool HasActiveServer(EntityUid station)
|
/// <param name="sender">The NanoChat cartridge trying to send</param>
|
||||||
|
private bool CanSend(Entity<NanoChatCartridgeComponent> sender)
|
||||||
{
|
{
|
||||||
// I have no idea why this isn't public in the RadioSystem
|
var sendAttemptEvent = new RadioSendAttemptEvent(_prototype.Index(sender.Comp.RadioChannel), sender);
|
||||||
var query =
|
RaiseLocalEvent(ref sendAttemptEvent);
|
||||||
EntityQueryEnumerator<TelecomServerComponent, EncryptionKeyHolderComponent, ApcPowerReceiverComponent>();
|
return !sendAttemptEvent.Cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
while (query.MoveNext(out var uid, out _, out _, out var power))
|
/// <summary>
|
||||||
{
|
/// Tests if a receiver can receive from a given NanoChat cartridge
|
||||||
if (_station.GetOwningStation(uid) == station && power.Powered)
|
/// </summary>
|
||||||
return true;
|
/// <param name="sender">The NanoChat cartridge trying to send</param>
|
||||||
}
|
/// <param name="receiver">The receiver cartridge trying to receive</param>
|
||||||
|
private bool CanReceive(Entity<NanoChatCartridgeComponent> sender, EntityUid receiver)
|
||||||
return false;
|
{
|
||||||
|
var receiveAttemptEv = new RadioReceiveAttemptEvent(_prototype.Index(sender.Comp.RadioChannel), sender, receiver);
|
||||||
|
RaiseLocalEvent(ref receiveAttemptEv);
|
||||||
|
return !receiveAttemptEv.Cancelled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -688,16 +683,15 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
|||||||
private void UpdateUI(Entity<NanoChatCartridgeComponent> ent, EntityUid loader)
|
private void UpdateUI(Entity<NanoChatCartridgeComponent> ent, EntityUid loader)
|
||||||
{
|
{
|
||||||
List<NanoChatRecipient>? contacts;
|
List<NanoChatRecipient>? contacts;
|
||||||
if (_station.GetOwningStation(loader) is { } station)
|
|
||||||
{
|
|
||||||
ent.Comp.Station = station;
|
|
||||||
|
|
||||||
|
if (CanSend(ent) && _radio.HasActiveServer(Transform(ent).MapID, ent.Comp.RadioChannel))
|
||||||
|
{
|
||||||
contacts = [];
|
contacts = [];
|
||||||
|
|
||||||
var query = AllEntityQuery<NanoChatCardComponent, IdCardComponent>();
|
var query = AllEntityQuery<NanoChatCardComponent, IdCardComponent>();
|
||||||
while (query.MoveNext(out var entityId, out var nanoChatCard, out var idCardComponent))
|
while (query.MoveNext(out var entityId, out var nanoChatCard, out var idCardComponent))
|
||||||
{
|
{
|
||||||
if (nanoChatCard.ListNumber && nanoChatCard.Number is uint nanoChatNumber && idCardComponent.FullName is string fullName && _station.GetOwningStation(entityId) == station)
|
if (nanoChatCard.ListNumber && nanoChatCard.Number is uint nanoChatNumber && idCardComponent.FullName is string fullName)
|
||||||
{
|
{
|
||||||
contacts.Add(new NanoChatRecipient(nanoChatNumber, fullName));
|
contacts.Add(new NanoChatRecipient(nanoChatNumber, fullName));
|
||||||
}
|
}
|
||||||
@@ -706,7 +700,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
|||||||
var borgQuery = AllEntityQuery<NanoChatCardComponent, BorgChassisComponent>();
|
var borgQuery = AllEntityQuery<NanoChatCardComponent, BorgChassisComponent>();
|
||||||
while (borgQuery.MoveNext(out var borgId, out var borgChatCard, out var _))
|
while (borgQuery.MoveNext(out var borgId, out var borgChatCard, out var _))
|
||||||
{
|
{
|
||||||
if (borgChatCard.ListNumber && borgChatCard.Number is uint nanoChatNumber && _station.GetOwningStation(borgId) == station)
|
if (borgChatCard.ListNumber && borgChatCard.Number is uint nanoChatNumber)
|
||||||
{
|
{
|
||||||
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(borgId).EntityName));
|
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(borgId).EntityName));
|
||||||
}
|
}
|
||||||
@@ -715,7 +709,7 @@ public sealed class NanoChatCartridgeSystem : EntitySystem
|
|||||||
var aiQuery = AllEntityQuery<NanoChatCardComponent, StationAiHeldComponent>();
|
var aiQuery = AllEntityQuery<NanoChatCardComponent, StationAiHeldComponent>();
|
||||||
while (aiQuery.MoveNext(out var aiId, out var aiChatCard, out var _))
|
while (aiQuery.MoveNext(out var aiId, out var aiChatCard, out var _))
|
||||||
{
|
{
|
||||||
if (aiChatCard.ListNumber && aiChatCard.Number is uint nanoChatNumber && _station.GetOwningStation(aiId) == station)
|
if (aiChatCard.ListNumber && aiChatCard.Number is uint nanoChatNumber)
|
||||||
{
|
{
|
||||||
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(aiId).EntityName));
|
contacts.Add(new NanoChatRecipient(nanoChatNumber, MetaData(aiId).EntityName));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
using Content.Server._DV.CosmicCult.Components;
|
||||||
|
using Content.Shared._DV.CosmicCult.Components;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Damage.Components;
|
||||||
|
using Content.Shared.Damage.Systems;
|
||||||
|
using Content.Shared.Weapons.Melee;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||||
|
|
||||||
|
public sealed class CosmicColossusBuffsSystem : EntitySystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly DamageableSystem _damage = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<MultiplyAttackRateOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleAttackRate);
|
||||||
|
SubscribeLocalEvent<FlatAttackBonusOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleDamageBonus);
|
||||||
|
SubscribeLocalEvent<MultiplyCorruptingSpeedOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleCorruptingSpeed);
|
||||||
|
SubscribeLocalEvent<HealOnSupercriticalComponent, CosmicColossusEffigySupercriticalEvent>(HandleHeal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleAttackRate(Entity<MultiplyAttackRateOnSupercriticalComponent> ent,
|
||||||
|
ref CosmicColossusEffigySupercriticalEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<MeleeWeaponComponent>(ent, out var weapon))
|
||||||
|
{
|
||||||
|
weapon.AttackRate *= ent.Comp.Multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDamageBonus(Entity<FlatAttackBonusOnSupercriticalComponent> ent,
|
||||||
|
ref CosmicColossusEffigySupercriticalEvent args)
|
||||||
|
{
|
||||||
|
if (!TryComp<CosmicColossusComponent>(ent, out var colossusComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
colossusComp.BonusDamage +=
|
||||||
|
new DamageSpecifier(_proto.Index(ent.Comp.BonusDamageType), ent.Comp.BonusDamage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCorruptingSpeed(Entity<MultiplyCorruptingSpeedOnSupercriticalComponent> ent,
|
||||||
|
ref CosmicColossusEffigySupercriticalEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<CosmicCorruptingComponent>(ent, out var corrupting))
|
||||||
|
{
|
||||||
|
corrupting.CorruptionSpeed *= ent.Comp.Multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleHeal(Entity<HealOnSupercriticalComponent> ent, ref CosmicColossusEffigySupercriticalEvent args)
|
||||||
|
{
|
||||||
|
if (TryComp<DamageableComponent>(ent, out var damageable))
|
||||||
|
{
|
||||||
|
_damage.TryChangeDamage(ent.Owner,
|
||||||
|
-damageable.Damage * Math.Abs(ent.Comp.DamageFractionHealed), // just in case someone sets a negative value in YML
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
using Content.Server._DV.CosmicCult.Components;
|
||||||
|
|
||||||
|
namespace Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||||
|
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct CosmicColossusEffigySupercriticalEvent(
|
||||||
|
Entity<CosmicEffigyComponent> Effigy
|
||||||
|
);
|
||||||
@@ -1,15 +1,24 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||||
|
using Content.Server._DV.CosmicCult.Components;
|
||||||
using Content.Server.Actions;
|
using Content.Server.Actions;
|
||||||
|
using Content.Server.Chat.Managers;
|
||||||
using Content.Server.Objectives.Components;
|
using Content.Server.Objectives.Components;
|
||||||
using Content.Server.Objectives.Systems;
|
using Content.Server.Objectives.Systems;
|
||||||
using Content.Server.Popups;
|
using Content.Server.Popups;
|
||||||
using Content.Shared._DV.CosmicCult;
|
using Content.Shared._DV.CosmicCult;
|
||||||
using Content.Shared._DV.CosmicCult.Components;
|
using Content.Shared._DV.CosmicCult.Components;
|
||||||
|
using Content.Shared.Anomaly.Components;
|
||||||
using Content.Shared.Maps;
|
using Content.Shared.Maps;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
|
using Content.Shared.Popups;
|
||||||
using Content.Shared.Warps;
|
using Content.Shared.Warps;
|
||||||
|
using Robust.Server.Audio;
|
||||||
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Map.Components;
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Player;
|
||||||
|
using Robust.Shared.Timing;
|
||||||
|
|
||||||
namespace Content.Server._DV.CosmicCult.Abilities;
|
namespace Content.Server._DV.CosmicCult.Abilities;
|
||||||
|
|
||||||
@@ -23,12 +32,93 @@ public sealed class CosmicEffigySystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
|
[Dependency] private readonly AudioSystem _audio = default!;
|
||||||
|
[Dependency] private readonly CosmicCultObjectiveSystem _cultObjective = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _time = default!;
|
||||||
|
[Dependency] private readonly IChatManager _chat = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<CosmicColossusComponent, EventCosmicColossusEffigy>(OnColossusEffigy);
|
SubscribeLocalEvent<CosmicColossusComponent, EventCosmicColossusEffigy>(OnColossusEffigy);
|
||||||
|
SubscribeLocalEvent<CosmicEffigyComponent, AnomalySupercriticalEvent>(OnSupercritical);
|
||||||
|
SubscribeLocalEvent<CosmicEffigyComponent, AnomalyShutdownEvent>(OnAnomShutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAnomShutdown(Entity<CosmicEffigyComponent> ent, ref AnomalyShutdownEvent args)
|
||||||
|
{
|
||||||
|
if (args.Forced || args.Supercritical || !Exists(ent.Comp.Colossus) || !TryComp<CosmicColossusComponent>(ent.Comp.Colossus, out var colossusComp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
colossusComp.DeathTimer = _time.CurTime;
|
||||||
|
colossusComp.Timed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSupercritical(Entity<CosmicEffigyComponent> ent, ref AnomalySupercriticalEvent args)
|
||||||
|
{
|
||||||
|
if (!Exists(ent.Comp.Colossus)
|
||||||
|
|| !TryComp<CosmicColossusComponent>(ent.Comp.Colossus, out var colossusComp)
|
||||||
|
|| !_mind.TryGetMind(ent.Comp.Colossus.Value, out _, out var mind))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var colossus = ent.Comp.Colossus.Value;
|
||||||
|
|
||||||
|
var ev = new CosmicColossusEffigySupercriticalEvent(ent);
|
||||||
|
RaiseLocalEvent(colossus, ref ev);
|
||||||
|
|
||||||
|
var transform = Transform(ent.Comp.Colossus.Value);
|
||||||
|
Spawn(colossusComp.BuffVfx, transform.Coordinates);
|
||||||
|
|
||||||
|
if (colossusComp.CompletedEffigies == 0)
|
||||||
|
{
|
||||||
|
_audio.PlayStatic(colossusComp.ReawakenSfx,
|
||||||
|
Filter.BroadcastMap(transform.MapID),
|
||||||
|
transform.Coordinates,
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_audio.PlayPvs(colossusComp.ReawakenSfx, ent);
|
||||||
|
}
|
||||||
|
|
||||||
|
colossusComp.CompletedEffigies += 1;
|
||||||
|
|
||||||
|
if (colossusComp.CompletedEffigies >= colossusComp.MaxEffigies)
|
||||||
|
{
|
||||||
|
colossusComp.Timed = false;
|
||||||
|
_popup.PopupEntity(Loc.GetString("colossus-buff-final-popup"), colossus, PopupType.Large);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_popup.PopupEntity(Loc.GetString("colossus-buff-popup"), colossus, PopupType.Large);
|
||||||
|
|
||||||
|
var objIndex = mind.Objectives.FindIndex(HasComp<CosmicEffigyConditionComponent>);
|
||||||
|
if (objIndex == -1 ||
|
||||||
|
!TryComp<CosmicEffigyConditionComponent>(mind.Objectives[objIndex], out var conditionComp))
|
||||||
|
{
|
||||||
|
Log.Error($"Failed to find effigy objective on {ToPrettyString(colossus)}!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var objective = mind.Objectives[objIndex];
|
||||||
|
if (_cultObjective.RandomizeEffigyTarget(objective, conditionComp, setDescription: true) is not {} nextTarget)
|
||||||
|
{
|
||||||
|
Log.Error("Failed to randomize effigy objective location!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mind.UserId is { } userId)
|
||||||
|
{
|
||||||
|
_chat.DispatchServerMessage(_player.GetSessionById(userId), Loc.GetString("colossus-next-target", ("location", nextTarget)));
|
||||||
|
}
|
||||||
|
|
||||||
|
_codeCondition.SetCompleted(objective, false);
|
||||||
|
|
||||||
|
colossusComp.EffigyPlaceActionEntity = _actions.AddAction(colossus, colossusComp.EffigyPlaceAction);
|
||||||
|
colossusComp.DeathTimer = _time.CurTime + colossusComp.DeathWaitEffigy;
|
||||||
|
colossusComp.Timed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnColossusEffigy(Entity<CosmicColossusComponent> ent, ref EventCosmicColossusEffigy args)
|
private void OnColossusEffigy(Entity<CosmicColossusComponent> ent, ref EventCosmicColossusEffigy args)
|
||||||
@@ -38,8 +128,16 @@ public sealed class CosmicEffigySystem : EntitySystem
|
|||||||
|
|
||||||
_actions.RemoveAction(ent.Owner, ent.Comp.EffigyPlaceActionEntity);
|
_actions.RemoveAction(ent.Owner, ent.Comp.EffigyPlaceActionEntity);
|
||||||
_codeCondition.SetCompleted(ent.Owner, ent.Comp.EffigyObjective);
|
_codeCondition.SetCompleted(ent.Owner, ent.Comp.EffigyObjective);
|
||||||
Spawn(ent.Comp.EffigyPrototype, pos);
|
var effigy = Spawn(ent.Comp.EffigyPrototype, pos);
|
||||||
ent.Comp.Timed = false;
|
ent.Comp.Timed = false;
|
||||||
|
|
||||||
|
if (!TryComp<CosmicEffigyComponent>(effigy, out var effigyComp))
|
||||||
|
{
|
||||||
|
Log.Error("Colossus tried to place Effigy prototype missing CosmicEffigyComponent!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
effigyComp.Colossus = ent.Owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool VerifyPlacement(Entity<CosmicColossusComponent> ent, out EntityCoordinates outPos)
|
private bool VerifyPlacement(Entity<CosmicColossusComponent> ent, out EntityCoordinates outPos)
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using Content.Shared.Damage.Prototypes;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Server._DV.CosmicCult.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The owning colossus's attack rate will be multiplied by
|
||||||
|
/// the given multiplier once its effigy goes supercritical.
|
||||||
|
/// Component meant to be applied to Cosmic Colossus.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class MultiplyAttackRateOnSupercriticalComponent : Component
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public float Multiplier = 1.1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The owning colossus's corrupting speed will be multiplied by
|
||||||
|
/// the given multiplier once its effigy goes supercritical.
|
||||||
|
/// Component meant to be applied to Cosmic Colossus.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class MultiplyCorruptingSpeedOnSupercriticalComponent : Component
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public float Multiplier = 0.9f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The owning colossus will receive a flat bonus to
|
||||||
|
/// its melee attack damage once its effigy goes supercritical.
|
||||||
|
/// Component meant to be applied to Cosmic Colossus.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class FlatAttackBonusOnSupercriticalComponent : Component
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public FixedPoint2 BonusDamage = 10;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<DamageTypePrototype> BonusDamageType = "Blunt";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The owning colossus will be healed for `-CurrentDamage * Abs(DamageFractionHealed)` damage
|
||||||
|
/// once its effigy goes supercritical.
|
||||||
|
/// Component meant to be applied to Cosmic Colossus.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class HealOnSupercriticalComponent : Component
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public float DamageFractionHealed = 1.0f;
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Server._DV.CosmicCult.Abilities.Colossus;
|
||||||
using Content.Server._DV.CosmicCult.EntitySystems;
|
using Content.Server._DV.CosmicCult.EntitySystems;
|
||||||
using Content.Shared.Maps;
|
using Content.Shared.Maps;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -5,7 +6,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
|||||||
|
|
||||||
namespace Content.Server._DV.CosmicCult.Components;
|
namespace Content.Server._DV.CosmicCult.Components;
|
||||||
|
|
||||||
[RegisterComponent, Access(typeof(CosmicCorruptingSystem))]
|
[RegisterComponent, Access(typeof(CosmicCorruptingSystem), typeof(CosmicColossusBuffsSystem))]
|
||||||
[AutoGenerateComponentPause]
|
[AutoGenerateComponentPause]
|
||||||
public sealed partial class CosmicCorruptingComponent : Component
|
public sealed partial class CosmicCorruptingComponent : Component
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,6 +61,39 @@ public sealed partial class CosmicCultRuleComponent : Component
|
|||||||
[DataField]
|
[DataField]
|
||||||
public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(5);
|
public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(5);
|
||||||
|
|
||||||
|
#region Progress Checking
|
||||||
|
/// <summary>
|
||||||
|
/// The next time the monument should check the progress of the cult.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan NextProgressCheck = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of time between progress checks.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan TimeBetweenProgressChecks = TimeSpan.FromMinutes(15);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The progress value of the monument last time it was checked.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int LastProgress = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of times the the progress check as failed. Resets after some progress.
|
||||||
|
/// </summary>
|
||||||
|
public int ConsecutiveProgressFails = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of times the progress check has to fail for the round to start to end.
|
||||||
|
/// </summary>
|
||||||
|
public int ProgressFailureTolerance = 3;
|
||||||
|
|
||||||
|
public LocId ProgressFailTextShuttleCall = "cosmiccult-progress-fail-shuttle-call";
|
||||||
|
|
||||||
|
public LocId ProgressFailTextAnnouncement = "cosmiccult-progress-fail-announcement";
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
[DataField]
|
[DataField]
|
||||||
public HashSet<EntityUid> Cultists = [];
|
public HashSet<EntityUid> Cultists = [];
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Content.Server._DV.CosmicCult.Abilities;
|
||||||
|
using Content.Server._DV.CosmicCult.EntitySystems;
|
||||||
|
|
||||||
|
namespace Content.Server._DV.CosmicCult.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Component for cosmic effigy anomalies, spawned by Cosmic Colossi.
|
||||||
|
/// </summary>
|
||||||
|
/// <seelso cref="CosmicEffigySystem"/>.
|
||||||
|
/// <seelso cref="CosmicColossusSystem"/>.
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed partial class CosmicEffigyComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The colossus that placed this effigy.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public EntityUid? Colossus;
|
||||||
|
}
|
||||||
@@ -35,6 +35,12 @@ public sealed class CosmicCultObjectiveSystem : EntitySystem
|
|||||||
if (args.Cancelled || !_roles.MindHasRole<CosmicColossusRoleComponent>(args.MindId))
|
if (args.Cancelled || !_roles.MindHasRole<CosmicColossusRoleComponent>(args.MindId))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (RandomizeEffigyTarget(uid, comp) is null)
|
||||||
|
args.Cancelled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? RandomizeEffigyTarget(EntityUid uid, CosmicEffigyConditionComponent comp, bool setDescription = false)
|
||||||
|
{
|
||||||
var warps = new List<EntityUid>();
|
var warps = new List<EntityUid>();
|
||||||
var query = EntityQueryEnumerator<WarpPointComponent>();
|
var query = EntityQueryEnumerator<WarpPointComponent>();
|
||||||
var effigyBlacklist = comp.Blacklist;
|
var effigyBlacklist = comp.Blacklist;
|
||||||
@@ -51,10 +57,22 @@ public sealed class CosmicCultObjectiveSystem : EntitySystem
|
|||||||
|
|
||||||
if (warps.Count <= 0)
|
if (warps.Count <= 0)
|
||||||
{
|
{
|
||||||
args.Cancelled = true;
|
return null;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
comp.EffigyTarget = _random.Pick(warps);
|
|
||||||
|
var newWarp = _random.Pick(warps);
|
||||||
|
var warpComp = Comp<WarpPointComponent>(newWarp);
|
||||||
|
|
||||||
|
comp.EffigyTarget = newWarp;
|
||||||
|
|
||||||
|
if (setDescription)
|
||||||
|
{
|
||||||
|
_metaData.SetEntityDescription(uid,
|
||||||
|
warpComp.Location != null
|
||||||
|
? Loc.GetString("objective-condition-effigy", ("location", warpComp.Location))
|
||||||
|
: Loc.GetString("objective-condition-effigy-no-target"));
|
||||||
|
}
|
||||||
|
return warpComp.Location;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEffigyAfterAssign(EntityUid uid, CosmicEffigyConditionComponent comp, ref ObjectiveAfterAssignEvent args)
|
private void OnEffigyAfterAssign(EntityUid uid, CosmicEffigyConditionComponent comp, ref ObjectiveAfterAssignEvent args)
|
||||||
|
|||||||
@@ -62,13 +62,15 @@ using Robust.Shared.Random;
|
|||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Content.Shared.StatusIcon;
|
||||||
|
using Content.Shared.SSDIndicator;
|
||||||
|
|
||||||
namespace Content.Server._DV.CosmicCult;
|
namespace Content.Server._DV.CosmicCult;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Where all the main stuff for Cosmic Cultists happens.
|
/// Where all the main stuff for Cosmic Cultists happens.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponent>
|
public sealed partial class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly ActionsSystem _actions = default!;
|
[Dependency] private readonly ActionsSystem _actions = default!;
|
||||||
[Dependency] private readonly AlertsSystem _alerts = default!;
|
[Dependency] private readonly AlertsSystem _alerts = default!;
|
||||||
@@ -172,6 +174,28 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
|
|||||||
component.StewardVoteTimer = null;
|
component.StewardVoteTimer = null;
|
||||||
StewardVote();
|
StewardVote();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Progress check. Is the cult trying to progress at all?
|
||||||
|
if (component.MonumentPlaced && _timing.CurTime > component.NextProgressCheck)
|
||||||
|
{
|
||||||
|
// We're going to assume one monument.
|
||||||
|
var monument = EntityQuery<MonumentComponent>().First();
|
||||||
|
if (component.LastProgress == monument.CurrentProgress)
|
||||||
|
component.ConsecutiveProgressFails++;
|
||||||
|
else
|
||||||
|
component.ConsecutiveProgressFails = 0;
|
||||||
|
|
||||||
|
// They haven't progressed enough in X checks, end the round.
|
||||||
|
if (component.ConsecutiveProgressFails >= component.ProgressFailureTolerance) {
|
||||||
|
SetWinType((uid, component), WinType.CrewMinor);
|
||||||
|
_roundEnd.DoRoundEndBehavior(component.RoundEndBehavior, component.EvacShuttleTime, component.RoundEndTextSender, component.ProgressFailTextShuttleCall, component.ProgressFailTextAnnouncement);
|
||||||
|
component.RoundEndBehavior = RoundEndBehavior.Nothing;
|
||||||
|
}
|
||||||
|
|
||||||
|
component.LastProgress = monument.CurrentProgress;
|
||||||
|
component.NextProgressCheck = _timing.CurTime + component.TimeBetweenProgressChecks;
|
||||||
|
}
|
||||||
|
|
||||||
if (component.ExtraRiftTimer is { } riftTimer && _timing.CurTime >= riftTimer && !component.RiftStop)
|
if (component.ExtraRiftTimer is { } riftTimer && _timing.CurTime >= riftTimer && !component.RiftStop)
|
||||||
{
|
{
|
||||||
component.ExtraRiftTimer = _timing.CurTime + _rand.Next(TimeSpan.FromSeconds(230), TimeSpan.FromSeconds(360)); //3min50 to 6min between new rifts. Seconds instead of minutes for granularity.
|
component.ExtraRiftTimer = _timing.CurTime + _rand.Next(TimeSpan.FromSeconds(230), TimeSpan.FromSeconds(360)); //3min50 to 6min between new rifts. Seconds instead of minutes for granularity.
|
||||||
@@ -442,17 +466,19 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CultistsAlive()
|
private int GetCultistsAlive()
|
||||||
{
|
{
|
||||||
var query = EntityQueryEnumerator<CosmicCultComponent, MobStateComponent>();
|
var query = EntityQueryEnumerator<CosmicCultComponent, MobStateComponent>();
|
||||||
|
int cultistsAlive = 0;
|
||||||
while (query.MoveNext(out var ent, out _, out var mobComp))
|
while (query.MoveNext(out var ent, out _, out var mobComp))
|
||||||
{
|
{
|
||||||
if (TryComp<CuffableComponent>(ent, out var cuffed) && cuffed.CuffedHandCount > 0) continue;
|
if (TryComp<CuffableComponent>(ent, out var cuffed) && cuffed.CuffedHandCount > 0) continue;
|
||||||
|
if (TryComp<SSDIndicatorComponent>(ent, out var ssd) && ssd.IsSSD) continue; // TODO: Maybe check how long.
|
||||||
if (mobComp.Running && mobComp.CurrentState != MobState.Dead)
|
if (mobComp.Running && mobComp.CurrentState != MobState.Dead)
|
||||||
return true;
|
cultistsAlive++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return cultistsAlive;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMobStateChanged(Entity<CosmicCultComponent> ent, ref MobStateChangedEvent args)
|
private void OnMobStateChanged(Entity<CosmicCultComponent> ent, ref MobStateChangedEvent args)
|
||||||
@@ -467,14 +493,19 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
|
|||||||
|
|
||||||
private void CheckForActiveCultists()
|
private void CheckForActiveCultists()
|
||||||
{
|
{
|
||||||
if (CultistsAlive())
|
// A lone cultist can't do anything, so check for more than one.
|
||||||
|
if (GetCultistsAlive() > 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var query = QueryActiveRules();
|
var query = QueryActiveRules();
|
||||||
|
List<Entity<CosmicCultRuleComponent>> cosCultRules = new();
|
||||||
|
|
||||||
|
// ConfirmWinState needs to be outside the query, because it might add an active rule
|
||||||
while (query.MoveNext(out var ruleUid, out _, out var ruleComp, out _))
|
while (query.MoveNext(out var ruleUid, out _, out var ruleComp, out _))
|
||||||
{
|
{
|
||||||
ConfirmWinState((ruleUid, ruleComp));
|
cosCultRules.Add((ruleUid, ruleComp));
|
||||||
}
|
}
|
||||||
|
cosCultRules.ForEach(ConfirmWinState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConfirmWinState(Entity<CosmicCultRuleComponent> ent)
|
private void ConfirmWinState(Entity<CosmicCultRuleComponent> ent)
|
||||||
@@ -504,8 +535,6 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
|
|||||||
else
|
else
|
||||||
SetWinType(ent, WinType.Neutral); //The monument wasn't completed, no cultists escaped to midpoint. Some cultists still remain on the station, though.
|
SetWinType(ent, WinType.Neutral); //The monument wasn't completed, no cultists escaped to midpoint. Some cultists still remain on the station, though.
|
||||||
|
|
||||||
if (CultistsAlive()) return; //If there are no cultists alive, ignore all previous checks, crew alreay won.
|
|
||||||
|
|
||||||
if (tier <= 1) //Prevent the cult getting cooked by accident before anyone even knows there's a cult.
|
if (tier <= 1) //Prevent the cult getting cooked by accident before anyone even knows there's a cult.
|
||||||
{
|
{
|
||||||
SetWinType(ent, WinType.CrewMinor); //If we somehow get here at the end of the round, we'll still count this as a crew minor.
|
SetWinType(ent, WinType.CrewMinor); //If we somehow get here at the end of the round, we'll still count this as a crew minor.
|
||||||
@@ -521,7 +550,7 @@ public sealed class CosmicCultRuleSystem : GameRuleSystem<CosmicCultRuleComponen
|
|||||||
{
|
{
|
||||||
finComp.CurrentState = FinaleState.Unavailable;
|
finComp.CurrentState = FinaleState.Unavailable;
|
||||||
_popup.PopupCoordinates(Loc.GetString("cosmiccult-monument-powerdown"), Transform(gameruleMonument).Coordinates, PopupType.Large);
|
_popup.PopupCoordinates(Loc.GetString("cosmiccult-monument-powerdown"), Transform(gameruleMonument).Coordinates, PopupType.Large);
|
||||||
_sound.StopStationEventMusic(gameruleMonument, StationEventMusicType.CosmicCult);
|
_sound.StopGlobalEventMusic(StationEventMusicType.CosmicCult);
|
||||||
_monument.UpdateMonumentAppearance(gameruleMonument, false);
|
_monument.UpdateMonumentAppearance(gameruleMonument, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,11 +82,9 @@ public sealed partial class CosmicCultSystem : SharedCosmicCultSystem
|
|||||||
|
|
||||||
_corrupting.SetCorruptionTime((uid, corruptingComp), TimeSpan.FromSeconds(1));
|
_corrupting.SetCorruptionTime((uid, corruptingComp), TimeSpan.FromSeconds(1));
|
||||||
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 2);
|
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 2);
|
||||||
_sound.DispatchStationEventMusic(uid, comp.SelectedSong, StationEventMusicType.CosmicCult);
|
_sound.DispatchGlobalEventMusic(comp.SelectedSong, StationEventMusicType.CosmicCult);
|
||||||
_chatSystem.DispatchStationAnnouncement(uid,
|
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cosmiccult-finale-location", ("location", indicatedLocation)),
|
||||||
Loc.GetString("cosmiccult-finale-location", ("location", indicatedLocation)),
|
null, false, null, Color.FromHex("#cae8e8"));
|
||||||
null, false, null,
|
|
||||||
Color.FromHex("#cae8e8"));
|
|
||||||
|
|
||||||
var stationUid = _station.GetStationInMap(Transform(uid).MapID);
|
var stationUid = _station.GetStationInMap(Transform(uid).MapID);
|
||||||
if (stationUid != null)
|
if (stationUid != null)
|
||||||
@@ -128,8 +126,8 @@ public sealed partial class CosmicCultSystem : SharedCosmicCultSystem
|
|||||||
if (stationUid != null)
|
if (stationUid != null)
|
||||||
_alert.SetLevel(stationUid.Value, "green", true, true, true);
|
_alert.SetLevel(stationUid.Value, "green", true, true, true);
|
||||||
|
|
||||||
_sound.PlayGlobalOnStation(uid, _audio.ResolveSound(comp.CancelEventSound));
|
_sound.PlayGlobal(_audio.ResolveSound(comp.CancelEventSound));
|
||||||
_sound.StopStationEventMusic(uid, StationEventMusicType.CosmicCult);
|
_sound.StopGlobalEventMusic(StationEventMusicType.CosmicCult);
|
||||||
uid.Comp.CurrentState = FinaleState.ReadyFinale;
|
uid.Comp.CurrentState = FinaleState.ReadyFinale;
|
||||||
|
|
||||||
if (TryComp<CosmicCorruptingComponent>(uid, out var corruptingComp))
|
if (TryComp<CosmicCorruptingComponent>(uid, out var corruptingComp))
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using Content.Shared.Mind;
|
|||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
@@ -25,6 +26,7 @@ public sealed class CosmicChantrySystem : EntitySystem
|
|||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
[Dependency] private readonly SharedMindSystem _mind = default!;
|
[Dependency] private readonly SharedMindSystem _mind = default!;
|
||||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||||
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||||
[Dependency] private readonly NavMapSystem _navMap = default!;
|
[Dependency] private readonly NavMapSystem _navMap = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -79,11 +81,11 @@ public sealed class CosmicChantrySystem : EntitySystem
|
|||||||
comp.SpawnTimer = _timing.CurTime + comp.SpawningTime;
|
comp.SpawnTimer = _timing.CurTime + comp.SpawningTime;
|
||||||
comp.CountdownTimer = _timing.CurTime + comp.EventTime;
|
comp.CountdownTimer = _timing.CurTime + comp.EventTime;
|
||||||
|
|
||||||
_sound.PlayGlobalOnStation(ent, _audio.ResolveSound(comp.ChantryAlarm));
|
var targetMap = _transform.GetMapId(ent.Owner);
|
||||||
_chatSystem.DispatchStationAnnouncement(ent,
|
_sound.PlayGlobalOnMap(targetMap, _audio.ResolveSound(comp.ChantryAlarm));
|
||||||
Loc.GetString("cosmiccult-chantry-location", ("location", indicatedLocation)),
|
|
||||||
null, false, null,
|
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cosmiccult-chantry-location",
|
||||||
Color.FromHex("#cae8e8"));
|
("location", indicatedLocation)), null, false, null, Color.FromHex("#cae8e8"));
|
||||||
|
|
||||||
if (_mind.TryGetMind(comp.InternalVictim, out _, out var mind))
|
if (_mind.TryGetMind(comp.InternalVictim, out _, out var mind))
|
||||||
mind.PreventGhosting = true;
|
mind.PreventGhosting = true;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Content.Shared.Popups;
|
|||||||
using Content.Shared.Station.Components;
|
using Content.Shared.Station.Components;
|
||||||
using Content.Shared.Throwing;
|
using Content.Shared.Throwing;
|
||||||
using Content.Shared.Warps;
|
using Content.Shared.Warps;
|
||||||
|
using Content.Shared.Weapons.Melee.Events;
|
||||||
using Robust.Server.GameObjects;
|
using Robust.Server.GameObjects;
|
||||||
using Robust.Shared.Audio.Systems;
|
using Robust.Shared.Audio.Systems;
|
||||||
using Robust.Shared.Physics.Components;
|
using Robust.Shared.Physics.Components;
|
||||||
@@ -41,6 +42,7 @@ public sealed class CosmicColossusSystem : EntitySystem
|
|||||||
base.Initialize();
|
base.Initialize();
|
||||||
SubscribeLocalEvent<CosmicColossusComponent, ComponentInit>(OnSpawn);
|
SubscribeLocalEvent<CosmicColossusComponent, ComponentInit>(OnSpawn);
|
||||||
SubscribeLocalEvent<CosmicColossusComponent, MobStateChangedEvent>(OnMobStateChanged);
|
SubscribeLocalEvent<CosmicColossusComponent, MobStateChangedEvent>(OnMobStateChanged);
|
||||||
|
SubscribeLocalEvent<CosmicColossusComponent, MeleeHitEvent>(OnMeleeHit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
public override void Update(float frameTime)
|
||||||
@@ -82,7 +84,7 @@ public sealed class CosmicColossusSystem : EntitySystem
|
|||||||
|
|
||||||
private void OnSpawn(Entity<CosmicColossusComponent> ent, ref ComponentInit args) // I WANT THIS BIG GUY HURLED TOWARDS THE STATION
|
private void OnSpawn(Entity<CosmicColossusComponent> ent, ref ComponentInit args) // I WANT THIS BIG GUY HURLED TOWARDS THE STATION
|
||||||
{
|
{
|
||||||
ent.Comp.DeathTimer = _timing.CurTime + ent.Comp.DeathWait;
|
ent.Comp.DeathTimer = _timing.CurTime + ent.Comp.DeathWaitSpawn;
|
||||||
var station = _station.GetStationInMap(Transform(ent).MapID);
|
var station = _station.GetStationInMap(Transform(ent).MapID);
|
||||||
if (TryComp<StationDataComponent>(station, out var stationData))
|
if (TryComp<StationDataComponent>(station, out var stationData))
|
||||||
{
|
{
|
||||||
@@ -115,4 +117,9 @@ public sealed class CosmicColossusSystem : EntitySystem
|
|||||||
RemComp<WarpPointComponent>(ent);
|
RemComp<WarpPointComponent>(ent);
|
||||||
RemComp<CosmicCorruptingComponent>(ent);
|
RemComp<CosmicCorruptingComponent>(ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnMeleeHit(Entity<CosmicColossusComponent> colossus, ref MeleeHitEvent args)
|
||||||
|
{
|
||||||
|
args.BonusDamage += colossus.Comp.BonusDamage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,13 +79,13 @@ public sealed class MonumentSystem : SharedMonumentSystem
|
|||||||
{
|
{
|
||||||
comp.SongTimer = null;
|
comp.SongTimer = null;
|
||||||
if (comp.SelectedSong is { } song)
|
if (comp.SelectedSong is { } song)
|
||||||
_sound.DispatchStationEventMusic(uid, song, StationEventMusicType.CosmicCult);
|
_sound.DispatchGlobalEventMusic(song, StationEventMusicType.CosmicCult);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comp.CurrentState == FinaleState.ActiveFinale && comp.FinaleAnnounceCheck && comp.FinaleTimer - _timing.CurTime < comp.VisualsThreshold)
|
if (comp.CurrentState == FinaleState.ActiveFinale && comp.FinaleAnnounceCheck && comp.FinaleTimer - _timing.CurTime < comp.VisualsThreshold)
|
||||||
{
|
{
|
||||||
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 3);
|
_appearance.SetData(uid, MonumentVisuals.FinaleReached, 3);
|
||||||
_chatSystem.DispatchStationAnnouncement(uid, Loc.GetString("cosmiccult-announce-finale-warning"), null, false, null, Color.FromHex("#cae8e8"));
|
_chatSystem.DispatchGlobalAnnouncement(Loc.GetString("cosmiccult-announce-finale-warning"), null, false, null, Color.FromHex("#cae8e8"));
|
||||||
comp.FinaleAnnounceCheck = false;
|
comp.FinaleAnnounceCheck = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Content.Server._DV.Psionics.Systems;
|
using Content.Server._DV.Psionics.Systems;
|
||||||
using Content.Shared._DV.Psionics.Components;
|
using Content.Shared._DV.Psionics.Components;
|
||||||
|
using Content.Shared._DV.Traits.Assorted;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
using Content.Shared.Roles.Components;
|
using Content.Shared.Roles.Components;
|
||||||
@@ -17,10 +18,12 @@ public sealed partial class ParadoxCloneRuleSystem
|
|||||||
private void FilterTargets(HashSet<Entity<MindComponent>> minds)
|
private void FilterTargets(HashSet<Entity<MindComponent>> minds)
|
||||||
{
|
{
|
||||||
// TODO: use generic IMindFilter
|
// TODO: use generic IMindFilter
|
||||||
// no picking other antags or non-crew
|
// no picking other antags or non-crew and entities with no paradox clone trait
|
||||||
minds.RemoveWhere(mind => _role.MindIsAntagonist(mind) ||
|
minds.RemoveWhere(mind => _role.MindIsAntagonist(mind) ||
|
||||||
!_role.MindHasRole<JobRoleComponent>((mind, mind), out var role) ||
|
!_role.MindHasRole<JobRoleComponent>((mind, mind), out var role) ||
|
||||||
role?.Comp1.JobPrototype == null);
|
role?.Comp1.JobPrototype == null ||
|
||||||
|
(mind.Comp.OwnedEntity is { } entity && HasComp<NoParadoxCloneComponent>(entity))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PostClone(EntityUid mob)
|
private void PostClone(EntityUid mob)
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ public sealed class DeepFryerSystem : SharedDeepFryerSystem
|
|||||||
private void OnPowerChanged(Entity<DeepFryerComponent> ent, ref PowerChangedEvent args)
|
private void OnPowerChanged(Entity<DeepFryerComponent> ent, ref PowerChangedEvent args)
|
||||||
{
|
{
|
||||||
UpdateAppearance(ent);
|
UpdateAppearance(ent);
|
||||||
|
ResetCookingItemsStartTime(ent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnThrowHitBy(Entity<DeepFryerComponent> ent, ref ThrowHitByEvent args)
|
private void OnThrowHitBy(Entity<DeepFryerComponent> ent, ref ThrowHitByEvent args)
|
||||||
@@ -674,4 +675,26 @@ public sealed class DeepFryerSystem : SharedDeepFryerSystem
|
|||||||
// Add the split solution to the food
|
// Add the split solution to the food
|
||||||
Solution.AddSolution(foodSolutionEnt.Value, transferredSolution);
|
Solution.AddSolution(foodSolutionEnt.Value, transferredSolution);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the starttime for cooking items (on power change)
|
||||||
|
/// </summary>
|
||||||
|
private void ResetCookingItemsStartTime(Entity<DeepFryerComponent> ent)
|
||||||
|
{
|
||||||
|
if (_power.IsPowered(ent.Owner) && !ent.Comp.WasPreviouslyPowered)
|
||||||
|
{
|
||||||
|
// Power just came back on - reset all cooking timers
|
||||||
|
foreach (var itemUid in ent.Comp.CookingItems.Keys.ToList())
|
||||||
|
{
|
||||||
|
var cookingItem = ent.Comp.CookingItems[itemUid];
|
||||||
|
cookingItem.TimeStarted = _timing.CurTime;
|
||||||
|
ent.Comp.CookingItems[itemUid] = cookingItem;
|
||||||
|
}
|
||||||
|
ent.Comp.WasPreviouslyPowered = true;
|
||||||
|
}
|
||||||
|
else if (!_power.IsPowered(ent.Owner) && ent.Comp.WasPreviouslyPowered)
|
||||||
|
{
|
||||||
|
ent.Comp.WasPreviouslyPowered = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,212 +0,0 @@
|
|||||||
using Content.Server.Cloning;
|
|
||||||
using Content.Server.Mind;
|
|
||||||
using Content.Server.Station.Systems;
|
|
||||||
using Content.Shared._DV.Psionics.Components;
|
|
||||||
using Content.Shared._DV.Psionics.Components.PsionicPowers;
|
|
||||||
using Content.Shared._DV.Psionics.Systems.PsionicPowers;
|
|
||||||
using Content.Shared._DV.Species;
|
|
||||||
using Content.Shared.Bed.Sleep;
|
|
||||||
using Content.Shared.Body.Components;
|
|
||||||
using Content.Shared.Humanoid.Prototypes;
|
|
||||||
using Content.Shared.Mobs.Components;
|
|
||||||
using Content.Shared.Popups;
|
|
||||||
using Content.Shared.Preferences;
|
|
||||||
using Robust.Server.GameObjects;
|
|
||||||
using Robust.Server.GameStates;
|
|
||||||
using Robust.Server.Player;
|
|
||||||
using Robust.Shared.Audio.Systems;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Random;
|
|
||||||
|
|
||||||
namespace Content.Server._DV.Psionics.Systems.PsionicPowers;
|
|
||||||
|
|
||||||
public sealed class FracturedFormPowerSystem : SharedFracturedFormPowerSystem
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
|
||||||
[Dependency] private readonly IPlayerManager _player = default!;
|
|
||||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
|
||||||
[Dependency] private readonly CloningSystem _cloning = default!;
|
|
||||||
[Dependency] private readonly MindSystem _mind = default!;
|
|
||||||
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
|
|
||||||
[Dependency] private readonly StationSystem _station = default!;
|
|
||||||
[Dependency] private readonly TransformSystem _transform = default!;
|
|
||||||
[Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
|
|
||||||
|
|
||||||
// holy initialize performance? but better for it to happen once than the double dict lookup every tick!!
|
|
||||||
private EntityQuery<FracturedFormBodyComponent> _bodyQuery;
|
|
||||||
private EntityQuery<SleepingComponent> _sleepingQuery;
|
|
||||||
|
|
||||||
public override void Initialize()
|
|
||||||
{
|
|
||||||
base.Initialize();
|
|
||||||
|
|
||||||
_bodyQuery = GetEntityQuery<FracturedFormBodyComponent>();
|
|
||||||
_sleepingQuery = GetEntityQuery<SleepingComponent>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnPowerInit(Entity<FracturedFormPowerComponent> power, ref MapInitEvent args)
|
|
||||||
{
|
|
||||||
base.OnPowerInit(power, ref args);
|
|
||||||
|
|
||||||
// The next random swap is between 5 and 20 minutes.
|
|
||||||
var randomTime = Random.Next(power.Comp.NextSwapMinTime, power.Comp.NextSwapMaxTime);
|
|
||||||
power.Comp.NextSwap = Timing.CurTime + randomTime;
|
|
||||||
power.Comp.NextVoluntarySwap = Timing.CurTime + power.Comp.VoluntarySwapCooldown;
|
|
||||||
|
|
||||||
// Don't generate a new body if we're already part of a network.
|
|
||||||
if (HasComp<FracturedFormBodyComponent>(power))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Don't make bodies if there is no body. This is solely for test fails.
|
|
||||||
if (!HasComp<BodyComponent>(power))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var bodyComp = AddComp<FracturedFormBodyComponent>(power);
|
|
||||||
bodyComp.ControllingForm = power.Owner;
|
|
||||||
power.Comp.Bodies.Add(power);
|
|
||||||
var body = GenerateForm(power);
|
|
||||||
// hide the SSD indicator.
|
|
||||||
if (SsdQuery.TryComp(body, out var ssdComp))
|
|
||||||
ssdComp.IsSSD = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private EntityUid GenerateForm(Entity<FracturedFormPowerComponent> original)
|
|
||||||
{
|
|
||||||
// Form:
|
|
||||||
// - Same appearance as original
|
|
||||||
// - Different apperance, still humanoid
|
|
||||||
// Equipment:
|
|
||||||
// - Same as original body
|
|
||||||
// - Nude and helpless
|
|
||||||
|
|
||||||
var xform = Transform(original);
|
|
||||||
|
|
||||||
var hasGear = Random.Prob(original.Comp.HasGearChance);
|
|
||||||
|
|
||||||
if (Random.Prob(original.Comp.DifferentSpeciesChance) || !_cloning.TryCloning(original, _transform.GetMapCoordinates(original), hasGear ? original.Comp.CopyClothed : original.Comp.CopyNaked, out var newBody)) // Slightly lower chance to copy the original body
|
|
||||||
{
|
|
||||||
// Either the dice rolled poorly, or the cloning failed. Either way, make a new body instead. (Or try to)
|
|
||||||
var validSpecies = new List<ProtoId<SpeciesPrototype>>();
|
|
||||||
var speciesPrototypes = _prototype.EnumeratePrototypes<SpeciesPrototype>();
|
|
||||||
foreach (var proto in speciesPrototypes)
|
|
||||||
{
|
|
||||||
var speciesEntityPrototype = _prototype.Index<EntityPrototype>(proto.Prototype);
|
|
||||||
// If they have the PotentialPsionicComponent, they can be psionic.
|
|
||||||
if (proto.RoundStart && speciesEntityPrototype.TryGetComponent<PotentialPsionicComponent>(out _, Factory) && !SpeciesHiderSystem.IsHidden(proto.ID))
|
|
||||||
validSpecies.Add(proto.ID);
|
|
||||||
}
|
|
||||||
var species = Random.Pick(validSpecies);
|
|
||||||
var character = HumanoidCharacterProfile.RandomWithSpecies(species);
|
|
||||||
newBody = _stationSpawning.SpawnPlayerMob(xform.Coordinates, hasGear ? original.Comp.VisitorJob : original.Comp.NakedJob, character, _station.GetOwningStation(original.Owner));
|
|
||||||
if (newBody is not { } bodyV || Deleted(bodyV))
|
|
||||||
{
|
|
||||||
Log.Error($"Failed to create a new body for {ToPrettyString(original)}. This is a bug.");
|
|
||||||
return EntityUid.Invalid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newBody is not { } body || Deleted(body))
|
|
||||||
return default!;
|
|
||||||
|
|
||||||
var bodyComp = AddComp<FracturedFormBodyComponent>(body);
|
|
||||||
original.Comp.Bodies.Add(body);
|
|
||||||
bodyComp.ControllingForm = original.Owner;
|
|
||||||
|
|
||||||
if (_player.TryGetSessionByEntity(original, out var session))
|
|
||||||
_pvsOverride.AddSessionOverride(body, session);
|
|
||||||
|
|
||||||
Dirty(original);
|
|
||||||
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetValidBody(Entity<FracturedFormPowerComponent> psionic, out EntityUid validBody)
|
|
||||||
{
|
|
||||||
foreach (var body in psionic.Comp.Bodies)
|
|
||||||
{
|
|
||||||
if (!IsValidBody(psionic, body))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
validBody = body;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
validBody = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Swap(Entity<FracturedFormPowerComponent> psionic)
|
|
||||||
{
|
|
||||||
if (!TryGetValidBody(psionic, out var targetBody))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_audio.PlayPvs(psionic.Comp.SwapSound, psionic);
|
|
||||||
// Transfer mind if present
|
|
||||||
if (MindContainerQuery.TryComp(psionic, out var mindContainer) && mindContainer.Mind.HasValue)
|
|
||||||
_mind.TransferTo(mindContainer.Mind.Value, targetBody);
|
|
||||||
// Wake up the new body
|
|
||||||
Sleeping.TryWaking(targetBody);
|
|
||||||
// Remove the action.
|
|
||||||
Action.RemoveAction(psionic.Comp.ActionEntity);
|
|
||||||
// Create new component on target and copy data
|
|
||||||
var duplicate = EnsureComp<FracturedFormPowerComponent>(targetBody);
|
|
||||||
duplicate.Bodies = psionic.Comp.Bodies;
|
|
||||||
// Update all body references
|
|
||||||
foreach (var body in duplicate.Bodies)
|
|
||||||
{
|
|
||||||
if (_bodyQuery.TryComp(body, out var bodyComp))
|
|
||||||
bodyComp.ControllingForm = targetBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_player.TryGetSessionByEntity(targetBody, out var session))
|
|
||||||
{
|
|
||||||
_pvsOverride.AddSessionOverride(psionic, session);
|
|
||||||
_pvsOverride.RemoveSessionOverride(targetBody, session);
|
|
||||||
}
|
|
||||||
|
|
||||||
RemCompDeferred<FracturedFormPowerComponent>(psionic);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float frameTime)
|
|
||||||
{
|
|
||||||
base.Update(frameTime);
|
|
||||||
|
|
||||||
List<Entity<FracturedFormPowerComponent>> swapTargets = [];
|
|
||||||
|
|
||||||
var entities = EntityQueryEnumerator<FracturedFormPowerComponent, MobStateComponent>();
|
|
||||||
while (entities.MoveNext(out var uid, out var comp, out var mobState))
|
|
||||||
{
|
|
||||||
// Check sleep warning
|
|
||||||
if (!comp.SleepWarned && Timing.CurTime > comp.NextSwap - comp.WarningTimeBeforeSleep)
|
|
||||||
{
|
|
||||||
comp.SleepWarned = true;
|
|
||||||
Popup.PopupEntity(Loc.GetString("psionic-power-fractured-form-sleepy"), uid, uid, PopupType.LargeCaution);
|
|
||||||
Chat.TryEmoteWithChat(uid, "Yawn");
|
|
||||||
}
|
|
||||||
// Swap check
|
|
||||||
if ((_sleepingQuery.HasComp(uid) || MobState.IsIncapacitated(uid, mobState)) && Timing.CurTime > comp.NextVoluntarySwap
|
|
||||||
|| Timing.CurTime > comp.NextSwap)
|
|
||||||
swapTargets.Add((uid, comp));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var target in swapTargets)
|
|
||||||
{
|
|
||||||
Swap(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process bodies
|
|
||||||
var bodies = EntityQueryEnumerator<FracturedFormBodyComponent, MobStateComponent>();
|
|
||||||
while (bodies.MoveNext(out var uid, out var comp, out var mobState))
|
|
||||||
{
|
|
||||||
// Put to sleep if no sleeping component and no mind
|
|
||||||
if (!_sleepingQuery.HasComp(uid) && !_mind.GetMind(uid).HasValue && !FracturedQuery.HasComp(uid))
|
|
||||||
Sleeping.TrySleeping((uid, mobState));
|
|
||||||
// Cleanup invalid bodies
|
|
||||||
if (!comp.ControllingForm.IsValid()
|
|
||||||
|| Deleted(comp.ControllingForm)
|
|
||||||
|| !FracturedQuery.HasComp(comp.ControllingForm))
|
|
||||||
{
|
|
||||||
RemCompDeferred<FracturedFormBodyComponent>(uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
using Content.Server.Wires;
|
||||||
|
using Content.Shared._DV.Silicons.Borgs;
|
||||||
|
using Content.Shared._DV.Silicons;
|
||||||
|
using Content.Shared.Wires;
|
||||||
|
|
||||||
|
namespace Content.Server._DV.Silicons;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls whether the cyborg's ID chip slot is active
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class BorgIdChipWireAction : ComponentWireAction<IdChipSlotComponent>
|
||||||
|
{
|
||||||
|
public override string Name { get; set; } = "wire-name-borg-id-chip";
|
||||||
|
public override Color Color { get; set; } = Color.Thistle;
|
||||||
|
public override object StatusKey => BorgWireActionKey.ChipKey;
|
||||||
|
|
||||||
|
public override StatusLightState? GetLightState(Wire wire, IdChipSlotComponent component)
|
||||||
|
{
|
||||||
|
return component.Active ? StatusLightState.On : StatusLightState.Off;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Cut(EntityUid user, Wire wire, IdChipSlotComponent component)
|
||||||
|
{
|
||||||
|
EntityManager.System<IdChipSlotSystem>()
|
||||||
|
.SetActive((wire.Owner, component), false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Mend(EntityUid user, Wire wire, IdChipSlotComponent component)
|
||||||
|
{
|
||||||
|
EntityManager.System<IdChipSlotSystem>()
|
||||||
|
.SetActive((wire.Owner, component), true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Pulse(EntityUid user, Wire wire, IdChipSlotComponent component)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using Content.Server.Silicons.Borgs;
|
||||||
|
using Content.Server.Wires;
|
||||||
|
using Content.Shared._DV.Silicons;
|
||||||
|
using Content.Shared.Silicons.Borgs.Components;
|
||||||
|
using Content.Shared.Wires;
|
||||||
|
|
||||||
|
namespace Content.Server._DV.Silicons;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls whether the cyborg's transponder is active
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class BorgTransponderWireAction : ComponentWireAction<BorgTransponderComponent>
|
||||||
|
{
|
||||||
|
public override string Name { get; set; } = "wire-name-borg-transponder";
|
||||||
|
public override Color Color { get; set; } = Color.YellowGreen;
|
||||||
|
public override object StatusKey => BorgWireActionKey.TransponderKey;
|
||||||
|
|
||||||
|
public override StatusLightState? GetLightState(Wire wire, BorgTransponderComponent component)
|
||||||
|
{
|
||||||
|
return component.Active ? StatusLightState.On : StatusLightState.Off;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Cut(EntityUid user, Wire wire, BorgTransponderComponent component)
|
||||||
|
{
|
||||||
|
EntityManager.System<BorgSystem>()
|
||||||
|
.SetTransponderActive((wire.Owner, component), false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Mend(EntityUid user, Wire wire, BorgTransponderComponent component)
|
||||||
|
{
|
||||||
|
EntityManager.System<BorgSystem>()
|
||||||
|
.SetTransponderActive((wire.Owner, component), true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Pulse(EntityUid user, Wire wire, BorgTransponderComponent component)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ namespace Content.Server._DV.Silicons.Laws;
|
|||||||
public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||||
|
[Dependency] private readonly SharedSiliconLawSystem _siliconLaws = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -24,12 +25,12 @@ public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
|||||||
|
|
||||||
private void OnGetSiliconLaws(Entity<SlavedBorgComponent> ent, ref GetSiliconLawsEvent args)
|
private void OnGetSiliconLaws(Entity<SlavedBorgComponent> ent, ref GetSiliconLawsEvent args)
|
||||||
{
|
{
|
||||||
if (ent.Comp.Added || !TryComp<SiliconLawProviderComponent>(ent, out var provider))
|
if (ent.Comp.HasBeenAdded || !TryComp<SiliconLawProviderComponent>(ent, out var provider))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (provider.Lawset is {} lawset)
|
if (provider.Lawset is {} lawset && ent.Comp.ShouldBeAdded)
|
||||||
AddLaw(lawset, ent.Comp.Law);
|
AddLaw(lawset, ent.Comp.Law);
|
||||||
ent.Comp.Added = true; // prevent opening the ui adding more law 0's
|
ent.Comp.HasBeenAdded = true; // prevent opening the ui adding more law 0's
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,4 +40,30 @@ public sealed class SlavedBorgSystem : SharedSlavedBorgSystem
|
|||||||
{
|
{
|
||||||
lawset.Laws.Insert(0, _proto.Index(law).ShallowClone());
|
lawset.Laws.Insert(0, _proto.Index(law).ShallowClone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets whether the slaving is active.
|
||||||
|
/// </summary>
|
||||||
|
public void SetShouldBeAdded(Entity<SlavedBorgComponent?> ent, bool active)
|
||||||
|
{
|
||||||
|
if (!Resolve(ent, ref ent.Comp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!TryComp<SiliconLawProviderComponent>(ent, out var provider) || provider.Lawset is not { } lawset)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!ent.Comp.ShouldBeAdded && active)
|
||||||
|
{
|
||||||
|
AddLaw(lawset, ent.Comp.Law);
|
||||||
|
ent.Comp.HasBeenAdded = true;
|
||||||
|
_siliconLaws.NotifyLawsChanged(ent);
|
||||||
|
}
|
||||||
|
else if (ent.Comp.ShouldBeAdded && !active)
|
||||||
|
{
|
||||||
|
lawset.Laws.Remove(_proto.Index(ent.Comp.Law));
|
||||||
|
ent.Comp.HasBeenAdded = false;
|
||||||
|
_siliconLaws.NotifyLawsChanged(ent);
|
||||||
|
}
|
||||||
|
ent.Comp.ShouldBeAdded = active;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using Content.Server.Wires;
|
||||||
|
using Content.Shared._DV.Silicons;
|
||||||
|
using Content.Shared.PowerCell;
|
||||||
|
using Content.Shared.PowerCell.Components;
|
||||||
|
using Content.Shared.Wires;
|
||||||
|
|
||||||
|
namespace Content.Server._DV.Silicons;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Controls whether the power cell slot is active
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class PowerCellSlotWireAction : ComponentWireAction<PowerCellSlotComponent>
|
||||||
|
{
|
||||||
|
public override string Name { get; set; } = "wire-name-power-cell";
|
||||||
|
public override Color Color { get; set; } = Color.BurlyWood;
|
||||||
|
public override object StatusKey => BorgWireActionKey.CellKey;
|
||||||
|
|
||||||
|
public override StatusLightState? GetLightState(Wire wire, PowerCellSlotComponent component)
|
||||||
|
{
|
||||||
|
return component.Active ? StatusLightState.On : StatusLightState.Off;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Cut(EntityUid user, Wire wire, PowerCellSlotComponent component)
|
||||||
|
{
|
||||||
|
EntityManager.System<PowerCellSystem>()
|
||||||
|
.SetSlotActive((wire.Owner, component), false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Mend(EntityUid user, Wire wire, PowerCellSlotComponent component)
|
||||||
|
{
|
||||||
|
EntityManager.System<PowerCellSystem>()
|
||||||
|
.SetSlotActive((wire.Owner, component), true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Pulse(EntityUid user, Wire wire, PowerCellSlotComponent component)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user