mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
395 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec234b620c | ||
|
|
cb12772f28 | ||
|
|
fdd593cdd1 | ||
|
|
133301ea57 | ||
|
|
62b4714f1f | ||
|
|
1d0404953f | ||
|
|
d0da13f895 | ||
|
|
ff23f98b26 | ||
|
|
ccfef2a786 | ||
|
|
62ce9724fc | ||
|
|
3bbe0e7f44 | ||
|
|
addd8b5bdd | ||
|
|
03f8d4d3e0 | ||
|
|
728d541ca5 | ||
|
|
4cbce064b8 | ||
|
|
93bb7b1532 | ||
|
|
3ba91d2ed0 | ||
|
|
8f9e0f6bab | ||
|
|
3ed408de5d | ||
|
|
2f85408f8f | ||
|
|
f49b01b1b7 | ||
|
|
3ce764311d | ||
|
|
adf0b6ae78 | ||
|
|
04406311dc | ||
|
|
ee45a608b9 | ||
|
|
077c91a54b | ||
|
|
2ac17009ee | ||
|
|
a73d1b6666 | ||
|
|
e5d6f194be | ||
|
|
72d893dec5 | ||
|
|
191d7ab81c | ||
|
|
65d2f2dd2f | ||
|
|
02b451db2a | ||
|
|
d5d4584e11 | ||
|
|
b146b1b82c | ||
|
|
2f8f4f2f7a | ||
|
|
7365a59bd9 | ||
|
|
37560f663b | ||
|
|
bd489e9218 | ||
|
|
33166e8866 | ||
|
|
ed16032280 | ||
|
|
cf785c886b | ||
|
|
6f28c396cf | ||
|
|
b631f408f2 | ||
|
|
34f4cf9452 | ||
|
|
6a4e4cf3b4 | ||
|
|
8aefa5c53e | ||
|
|
2cfc981aa3 | ||
|
|
4987c324d9 | ||
|
|
5450ddd0ba | ||
|
|
378a10678c | ||
|
|
2e0735b92f | ||
|
|
5756d15333 | ||
|
|
b6f74b8dea | ||
|
|
3800c5707e | ||
|
|
8f49785b4e | ||
|
|
f274de0f10 | ||
|
|
e128338f9d | ||
|
|
588c46273e | ||
|
|
919de8ce0e | ||
|
|
af27d2d872 | ||
|
|
45bb8740a0 | ||
|
|
4a24539629 | ||
|
|
7536c4ec68 | ||
|
|
9268c8629d | ||
|
|
0bc0cafe64 | ||
|
|
8891f3fa0a | ||
|
|
4f96c2d233 | ||
|
|
ab55d5b2f2 | ||
|
|
806c5b694b | ||
|
|
6898053dbd | ||
|
|
ae625ebad8 | ||
|
|
3c754a4f49 | ||
|
|
d84cb6327c | ||
|
|
4bfd92dbc5 | ||
|
|
c7d228c223 | ||
|
|
f244c94905 | ||
|
|
01cac6465b | ||
|
|
5a5f238d9a | ||
|
|
089224cd44 | ||
|
|
9f807f1ad2 | ||
|
|
4be95ea375 | ||
|
|
03010bf4be | ||
|
|
dacaa974d4 | ||
|
|
9f73e0398a | ||
|
|
ccc383b1bf | ||
|
|
ceb59402a1 | ||
|
|
5a6b29fcd2 | ||
|
|
6b87cd1e1c | ||
|
|
cf2d6a1dbf | ||
|
|
f8c838f425 | ||
|
|
7405904041 | ||
|
|
2eeebab275 | ||
|
|
2856bb3626 | ||
|
|
be0189748b | ||
|
|
4529a7569a | ||
|
|
2b16e4db96 | ||
|
|
64f2245194 | ||
|
|
1029047e2f | ||
|
|
45dc9ad80e | ||
|
|
54ad808eea | ||
|
|
37c75df6a2 | ||
|
|
e93c1fae61 | ||
|
|
cd0a35f542 | ||
|
|
80f2dc6dd3 | ||
|
|
139b6f796c | ||
|
|
2ee7c35fd3 | ||
|
|
56eae5ad08 | ||
|
|
0662cae224 | ||
|
|
0e2b00edd0 | ||
|
|
d48f7ecb5b | ||
|
|
e064b7a4f9 | ||
|
|
654480862e | ||
|
|
353c044b52 | ||
|
|
3dda8d9e93 | ||
|
|
41ea10083d | ||
|
|
6290bb7af1 | ||
|
|
348ab70a8d | ||
|
|
47e11e988c | ||
|
|
c459b55052 | ||
|
|
f10e96a6d1 | ||
|
|
e8bac558c6 | ||
|
|
ffa3bb7202 | ||
|
|
9bcdc95651 | ||
|
|
543088ea1f | ||
|
|
56daa63783 | ||
|
|
9fe9730d4a | ||
|
|
a1a7ea92d9 | ||
|
|
0dec6a425f | ||
|
|
56ced913b7 | ||
|
|
76b46479b6 | ||
|
|
de9a8d286a | ||
|
|
a1df0fb4af | ||
|
|
e6bc5a1057 | ||
|
|
11b24579a2 | ||
|
|
685d002bb7 | ||
|
|
2e0d18aeaf | ||
|
|
06dbff0429 | ||
|
|
15958a9447 | ||
|
|
fd5a4d9b8a | ||
|
|
6d958847cb | ||
|
|
8a04a4f3a5 | ||
|
|
7104a4f459 | ||
|
|
f29949a32c | ||
|
|
3bbbabf238 | ||
|
|
d95aca3d9e | ||
|
|
e14537074e | ||
|
|
af2d01981f | ||
|
|
7df23e047c | ||
|
|
5c7ab43049 | ||
|
|
8f75560ec4 | ||
|
|
b323c8bd1e | ||
|
|
faef44daaa | ||
|
|
fbc706f37b | ||
|
|
9d1b15ab4b | ||
|
|
ea1cc5e446 | ||
|
|
bcb5c2d35d | ||
|
|
c011eff80e | ||
|
|
e163c496c3 | ||
|
|
fec81bc2a1 | ||
|
|
7016facb9a | ||
|
|
0c41a041e3 | ||
|
|
55571ef5b1 | ||
|
|
afaef645b0 | ||
|
|
d442d90d60 | ||
|
|
fea592e1d5 | ||
|
|
bb9517fd19 | ||
|
|
a734bc50fa | ||
|
|
9e9ac56c95 | ||
|
|
6979a63b1e | ||
|
|
ae7725aafe | ||
|
|
1a7e490e4b | ||
|
|
51971d0994 | ||
|
|
d2aa8ecb5a | ||
|
|
c4a5752c2a | ||
|
|
6a336d236b | ||
|
|
fc55c8e0d3 | ||
|
|
2719b9f0c8 | ||
|
|
bd69d51d36 | ||
|
|
1bf0687671 | ||
|
|
bdef9e3401 | ||
|
|
43648201ce | ||
|
|
2b2d08ba47 | ||
|
|
d7f6a9ba43 | ||
|
|
15f81751f7 | ||
|
|
033c52751a | ||
|
|
51edceae4d | ||
|
|
5d7720755a | ||
|
|
acc7bf7595 | ||
|
|
de55d1bc52 | ||
|
|
d5e6e91b58 | ||
|
|
da2bfdaa10 | ||
|
|
af6cac14d6 | ||
|
|
3f37846731 | ||
|
|
d9bf1d1afb | ||
|
|
b9b80192e7 | ||
|
|
e03aec47ef | ||
|
|
ee906af16e | ||
|
|
e205ae3627 | ||
|
|
d818c5aa0c | ||
|
|
aa8fe8ac92 | ||
|
|
4ba6687b9d | ||
|
|
8f2817aa4e | ||
|
|
89c7839fe2 | ||
|
|
9799132001 | ||
|
|
34ffa56c57 | ||
|
|
f8410a4674 | ||
|
|
43b991c690 | ||
|
|
c463fc5e78 | ||
|
|
e21b3e069a | ||
|
|
c8f94ab40d | ||
|
|
2a882b5555 | ||
|
|
32d8a1cba9 | ||
|
|
5d84be9c78 | ||
|
|
eaaa70437a | ||
|
|
448e8b0c2c | ||
|
|
42948d8f8e | ||
|
|
039468f4b6 | ||
|
|
6a3f88b1c6 | ||
|
|
a314c5f797 | ||
|
|
bcc4cd77cf | ||
|
|
941cb4c1d6 | ||
|
|
7b58760331 | ||
|
|
f0306b593a | ||
|
|
5e1935c310 | ||
|
|
67c44a5fc5 | ||
|
|
3ea0a0244b | ||
|
|
325a39ee4b | ||
|
|
e4190f4f29 | ||
|
|
4d163ed818 | ||
|
|
09c6a816e0 | ||
|
|
dfc4894c8b | ||
|
|
f2b096f145 | ||
|
|
1984e97d2f | ||
|
|
57291b88c0 | ||
|
|
f40dd51648 | ||
|
|
d08fdd3a18 | ||
|
|
80dbf02af4 | ||
|
|
a2983a5ee0 | ||
|
|
7810cd0c2e | ||
|
|
6c8b863731 | ||
|
|
c2ca7c7811 | ||
|
|
347d240fae | ||
|
|
87a5745519 | ||
|
|
e47ba0faea | ||
|
|
fb705702fb | ||
|
|
a2aec44ebb | ||
|
|
5e97db435c | ||
|
|
9af119f57a | ||
|
|
6247be2c84 | ||
|
|
acb1d37b99 | ||
|
|
82c94fc8b0 | ||
|
|
9837c33de7 | ||
|
|
ac30ad1820 | ||
|
|
9c30fdf5fd | ||
|
|
0b7e8c2560 | ||
|
|
7982aa236c | ||
|
|
0559339143 | ||
|
|
89fcd1dd2b | ||
|
|
649378e59a | ||
|
|
0c7ace16d1 | ||
|
|
27f7f5ee36 | ||
|
|
fe0fcbd851 | ||
|
|
aca7847933 | ||
|
|
1621d25a92 | ||
|
|
b7e0a9bc03 | ||
|
|
9909416006 | ||
|
|
c3e487b61c | ||
|
|
89ad8b6c9f | ||
|
|
efbc9ef2bf | ||
|
|
ce240773e8 | ||
|
|
8563466011 | ||
|
|
af4d53fb54 | ||
|
|
3086fc446c | ||
|
|
5f3a54376d | ||
|
|
9bb7af364e | ||
|
|
92b0e7f1a8 | ||
|
|
47d1c372b2 | ||
|
|
453f763128 | ||
|
|
65a7942d63 | ||
|
|
f1f3c60d1f | ||
|
|
d4e8a27c23 | ||
|
|
18a17da8fa | ||
|
|
90a8c66e96 | ||
|
|
45c14b2bc3 | ||
|
|
d227613997 | ||
|
|
7557cc703c | ||
|
|
7b81d0d881 | ||
|
|
b59f7801ac | ||
|
|
d724c5b3eb | ||
|
|
f812dc4dac | ||
|
|
2a1bcb6f1e | ||
|
|
fa9030e59c | ||
|
|
8dcae8631b | ||
|
|
21c3535486 | ||
|
|
4e100d96bc | ||
|
|
14d3699ae2 | ||
|
|
350fa8736d | ||
|
|
5a82df216d | ||
|
|
32bca7cfd4 | ||
|
|
008babebc6 | ||
|
|
c65c4ba57e | ||
|
|
eb5b838e61 | ||
|
|
6b43036c9d | ||
|
|
f23a55793d | ||
|
|
46143d2589 | ||
|
|
ba7d1452c1 | ||
|
|
1c1343466e | ||
|
|
0d534e8bcd | ||
|
|
c83c6f9592 | ||
|
|
c794bd84bf | ||
|
|
d1d43f834b | ||
|
|
9505cb68df | ||
|
|
9763f5fdf4 | ||
|
|
80a963ec05 | ||
|
|
3a670ec25e | ||
|
|
e45950a557 | ||
|
|
6f1427ef3c | ||
|
|
9a7d1a39c1 | ||
|
|
9be903ee56 | ||
|
|
72f9f9c343 | ||
|
|
3ad760a99e | ||
|
|
e2f3722ce9 | ||
|
|
b4beca6562 | ||
|
|
ea02260230 | ||
|
|
3b243e487d | ||
|
|
f40ccb7558 | ||
|
|
f467a7027b | ||
|
|
c9d7d442d9 | ||
|
|
342626ad9b | ||
|
|
1c3ea968e4 | ||
|
|
f0ed3537ee | ||
|
|
74e7e61a98 | ||
|
|
fb9b0ae89b | ||
|
|
dbe297b1fc | ||
|
|
b84917e8e4 | ||
|
|
abb3f65fe4 | ||
|
|
41ec2dc131 | ||
|
|
e714dcc83c | ||
|
|
46291af1be | ||
|
|
ad929c9955 | ||
|
|
c86cb0b795 | ||
|
|
8d03feb84f | ||
|
|
0fa21ee2d2 | ||
|
|
9be0f032e8 | ||
|
|
afffb33446 | ||
|
|
19a87fb67a | ||
|
|
2fda62a274 | ||
|
|
5218bf70b0 | ||
|
|
4f95c07ab3 | ||
|
|
786acae47a | ||
|
|
f81e30a031 | ||
|
|
f5c1d870f9 | ||
|
|
4949b34c88 | ||
|
|
0f60ad9018 | ||
|
|
f7287b181d | ||
|
|
45b7500d93 | ||
|
|
dbe6f65880 | ||
|
|
4faef1bfd3 | ||
|
|
48d70a09c6 | ||
|
|
f682fb9cc7 | ||
|
|
814e5bcf17 | ||
|
|
dbc4e80e61 | ||
|
|
5eb5ddd96e | ||
|
|
405ed378c0 | ||
|
|
be9db264dd | ||
|
|
2f73f6190d | ||
|
|
f3dfa1f666 | ||
|
|
b0d17e9527 | ||
|
|
4c81e68bf1 | ||
|
|
4490751001 | ||
|
|
bc8d2c154c | ||
|
|
3c83f8e62a | ||
|
|
c36919d76a | ||
|
|
70a853cdd5 | ||
|
|
fd3eb092cc | ||
|
|
c740026014 | ||
|
|
44f9262d1a | ||
|
|
df2160b151 | ||
|
|
5c7b1e6823 | ||
|
|
eaf7a6ba0f | ||
|
|
9ab4286592 | ||
|
|
3f02ef3730 | ||
|
|
2f17cbb1dc | ||
|
|
c2657812f5 | ||
|
|
f17f077849 | ||
|
|
306deddbd2 | ||
|
|
cdd8df743a | ||
|
|
e7ac5ad047 | ||
|
|
0e621a26be | ||
|
|
bbcc7cfe1f | ||
|
|
1208c25dcd | ||
|
|
38c227b692 | ||
|
|
4e73d72753 | ||
|
|
b1e1a0cd88 |
44
.github/workflows/build-docfx.yml
vendored
44
.github/workflows/build-docfx.yml
vendored
@@ -5,30 +5,30 @@ on:
|
||||
- cron: "0 0 * * 0"
|
||||
jobs:
|
||||
docfx:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
- name: Build Project
|
||||
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
|
||||
- name: Build Project
|
||||
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
|
||||
|
||||
- name: Build DocFX
|
||||
uses: nikeee/docfx-action@v1.0.0
|
||||
with:
|
||||
args: Robust.Docfx/docfx.json
|
||||
- name: Build DocFX
|
||||
uses: nikeee/docfx-action@v1.0.0
|
||||
with:
|
||||
args: Robust.Docfx/docfx.json
|
||||
|
||||
- name: Publish Docfx Documentation on GitHub Pages
|
||||
uses: maxheld83/ghpages@master
|
||||
env:
|
||||
BUILD_DIR: Robust.Docfx/_robust-site
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
- name: Publish Docfx Documentation on GitHub Pages
|
||||
uses: maxheld83/ghpages@master
|
||||
env:
|
||||
BUILD_DIR: Robust.Docfx/_robust-site
|
||||
GH_PAT: ${{ secrets.GH_PAT }}
|
||||
|
||||
37
.github/workflows/build-test.yml
vendored
37
.github/workflows/build-test.yml
vendored
@@ -2,33 +2,32 @@ name: Build & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest ] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
|
||||
os: [ubuntu-latest, windows-latest] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
|
||||
- name: Robust.UnitTesting
|
||||
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0
|
||||
- name: Robust.Analyzers.Tests
|
||||
run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
|
||||
- name: Robust.UnitTesting
|
||||
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0
|
||||
- name: Robust.Analyzers.Tests
|
||||
run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0
|
||||
|
||||
78
.github/workflows/codeql-analysis.yml
vendored
78
.github/workflows/codeql-analysis.yml
vendored
@@ -11,14 +11,8 @@
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
#on:
|
||||
# push:
|
||||
# branches: [ master ]
|
||||
# pull_request:
|
||||
# # The branches below must be a subset of the branches above
|
||||
# branches: [ master ]
|
||||
# schedule:
|
||||
# - cron: '30 18 * * 6'
|
||||
on:
|
||||
workflow_dispatch
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
@@ -28,50 +22,50 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'csharp' ]
|
||||
language: ["csharp"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
|
||||
73
.github/workflows/publish-client.yml
vendored
73
.github/workflows/publish-client.yml
vendored
@@ -3,51 +3,50 @@
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Parse version
|
||||
id: parse_version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ver = [regex]::Match($env:GITHUB_REF, "refs/tags/v?(.+)").Groups[1].Value
|
||||
echo ("::set-output name=version::{0}" -f $ver)
|
||||
- name: Parse version
|
||||
id: parse_version
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ver = [regex]::Match($env:GITHUB_REF, "refs/tags/v?(.+)").Groups[1].Value
|
||||
echo ("::set-output name=version::{0}" -f $ver)
|
||||
|
||||
- uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
submodules: true
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Package client
|
||||
run: Tools/package_client_build.py -p windows mac linux
|
||||
- name: Package client
|
||||
run: Tools/package_client_build.py -p windows mac linux
|
||||
|
||||
- name: Shuffle files around
|
||||
run: |
|
||||
mkdir "release/${{ steps.parse_version.outputs.version }}"
|
||||
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
|
||||
- name: Shuffle files around
|
||||
run: |
|
||||
mkdir "release/${{ steps.parse_version.outputs.version }}"
|
||||
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
|
||||
|
||||
- name: Upload files to Suns
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
source: "release/${{ steps.parse_version.outputs.version }}"
|
||||
target: "/var/lib/robust-builds/builds/"
|
||||
strip_components: 1
|
||||
|
||||
- name: Update manifest JSON
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}
|
||||
- name: Upload files to Suns
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
source: "release/${{ steps.parse_version.outputs.version }}"
|
||||
target: "/var/lib/robust-builds/builds/"
|
||||
strip_components: 1
|
||||
|
||||
- name: Update manifest JSON
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}
|
||||
|
||||
55
.github/workflows/test-content.yml
vendored
55
.github/workflows/test-content.yml
vendored
@@ -2,40 +2,39 @@ name: Test content master against engine
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out content
|
||||
uses: actions/checkout@v3.6.0
|
||||
with:
|
||||
repository: space-wizards/space-station-14
|
||||
submodules: recursive
|
||||
- name: Check out content
|
||||
uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
repository: space-wizards/space-station-14
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Disable submodule autoupdate
|
||||
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
- name: Disable submodule autoupdate
|
||||
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
|
||||
|
||||
- name: Check out engine version
|
||||
run: |
|
||||
cd RobustToolbox
|
||||
git fetch origin ${{ github.sha }}
|
||||
git checkout FETCH_HEAD
|
||||
git submodule update --init --recursive
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --configuration Tools --no-restore
|
||||
- name: Content.Tests
|
||||
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
|
||||
- name: Content.IntegrationTests
|
||||
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
- name: Check out engine version
|
||||
run: |
|
||||
cd RobustToolbox
|
||||
git fetch origin ${{ github.sha }}
|
||||
git checkout FETCH_HEAD
|
||||
git submodule update --init --recursive
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --configuration Tools --no-restore
|
||||
- name: Content.Tests
|
||||
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
|
||||
- name: Content.IntegrationTests
|
||||
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
|
||||
@@ -10,66 +10,69 @@
|
||||
<ManagePackageVersionsCentrally />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageVersion Include="ILReader.Core" Version="1.0.0.4" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
|
||||
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.0" />
|
||||
<PackageVersion Include="JetBrains.Annotations" Version="2024.3.0" />
|
||||
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.8" />
|
||||
<PackageVersion Include="Linguini.Bundle" Version="0.8.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit" Version="1.1.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.CodeCoverage" Version="17.8.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.2" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.2" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.2" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.12.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.12.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.12.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.12.0" />
|
||||
<PackageVersion Include="Microsoft.CodeCoverage" Version="17.12.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.DotNet.RemoteExecutor" Version="8.0.0-beta.24059.4" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.ILVerification" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.ILVerification" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="9.0.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||
<PackageVersion Include="Moq" Version="4.20.70" />
|
||||
<PackageVersion Include="NUnit" Version="4.0.1" />
|
||||
<PackageVersion Include="NUnit.Analyzers" Version="3.10.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
<PackageVersion Include="Moq" Version="4.20.72" />
|
||||
<PackageVersion Include="NUnit" Version="4.3.2" />
|
||||
<PackageVersion Include="NUnit.Analyzers" Version="4.5.0" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||
<PackageVersion Include="Nett" Version="0.15.0" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
|
||||
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageVersion Include="Pidgin" Version="3.2.2" />
|
||||
<PackageVersion Include="Pidgin" Version="3.3.0" />
|
||||
<PackageVersion Include="Robust.Natives" Version="0.1.1" />
|
||||
<PackageVersion Include="Robust.Natives.Cef" Version="120.1.9" />
|
||||
<PackageVersion Include="Robust.Natives.Cef" Version="131.3.5" />
|
||||
<PackageVersion Include="Robust.Shared.AuthLib" Version="0.1.2" />
|
||||
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.7" />
|
||||
<PackageVersion Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.7" />
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.10" />
|
||||
<PackageVersion Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.10" />
|
||||
<PackageVersion Include="Serilog" Version="4.2.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageVersion Include="System.Memory" Version="4.5.5" />
|
||||
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.5" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />
|
||||
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
|
||||
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
|
||||
<PackageVersion Include="YamlDotNet" Version="13.7.1" />
|
||||
<PackageVersion Include="YamlDotNet" Version="16.3.0" />
|
||||
<PackageVersion Include="prometheus-net" Version="8.2.1" />
|
||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||
<PackageVersion Include="PolySharp" Version="1.14.1" />
|
||||
<PackageVersion Include="PolySharp" Version="1.15.0" />
|
||||
|
||||
<!-- Transitive deps that we need to pin versions for to avoid NuGet warnings. -->
|
||||
<PackageVersion Include="System.Formats.Asn1" Version="9.0.0" />
|
||||
<PackageVersion Include="System.Reflection.Metadata" Version="9.0.0" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.12.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<LangVersion>13.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
<!-- Engine-specific properties. Content should not use this file. -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>13</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Import this at the end of any project files in Robust and Content. -->
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -71,6 +71,6 @@
|
||||
</PropertyGroup>
|
||||
<Exec
|
||||
Condition="'$(_RobustUseExternalMSBuild)' == 'true'"
|
||||
Command=""$(DOTNET_HOST_PATH)" msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileRobustXaml /p:_RobustForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
|
||||
Command=""$(DOTNET_HOST_PATH)" msbuild /nodereuse:false $(MSBuildProjectFile) /t:CompileRobustXaml /p:_RobustForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:RuntimeIdentifier=$(RuntimeIdentifier) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false /p:IntermediateOutputPath="$(IntermediateOutputPath.TrimEnd('\'))/""/>
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
Submodule NetSerializer updated: 7f51deaeca...4882400f2c
836
RELEASE-NOTES.md
836
RELEASE-NOTES.md
@@ -54,6 +54,840 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 255.1.2
|
||||
|
||||
|
||||
## 255.1.1
|
||||
|
||||
|
||||
## 255.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* The client localisation manager now supports hot-reloading ftl files.
|
||||
* TransformSystem can now raise `GridUidChangedEvent` and `MapUidChangedEvent` when a entity's grid or map changes. This event is only raised if the `ExtraTransformEvents` metadata flag is enabled.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a server crash due to a `NullReferenceException` in PVS system when a player's local entity is also one of their view subscriptions.
|
||||
* Fix CompileRobustXamlTask for benchmarks.
|
||||
* .ftl files will now hot reload.
|
||||
* Fix placementmanager sometimes not clearing.
|
||||
|
||||
### Other
|
||||
|
||||
* Container events are now documented.
|
||||
|
||||
|
||||
## 255.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `RobustIntegrationTest` now pools server/client instances by default. If a custom settings class is provided, it will still disable pooling unless explicitly enabled.
|
||||
* Server/Client instances that are returned to the pool should be disconnected. This might require you to update some tests.
|
||||
* Pooled instances also require you to use `RobustIntegrationTest` methods like `WaitPost()` to ensure the correct thread is used.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `EntityDeserializer` improperly setting entity lifestages when loading a post-mapinit map.
|
||||
* Fix `EntityManager.PredictedDeleteEntity()` not deleting pure client-side entities.
|
||||
* Fix grid fixtures using a locale dependent id. This could cause some clients to crash/freeze when connected to a server with a different locale.
|
||||
|
||||
### Other
|
||||
|
||||
* Add logic to block cycles in master MIDI renderers, which could otherwise cause client freezes.
|
||||
|
||||
|
||||
## 254.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add CC ND licences to the RGA validator.
|
||||
* Add entity spawn prediction and entity deletion prediction. This is currently limited as you are unable to predict interactions with these entities. These are done via the new methods prefixed with "Predicted". You can also manually flag an entity as a predicted spawn with the `FlagPredicted` method which will clean it up when prediction is reset.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix tile edge rendering for neighbor tiles being the same priority.
|
||||
|
||||
### Other
|
||||
|
||||
* Fix SpawnAttachedTo's system proxy method not the rotation arg like EntityManager.
|
||||
|
||||
|
||||
## 254.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Yaml mappings/dictionaries now only support string keys instead of generic nodes
|
||||
* Several MappingDataNode method arguments or return values now use strings instead of a DataNode object
|
||||
* The MappingDataNode class has various helper methods that still accept a ValueDataNode, but these methods are marked as obsolete and may be removed in the future.
|
||||
* yaml validators should use `MappingDataNode.GetKeyNode()` when validating mapping keys, so that errors can print node start & end information
|
||||
* ValueTuple yaml serialization has changed
|
||||
* Previously they would get serialized into a single mapping with one entry (i.e., `{foo : bar }`)
|
||||
* Now they serialize into a sequence (i.e., `[foo, bar]`)
|
||||
* The ValueTuple serializer will still try to read mappings, but due to the MappingDataNode this may fail if the previously serialized "key" can't be read as a simple string
|
||||
|
||||
### New features
|
||||
|
||||
* Add cvar to disable tile edges.
|
||||
* Add GetContainingContainers method to ContainerSystem to recursively get containers upwards on an entity.
|
||||
|
||||
### Internal
|
||||
|
||||
* Make component lifecycle methods use generics.
|
||||
|
||||
|
||||
## 253.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add a new `SerializationManager.PushComposition()` overload that takes in a single parent instead of an array of parents.
|
||||
* `BoundUserInterfaceMessageAttempt` once again gets raised as a broadcast event, in addition to being directed.
|
||||
* This effectively reverts the breaking part of the changes made in v252.0.0
|
||||
* Fix CreateDistanceJoint using an int instead of a float for minimum distance.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix deferred component removal not setting the component's life stage to `ComponentLifeStage.Stopped` if the component has not yet been initialised.
|
||||
* Fix some `EntitySystem.Resolve()` overloads not respecting the optional `logMissing` argument.
|
||||
* Fix screen-space overlays not being useable without first initializing/starting entity manager & systems
|
||||
* ItemList is now significantly optimized. VV's `AddComponent` window in particular should be much faster.
|
||||
* Fix some more MapValidator fields.
|
||||
* Fix popup text overflowing the sides of the screen.
|
||||
* Improve location reporting for non-writeable datafields via analyzer.
|
||||
|
||||
### Other
|
||||
|
||||
* TestPoint now uses generics rather than IPhysShape directly.
|
||||
|
||||
|
||||
## 252.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* BoundUserInterfaceMessageAttempt is raised directed against entities and no longer broadcast.
|
||||
|
||||
|
||||
## 251.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Localization is now separate between client and server and is handled via cvar.
|
||||
* Contacting entities no longer can be disabled for CollisionWake to avoid destroying the contacts unnecessarily.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `DirectionExtensions.AllDirections`, which contains a list of all `Direction`s for easy enumeration.
|
||||
* Add ForbidLiteralAttribute.
|
||||
* Log late MsgEntity again.
|
||||
* Show entity name in `physics shapeinfo` output.
|
||||
* Make SubscribeLocalEvent not require EntityEventArgs.
|
||||
* Add autocomplete to `tp` command.
|
||||
* Add button to jump to live chat when scrolled up.
|
||||
* Add autocomplete to `savemap` and `savegrid`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix velocity not re-applying correctly on re-parenting.
|
||||
* Fix Equatable on FormattedMessage.
|
||||
* Fix SharedTransformSystem methods logging errors on resolves.
|
||||
|
||||
### Other
|
||||
|
||||
* Significantly optimized tile edge rendering.
|
||||
|
||||
### Internal
|
||||
|
||||
* Remove duplicate GetMassData method.
|
||||
* Inline manifold points for physics.
|
||||
|
||||
|
||||
## 250.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The default shader now interprets negative color modulation as a flag that indicates that the light map should be ignored.
|
||||
* This can be used to avoid having to change the light map texture, thus reducing draw batches.
|
||||
* Sprite layers that are set to use the "unshaded" shader prototype now use this.
|
||||
* Any fragment shaders that previously the `VtxModulate` colour modulation variable should instead use the new `MODULATE` variable, as the former may now contain negative values.
|
||||
|
||||
### New features
|
||||
|
||||
* Add OtherBody API to contacts.
|
||||
* Make FormattedMessages Equatable.
|
||||
* AnimationCompletionEvent now has the AnimationPlayerComponent.
|
||||
* Add entity description as a tooltip on the entity spawn panel.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix serialization source generator breaking if a class has two partial locations.
|
||||
* Fix map saving throwing a `DirectoryNotFoundException` when given a path with a non-existent directory. Now it once again creates any missing directories.
|
||||
* Fix map loading taking a significant time due to MappingDataNode.Equals calls being slow.
|
||||
|
||||
### Other
|
||||
|
||||
* Add Pure to some Angle methods.
|
||||
|
||||
### Internal
|
||||
|
||||
* Cleanup some warnings in classes.
|
||||
|
||||
|
||||
## 249.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Layer is now read-only on VisibilityComponent and isn't serialized.
|
||||
|
||||
### New features
|
||||
|
||||
* Added a debug overlay for the linear and angular velocity of all entities on the screen. Use the `showvel` and `showangvel` commands to toggle it.
|
||||
* Add a GetWorldManifold overload that doesn't require a span of points.
|
||||
* Added a GetVisMaskEvent. Calling `RefreshVisibilityMask` will raise it and subscribers can update the vismask via the event rather than subscribers having to each manually try and handle the vismask directly.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `BoxContainer` no longer causes stretching children to go below their minimum size.
|
||||
* Fix lights on other grids getting clipped due to ignoring the light range cvar.
|
||||
* Fix the `showvelocities` command.
|
||||
* Fix the DirtyFields overload not being sandbox safe for content.
|
||||
|
||||
### Internal
|
||||
|
||||
* Polygon vertices are now inlined with FixedArray8 and a separate SlimPolygon using FixedArray4 for hot paths rather than using pooled arrays.
|
||||
|
||||
|
||||
## 248.0.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Don't throw in overlay rendering if MapUid not found.
|
||||
|
||||
### Internal
|
||||
|
||||
* Reduce EntityManager.IsDefault allocations.
|
||||
|
||||
|
||||
## 248.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Bump ImageSharp version.
|
||||
* Fix instances of NaN gain for audio where a negative-infinity value is being used for volume.
|
||||
|
||||
|
||||
## 248.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Use `Entity<MapGridComponent>` for TileChangedEvent instead of EntityUid.
|
||||
* Audio files are no longer tempo perfect when being played if the offset is small. At some point in the future an AudioParams bool is likely to be added to enforce this.
|
||||
* MoveProxy method args got changed in the B2DynamicTree update.
|
||||
* ResPath will now assert in debug if you pass in an invalid path containing the non-standardized directory separator.
|
||||
|
||||
### New features
|
||||
|
||||
* Added a new `MapLoaderSystem.TryLoadGrid()` override that loads a grid onto a newly created map.
|
||||
* Added a CVar for the endbuffer for audio. If an audio file will play below this length (for PVS reasons) it will be ignored.
|
||||
* Added Regex.Count + StringBuilder.Chars setter to the sandbox.
|
||||
* Added a public API for PhysicsHull.
|
||||
* Made MapLoader log more helpful.
|
||||
* Add TryLoadGrid override that also creates a map at the same time.
|
||||
* Updated B2Dynamictree to the latest Box2D V3 version.
|
||||
* Added SetItems to ItemList control to set items without removing the existing ones.
|
||||
* Shaders, textures, and audio will now hot reload automatically to varying degrees. Also added IReloadManager to handle watching for file-system changes and relaying events.
|
||||
* Wrap BUI disposes in a try-catch in case of exceptions.
|
||||
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix some instances of invalid PlaybackPositions being set.
|
||||
* Play audio from the start of a file if it's only just come into PVS range / had its state handled.
|
||||
* Fix TryCopyComponents.
|
||||
* Use shell.WriteError if TryLoad fails for mapping commands.
|
||||
* Fix UI control position saving causing exceptions where the entity is cleaned-up alongside a state change.
|
||||
* Fix Map NetId completions.
|
||||
* Fix some ResPath calls using the wrong paths.
|
||||
|
||||
### Internal
|
||||
|
||||
* Remove some unused local variables and the associated warnings.
|
||||
|
||||
|
||||
## 247.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added functions for copying components to `IEntityManager` and `EntitySystem`.
|
||||
* Sound played from sound collections is now sent as "collection ID + index" over the network instead of the final filename.
|
||||
* This enables integration of future accessibility systems.
|
||||
* Added a new `ResolvedSoundSpecifier` to represent played sounds. Methods that previously took a filename now take a `ResolvedSoundSpecifier`, with an implicit cast from string being interpreted as a raw filename.
|
||||
* `VisibilitySystem` has been made accessible to shared as `SharedVisibilitySystem`.
|
||||
* `ScrollContainer` now has properties exposing `Value` and `ValueTarget` on its internal scroll bars.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix prototype hot reload crashing when adding a new component already exists on an entity.
|
||||
* Fix maps failing to save in some cases related to tilemap IDs.
|
||||
* Fix `Regex.Escape(string)` not being available in sandbox.
|
||||
* Prototypes that parent themselves directly won't cause the game to hang on an infinite loop anymore.
|
||||
* Fixed disconnecting during a connection attempt leaving the client stuck in a phantom state.
|
||||
|
||||
### Internal
|
||||
|
||||
* More warning cleanup.
|
||||
|
||||
## 247.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added support for `Color[]` shader uniforms
|
||||
* Added optional minimumDistance parameter to `SharedJointSystem.CreateDistanceJoint()`
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `EntitySystem.DirtyFields()` not actually marking fields as dirty.
|
||||
|
||||
### Other
|
||||
|
||||
* Updated the Yamale map file format validator to support v7 map/grid files.
|
||||
|
||||
|
||||
## 247.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `ITileDefinitionManager.AssignAlias` and general tile alias functionality has been removed. `TileAliasPrototype` still exist, but are only used during entity deserialization.
|
||||
* `IMapManager.AddUninitializedMap` has been removed. Use the map-init options on `CreateMap()` instead.
|
||||
* Re-using a MapId will now log a warning. This may cause some integration tests to fail if they are configured to fail
|
||||
when warnings are logged.
|
||||
* The minimum supported map format / version has been increased from 2 to 3.
|
||||
* The server-side `MapLoaderSystem` and associated classes & structs has been moved to `Robust.Shared`, and has been significantly modified.
|
||||
* The `TryLoad` and `Save` methods have been replaced with grid, map, generic entity variants. I.e, `SaveGrid`, `SaveMap`, and `SaveEntities`.
|
||||
* Most of the serialization logic and methods have been moved out of `MapLoaderSystem` and into new `EntitySerializer`
|
||||
and `EntityDeserializer` classes, which also replace the old `MapSerializationContext`.
|
||||
* The `MapLoadOptions` class has been split into `MapLoadOptions`, `SerializationOptions`, and `DeserializationOptions`
|
||||
structs.
|
||||
* The interaction between PVS overrides and visibility masks / layers have changed:
|
||||
* Any forced entities (i.e., `PvsOverrideSystem.AddForceSend()`) now ignore visibility masks.
|
||||
* Any global & session overrides (`PvsOverrideSystem.AddGlobalOverride()` & `PvsOverrideSystem.AddSessionOverride()`) now respect visibility masks.
|
||||
* Entities added via the `ExpandPvsEvent` respect visibility masks.
|
||||
* The mask used for any global/session overrides can be modified via `ExpandPvsEvent.Mask`.
|
||||
* Toolshed Changes:
|
||||
* The signature of Toolshed type parsers have changed. Instead of taking in an optional command argument name string, they now take in a `CommandArgument` struct.
|
||||
* Toolshed commands can no longer contain a '|', as this symbol is now used for explicitly piping the output of one command to another. command pipes. The existing `|` and '|~' commands have been renamed to `bitor` and `bitnotor`.
|
||||
* Semicolon terminated command blocks in toolshed commands no longer return anything. I.e., `i { i 2 ; }` is no longer a valid command, as the block has no return value.
|
||||
|
||||
### New features
|
||||
|
||||
* The current map format/version has increased from 6 to 7 and now contains more information to try support serialization of maps with null-space entities and full game saves.
|
||||
* `IEntitySystemManager` now provides access to the system `IDependencyCollection`.
|
||||
* Toolshed commands now support optional and `params T[]` arguments. optional / variable length commands can be terminated using ';' or '|'.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed entity deserialization for components with a data fields that have a AlwaysPushInheritance Attribute
|
||||
* Audio entities attached to invisible / masked entities should no longer be able to temporarily make those entities visible to all players.
|
||||
* The map-like Toolshed commands now work when a collection is piped in.
|
||||
* Fixed a bug in toolshed that could cause it to preferentially use the incorrect command implementation.
|
||||
* E.g., passing a concrete enumerable type would previously use the command implementation that takes in an unconstrained generic parameter `T` instead of a dedicated `IEnumeerable<T>` implementation.
|
||||
|
||||
### Other
|
||||
|
||||
* `MapChangedEvent` has been marked as obsolete, and should be replaced with `MapCreatedEvent` and `MapRemovedEvent.
|
||||
* The default auto-completion hint for Toolshed commands have been changed and somewhat standardized. Most parsers should now generate a hint of the form:
|
||||
* `<name (Type)>` for mandatory arguments
|
||||
* `[name (Type)]` for optional arguments
|
||||
* `[name (Type)]...` for variable length arguments (i.e., for `params T[]`)
|
||||
|
||||
|
||||
## 246.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The fixes to renderer state may have inadvertantly broken some rendering code that relied upon the old behavior.
|
||||
* TileRenderFlag has been removed and now it's just a byte flag on the tile for content usage.
|
||||
|
||||
### New features
|
||||
|
||||
* Add BeforeLighting overlay draw space for overlays that need to draw directly to lighting and want to do it immediately beforehand.
|
||||
* Change BlurLights to BlurRenderTarget and make it public for content usage.
|
||||
* Add ContentFlag to tiles for content-flag usage.
|
||||
* Add a basic mix shader for doing canvas blends.
|
||||
* Add GetClearColorEvent for content to override the clear color behavior.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix pushing renderer state not restoring stencil status, blend status, queued shader instance scissor state.
|
||||
|
||||
|
||||
## 245.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add more info to the AnchorEntity debug message.
|
||||
* Make ParseObject public where it will parse a supplied Type and string into the specified object.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix EntityPrototypeView not always updating the entity correctly.
|
||||
* Tweak BUI shutdown to potentially avoid skipping closing.
|
||||
|
||||
### Other
|
||||
|
||||
* Increase Audio entity despawn buffer to avoid clipping.
|
||||
|
||||
|
||||
## 245.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `BoundUserInterface.Open()` now has the `MustCallBase` attribute
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an error in `MappingDataNode.TryAddCopy()`, which was causing yaml inheritance/deserialization bugs.
|
||||
|
||||
|
||||
## 244.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Increase physics speedcap default from 35m/s to 400m/s in-line with box2d v3.
|
||||
|
||||
### New features
|
||||
|
||||
* Add EntityManager overloads for ComponentRegistration that's faster than the generic methods.
|
||||
* Add CreateWindowCenteredRight for BUIs.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Avoid calling UpdateState before opening a BUI.
|
||||
|
||||
|
||||
## 243.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `BaseWindow` sometimes not properly updating the mouse cursor shape.
|
||||
* Revert `BaseWindow` OnClose ordering due to prior reliance upon the ordering.
|
||||
|
||||
|
||||
## 243.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* RemoveChild is called after OnClose for BaseWindow.
|
||||
|
||||
### New features
|
||||
|
||||
* BUIs now have their positions saved when closed and re-used when opened when using the `CreateWindow<T>` helper or via manually registering it via RegisterControl.
|
||||
|
||||
### Other
|
||||
|
||||
* Ensure grid fixtures get updated in client state handling even if exceptions occur.
|
||||
|
||||
|
||||
## 242.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed prototype reloading/hotloading not properly handling data-fields with the `AlwaysPushInheritanceAttribute`
|
||||
* Fix the pooled polygons using incorrect vertices for EntityLookup and MapManager.
|
||||
|
||||
### Internal
|
||||
|
||||
* Avoid normalizing angles constructed from vectors.
|
||||
|
||||
|
||||
## 242.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The order in which the client initialises networked entities has changed. It will now always apply component states, initialise, and start an entity's parent before processing any children. This might break anything that was relying on the old behaviour where all component states were applied before any entities were initialised & started.
|
||||
* `IClydeViewport` overlay rendering methods now take in an `IRenderHandle` instead of a world/screen handle.
|
||||
* The `OverlayDrawArgs` struct now has an internal constructor.
|
||||
|
||||
### New features
|
||||
|
||||
* Controls can now be manually restyled via `Control.InvalidateStyleSheet()` and `Control.DoStyleUpdate()`
|
||||
* Added `IUserInterfaceManager.RenderControl()` for manually drawing controls.
|
||||
* `OverlayDrawArgs` struct now has an `IRenderHandle` field such that overlays can use the new `RenderControl()` methods.
|
||||
* TileSpawnWindow will now take focus when opened.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a client-side bug where `TransformComponent.GridUid` does not get set properly when an existing entity is attached to a new entity outside of the player's PVS range.
|
||||
* EntityPrototypeView will only create entities when it's on the UI tree and not when the prototype is set.
|
||||
* Make CollisionWake not log errors if it can't resolve.
|
||||
|
||||
### Other
|
||||
|
||||
* Replace IPhysShape API with generics on IMapManager and EntityLookupSystem.
|
||||
|
||||
### Internal
|
||||
|
||||
* Significantly reduce allocations for Box2 / Box2Rotated queries.
|
||||
|
||||
|
||||
## 241.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove DeferredClose from BUIs.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `EntityManager.DirtyFields()`, which allows components with delta states to simultaneously mark several fields as dirty at the same time.
|
||||
* Add `CloserUserUIs<T>` to close keys of a specific key.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `RaisePredictiveEvent()` not properly re-raising events during prediction for event handlers that did not take an `EntitySessionEventArgs` argument.
|
||||
* BUI openings are now deferred to avoid having slight desync between deferred closes and opens occurring in the same tick.
|
||||
|
||||
|
||||
## 240.1.2
|
||||
|
||||
|
||||
## 240.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed one of the `IOverlayManager.RemoveOverlay` overrides not fully removing the overlay.
|
||||
|
||||
|
||||
## 240.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added an `AsNullable` extension method for converting an `Entity<T>` into an `Entity<T?>`
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an exception in `PhysicsSystem.DestroyContacts()` that could result in entities getting stuck with broken physics.
|
||||
|
||||
### Other
|
||||
|
||||
* `GamePrototypeLoadManager` will now send all uploaded prototypes to connecting players in a single `GamePrototypeLoadMessage`, as opposed to one message per upload.
|
||||
|
||||
|
||||
## 240.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `SharedBroadphaseSystem.GetBroadphases()` not returning the map itself, which was causing physics to not work properly off-grid.
|
||||
|
||||
|
||||
## 240.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `ComponentRegistry` no longer implements `ISerializationContext`
|
||||
* Tickrate values are now `ushort`, allowing them to go up to 65535.
|
||||
|
||||
### New features
|
||||
|
||||
* Console completion options now have new flags for preventing suggestions from being escaped or quoted.
|
||||
* Added `ILocalizationManager.HasCulture()`.
|
||||
* Static `EntProtoId<T>` fields are now validated to exist.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed a state handling bug in replays, which was causing exceptions to be thrown when applying delta states.
|
||||
|
||||
### Other
|
||||
|
||||
* Reduced amount of `DynamicMethod`s used by serialization system. This should improve performance somewhat.
|
||||
|
||||
### Internal
|
||||
|
||||
* Avoided sorting overlays every render frame.
|
||||
* Various clean up to grid fixture code/adding asserts.
|
||||
|
||||
## 239.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix logging of received packets with `net.packet` logging level.
|
||||
* Downgrade `VorbisPizza` to fix audio playback for systems without AVX2 support.
|
||||
|
||||
### Other
|
||||
|
||||
* Improved performance of some Roslyn analyzers and source generators, which should significantly improve compile times and IDE performance.
|
||||
|
||||
|
||||
## 239.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Robust now uses **.NET 9**.
|
||||
* `ISerializationManager` will now log errors if it encounters `Entity<T>` data-fields.
|
||||
* To be clear, this has never been supported and is not really a breaking change, but this will likely require manual intervention to prevent tests from failing.
|
||||
* `IClyde.TextInputSetRect`, `TextInputStart` and `TextInputStop` have been moved to be on `IClydeWindow`.
|
||||
* Updated various NuGet dependencies and removed some other ones, of note:
|
||||
* `FastAccessors`, which is a transitive dep we never used, is now gone. It might have snuck into some `using` statement thanks to your IDE, and those will now fail to compile. Remove them.
|
||||
* NUnit `Is.EqualTo(default)` seems to have ambiguous overload resolution in some cases now, this can be fixed by using an explicit `default(type)` syntax.
|
||||
* This also fixed various false-positive warnings reported by NuGet.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `MockInterfaces.MakeConfigurationManager` for creating functional configuration managers for unit test mocking.
|
||||
* Added `ISawmill.IsLogLevelEnabled()` to avoid doing expensive verbose logging operations when not necessary.
|
||||
* ``string[] Split(System.ReadOnlySpan`1<char>)`` is now available in sandbox.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed auto-generated component delta-states not raising `AfterAutoHandleStateEvent`
|
||||
* Fixed auto-generated component delta-states improperly implementing `IComponentDeltaState` methods. May have caused bugs in replays.
|
||||
* Fixed `Robust.Client.WebView` on the launcher via a new release.
|
||||
* Fixed an exception that could occur when saving a map that had tiles migrated by alias.
|
||||
|
||||
### Other
|
||||
|
||||
* The `loglevel` command now properly shows the "`null`" log level that resets the level to inheriting from parent. This was already supported by it, but the completions didn't list it.
|
||||
|
||||
### Internal
|
||||
|
||||
* Experimental SDL2 windowing backend has been replaced with SDL3. SDL3 backend is also more feature-complete, though it is still not in use.
|
||||
* Updated CEF used by Robust.Client.WebView to 131.3.5.
|
||||
|
||||
## 238.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed source generation for auto-networked EntityUid Dictionaries missing a semicolon
|
||||
* Fixed PlacementManager using the wrong coordinates when deleting entities in an area.
|
||||
|
||||
|
||||
## 238.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Some toolshed command syntax/parsing has changed slightly, and several toolshed related classes and interfaces have changed significantly, including ToolshedManager, type parsers, invocation contexts, and parser contexts. For more detail see the the description of PR #5455
|
||||
|
||||
|
||||
## 237.4.0
|
||||
|
||||
### New features
|
||||
|
||||
* Implement automatic field-level delta states via AutoGenerateComponentState via opt-in.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Remove redundant TransformComponentState bool.
|
||||
|
||||
|
||||
## 237.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added stack-like functions to `ValueList<T>` and added an `AddRange(ReadOnlySpan<T>)` overload.
|
||||
* Added new `AssetPassFilterDrop`.
|
||||
* Added a new RayCastSystem with the latest Box2D raycast + shapecasts implemented.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `IPrototypeManager.TryGetKindFrom()` not working for prototypes with automatically inferred kind names.
|
||||
|
||||
### Other
|
||||
|
||||
* Sandbox error reference locator now works with generic method calls.
|
||||
|
||||
|
||||
## 237.2.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `SharedEyeSystem..SetTarget()` will now also automatically remove the old target from the session's ViewSubscriptions
|
||||
|
||||
### New features
|
||||
|
||||
* `ImmutableArray<T>` can now be serialized by `RobustSerializer`.
|
||||
* `RequiresLocationAttribute`, used by `ref readonly`, is now allowed by the sandbox.
|
||||
* Added `DAT-OBJ()` localization function, for the dative case in certain languages.
|
||||
* Client builds for FreeBSD are now made.
|
||||
* Added `FormattedMessage.TrimEnd()`.
|
||||
* Added Toolshed `with` for `ProtoId<T>`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `UniqueIndex<,>.RemoveRange()` and`UniqueIndexHkm<,>.RemoveRange()` clearing the whole set instead of just removing the specified values.
|
||||
* Avoid server crashes on some weird console setups (notably Pterodactyl).
|
||||
* Avoid unhandled exceptions during server shutdown getting swallowed due logging into a disposed logger.
|
||||
* Fix sandbox definitions for `Regex` functions returning `MatchCollection`.
|
||||
* Fix minor layout bugs with `SplitContainer` and `BoxContainer`.
|
||||
|
||||
### Other
|
||||
|
||||
* Changed how multi-window rendering presents to the screen with a new CVar `display.thread_unlock_before_swap`. This is an experiment to see if it solves some synchronization issues.
|
||||
* View Variables no longer clears the window on refresh while waiting on response from server.
|
||||
* `SpinBox` buttons now have a `+` prefix for the positive ones.
|
||||
* Improve Toolshed type intersection mechanism
|
||||
|
||||
### Internal
|
||||
|
||||
* Warning cleanup.
|
||||
|
||||
## 237.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* csi's auto import-system can now handle generic types.
|
||||
* csi's reflection helpers (like `fld()`) handle private members up the inheritance chain.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `UniqueIndexHkm<,>` and, by extension, entity data storage memory leaking.
|
||||
* Fix bugs related to UIScale on `OSWindow`s.
|
||||
|
||||
|
||||
## 237.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `IClydeWindow.Size` is now settable, allowing window sizes to be changed after creation.
|
||||
|
||||
### New features
|
||||
|
||||
* The game server's `/update` endpoint now supports passing more information on why an update is available.
|
||||
* This information is accessible via `IWatchdogApi.RestartRequested`.
|
||||
* Information can be specified by passing a JSON object with a `Reason` code and `Message` field.
|
||||
* Added an "Erase" button to the tile spawn menu.
|
||||
* Added `OSWindow.Create()`, which allows OS windows to be created & initialised without immediately opening/showing them.
|
||||
|
||||
### Other
|
||||
|
||||
* Made `WatchdogApi` and some members of `IWatchdogApi` private. These symbols should never have been accessed by content.
|
||||
|
||||
|
||||
## 236.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `RequiredMemberAttribute` and `SetsRequiredMembersAttribute` have been added to the sandbox whitelist. I.e., you can now use the `required` keyword in client/shared code.
|
||||
* Added `SwitchExpressionException` to sandbox. This type gets used if you have a `switch` expression with no default case.
|
||||
* Added `LineEdit.SelectAllOnFocus`.
|
||||
* `GameTitle`, `WindowIconSet` and `SplashLogo` are exposed in `IGameController`. These will return said information set in game options or whatever is set in `manifest.yml`.
|
||||
* `BoundUserInterface` inheritors now have access to `PlayerManager`.
|
||||
* Added `MuteSounds` bool to `BaseButton`.
|
||||
* The engine has a new future-proof HWID system.
|
||||
* The auth server now manages HWIDs. This avoids HWID impersonation attacks.
|
||||
* The auth server can return multiple HWIDs. They are accessible in `NetUserData.ModernHWIds`.
|
||||
* The auth server also returns a trust score factor, accessible as `NetUserData.Trust`.
|
||||
* HWID can be disabled client side (`ROBUST_AUTH_ALLOW_HWID` env var) or server side (`net.hwid` cvar).
|
||||
* The old HWID system is still in place. It is intended that content switches to placing new bans against the new HWIDs.
|
||||
* Old HWIDs no longer work if the connection is not authenticated.
|
||||
* `launchauth` command now recognizes `SS14_LAUNCHER_APPDATA_NAME`.
|
||||
* Added new overload to `EntityLookupSystem.GetEntitiesIntersecting`.
|
||||
* Added `Control.RemoveChild(int childIndex)`.
|
||||
* `build.entities_category_filter` allows filtering the entity spawn panel to a specific category.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `SpriteView` offset calculations when scaled.
|
||||
|
||||
### Other
|
||||
|
||||
* Sprite flicks are applied immediately when started.
|
||||
* More warning fixes.
|
||||
* If the server gets shut down before finishing startup, the reason is now logged properly.
|
||||
|
||||
|
||||
## 236.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Revert IsTouching only being set to true if the contact were laready touching in clientside physics prediction.
|
||||
* Don't touch IsTouching if both bodies are asleep for clientside physics contacts. This change and the one above should fix a lot of clientside contact issues, particularly around repeated incorrect clientside contact events.
|
||||
|
||||
### New features
|
||||
|
||||
* Added an analyzer to detect duplicate Dependency fields.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Auto-networked dictionaries now use `TryAdd()` to avoid duplicate key errors when a dictionary contains multiple unknown networked entities.
|
||||
* Fixed `ICommonSession.Ping` always returning zero instead of the ping. Note that this will still return zero for client-side code when trying to get the ping of other players.
|
||||
* Hot reload XAML files on rename to fix them potentially not being reloaded with Visual Studio.
|
||||
* Fix TabContainer click detection for non-1.0 UI scales.
|
||||
|
||||
### Other
|
||||
|
||||
* Obsolete some static localization methods.
|
||||
* Tried to improve PVS tolerance to exceptions occurring.
|
||||
|
||||
|
||||
## 235.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Several different `AudioSystem` methods were incorrectly given a `[return: NotNullIfNotNull]` attribute. Content code that uses these methods needs to be updated to perform null checks.
|
||||
* noSpawn is no longer obsolete and is now removed in lieu of the EntityCategory HideSpawnMenu.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* physics.maxlinvelocity is now a replicated cvar.
|
||||
* Fix DistanceJoint debug drawing in physics not using the local anchors.
|
||||
* Fixed filtered AudioSystem methods playing a sound for all players when given an empty filter.
|
||||
* Fixed equality checks for `MarkupNode` not properly handling attributes.
|
||||
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
|
||||
* Fixed a PVS error that could occur when trying to delete the first entity that gets created in a round.
|
||||
* Fixed the "to" and "take" toolshed commands not working as intended.
|
||||
* Rich text controls within an `OutputPanel` control will now become invisible when they are out of view.
|
||||
|
||||
### Other
|
||||
|
||||
* Improve precision for Quaternion2D constructor from angles.
|
||||
|
||||
|
||||
## 234.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* SharedAudioSystem now has PlayLocal which only runs audio locally on the client.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix AudioParams not being passed through on PlayGlobal methods.
|
||||
|
||||
|
||||
## 234.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove a lot of obsoleted code that has been obsoleted for a while.
|
||||
|
||||
### New features
|
||||
|
||||
* Add another GetLocalEntitiesIntersecting override.
|
||||
|
||||
### Other
|
||||
|
||||
* Mark large replays as requiring Server GC.
|
||||
* Obsolete some IResourceCache proxies.
|
||||
|
||||
|
||||
## 233.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add GetGridEntities and another GetEntitiesIntersecting overload to EntityLookupSystem.
|
||||
* `MarkupNode` is now `IEquatable<MarkupNode>`. It already supported equality checks, now it implements the interface.
|
||||
* Added `Entity<T>` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`.
|
||||
* Added `EntityUid`-only overloads to the following `SharedTransformSystem` methods: `AnchorEntity`, `Unanchor`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed equality checks for `MarkupNode` not properly handling attributes.
|
||||
* Fixed toolshed commands failing to generate error messages when working with array types
|
||||
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
|
||||
|
||||
### Other
|
||||
|
||||
* If `EntityManager.FlushEntities()` fails to delete all entities, it will now attempt to do so a second time before throwing an exception.
|
||||
|
||||
|
||||
## 233.0.2
|
||||
|
||||
### Bugfixes
|
||||
@@ -83,7 +917,7 @@ END TEMPLATE-->
|
||||
|
||||
### Internal
|
||||
|
||||
* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
|
||||
* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
|
||||
* Engine version script now supports dashes.
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
kind: canvas
|
||||
light_mode: unshaded
|
||||
|
||||
# Simple mix blend
|
||||
- type: shader
|
||||
id: Mix
|
||||
kind: canvas
|
||||
blend_mode: Mix
|
||||
|
||||
- type: shader
|
||||
id: shaded
|
||||
kind: canvas
|
||||
|
||||
@@ -18,3 +18,9 @@
|
||||
description: entity-category-desc-hide
|
||||
hideSpawnMenu: true
|
||||
inheritable: false
|
||||
|
||||
# Entity prototypes added by the fork. With CVar you can hide all entities without this category
|
||||
- type: entityCategory
|
||||
id: ForkFiltered
|
||||
name: entity-category-name-fork
|
||||
description: entity-category-desc-fork
|
||||
@@ -20,6 +20,15 @@ zzzz-object-pronoun = { GENDER($ent) ->
|
||||
*[neuter] it
|
||||
}
|
||||
|
||||
# Used internally by the DAT-OBJ() function.
|
||||
# Not used in en-US. Created for supporting other languages.
|
||||
zzzz-dat-object = { GENDER($ent) ->
|
||||
[male] him
|
||||
[female] her
|
||||
[epicene] them
|
||||
*[neuter] it
|
||||
}
|
||||
|
||||
# Used internally by the POSS-PRONOUN() function.
|
||||
zzzz-possessive-pronoun = { GENDER($ent) ->
|
||||
[male] his
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
### Localization for engine console commands
|
||||
|
||||
cmd-hint-float = [float]
|
||||
|
||||
## generic command errors
|
||||
|
||||
cmd-invalid-arg-number-error = Invalid number of arguments.
|
||||
@@ -11,6 +13,7 @@ cmd-parse-failure-uid = {$arg} is not a valid entity UID.
|
||||
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
|
||||
cmd-parse-failure-enum = {$arg} is not a {$enum} Enum.
|
||||
cmd-parse-failure-grid = {$arg} is not a valid grid.
|
||||
cmd-parse-failure-cultureinfo = "{$arg}" is not valid CultureInfo.
|
||||
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
|
||||
cmd-parse-failure-session = There is no session with username: {$username}
|
||||
|
||||
@@ -156,6 +159,7 @@ cmd-savemap-not-exist = Target map does not exist.
|
||||
cmd-savemap-init-warning = Attempted to save a post-init map without forcing the save.
|
||||
cmd-savemap-attempt = Attempting to save map {$mapId} to {$path}.
|
||||
cmd-savemap-success = Map successfully saved.
|
||||
cmd-savemap-error = Could not save map! See server log for details.
|
||||
cmd-hint-savemap-id = <MapID>
|
||||
cmd-hint-savemap-path = <Path>
|
||||
cmd-hint-savemap-force = [bool]
|
||||
@@ -293,7 +297,7 @@ cmd-lsgrid-desc = Lists grids.
|
||||
cmd-lsgrid-help = lsgrid
|
||||
|
||||
cmd-addmap-desc = Adds a new empty map to the round. If the mapID already exists, this command does nothing.
|
||||
cmd-addmap-help = addmap <mapID> [initialize]
|
||||
cmd-addmap-help = addmap <mapID> [pre-init]
|
||||
|
||||
cmd-rmmap-desc = Removes a map from the world. You cannot remove nullspace.
|
||||
cmd-rmmap-help = rmmap <mapId>
|
||||
@@ -427,11 +431,20 @@ cmd-entfo-help = Usage: entfo <entityuid>
|
||||
The entity UID can be prefixed with 'c' to convert it to a client entity UID.
|
||||
|
||||
cmd-fuck-desc = Throws an exception
|
||||
cmd-fuck-help = Throws an exception
|
||||
cmd-fuck-help = Usage: fuck
|
||||
|
||||
cmd-showpos-desc = Enables debug drawing over all entity positions in the game.
|
||||
cmd-showpos-desc = Show the position of all entities on the screen.
|
||||
cmd-showpos-help = Usage: showpos
|
||||
|
||||
cmd-showrot-desc = Show the rotation of all entities on the screen.
|
||||
cmd-showrot-help = Usage: showrot
|
||||
|
||||
cmd-showvel-desc = Show the local velocity of all entites on the screen.
|
||||
cmd-showvel-help = Usage: showvel
|
||||
|
||||
cmd-showangvel-desc = Show the angular velocity of all entities on the screen.
|
||||
cmd-showangvel-help = Usage: showangvel
|
||||
|
||||
cmd-sggcell-desc = Lists entities on a snap grid cell.
|
||||
cmd-sggcell-help = Usage: sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.
|
||||
|
||||
@@ -562,3 +575,8 @@ cmd-pvs-override-info-desc = Prints information about any PVS overrides associat
|
||||
cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
|
||||
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
|
||||
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.
|
||||
|
||||
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager
|
||||
cmd-localization_set_culture-help = Usage: localization_set_culture <cultureName>
|
||||
cmd-localization_set_culture-culture-name = <cultureName>
|
||||
cmd-localization_set_culture-changed = Localization changed to { $code } ({ $nativeName } / { $englishName })
|
||||
|
||||
@@ -4,9 +4,20 @@ entity-spawn-window-title = Entity Spawn Panel
|
||||
entity-spawn-window-search-bar-placeholder = search
|
||||
entity-spawn-window-clear-button = Clear
|
||||
entity-spawn-window-replace-button-text = Replace
|
||||
entity-spawn-window-erase-button-text = Erase Mode
|
||||
entity-spawn-window-override-menu-tooltip = Override placement
|
||||
|
||||
## TileSpawnWindow
|
||||
|
||||
tile-spawn-window-title = Place Tiles
|
||||
|
||||
## Console
|
||||
|
||||
console-line-edit-placeholder = Command Here
|
||||
|
||||
## OutputPanel
|
||||
|
||||
output-panel-scroll-down-button-text = Scroll Down
|
||||
|
||||
## Common Used
|
||||
|
||||
window-erase-button-text = Erase Mode
|
||||
|
||||
@@ -7,3 +7,6 @@ entity-category-desc-spawner = Entity prototypes that spawn other entities.
|
||||
|
||||
entity-category-name-hide = Hidden
|
||||
entity-category-desc-hide = Entity prototypes that should be hidden from entity spawn menus
|
||||
|
||||
entity-category-name-fork = Fork Filtered
|
||||
entity-category-desc-fork = Entity prototypes added by the fork. With CVar you can hide all entities without this category
|
||||
@@ -1,4 +1,8 @@
|
||||
command-description-tpto =
|
||||
command-help-usage =
|
||||
Usage:
|
||||
command-help-invertible =
|
||||
The behaviour of this command can be inverted using the "not" prefix.
|
||||
command-description-tpto =
|
||||
Teleport the given entities to some target entity.
|
||||
command-description-player-list =
|
||||
Returns a list of all player sessions.
|
||||
@@ -19,7 +23,7 @@ command-description-buildinfo =
|
||||
command-description-cmd-list =
|
||||
Returns a list of all commands, for this side.
|
||||
command-description-explain =
|
||||
Explains the given expression, providing command descriptions and signatures.
|
||||
Explains the given expression, providing command descriptions and signatures. This only works for valid expressions, it can't explain commands that it fails to parse.
|
||||
command-description-search =
|
||||
Searches through the input for the provided value.
|
||||
command-description-stopwatch =
|
||||
@@ -38,8 +42,7 @@ command-description-as =
|
||||
command-description-count =
|
||||
Counts the amount of entries in it's input, returning an integer.
|
||||
command-description-map =
|
||||
Maps the input over the given block, with the provided expected return type.
|
||||
This command may be modified to not need an explicit return type in the future.
|
||||
Maps the input over the given block.
|
||||
command-description-select =
|
||||
Selects N objects or N% of objects from the input.
|
||||
One can additionally invert this command with not to make it select everything except N objects instead.
|
||||
@@ -53,10 +56,8 @@ command-description-entities =
|
||||
Returns all entities on the server.
|
||||
command-description-paused =
|
||||
Filters the input entities by whether or not they are paused.
|
||||
This command can be inverted with not.
|
||||
command-description-with =
|
||||
Filters the input entities by whether or not they have the given component.
|
||||
This command can be inverted with not.
|
||||
command-description-fuck =
|
||||
Throws an exception.
|
||||
command-description-ecscomp-listty =
|
||||
@@ -95,6 +96,8 @@ command-description-vars =
|
||||
Provides a list of all variables set in this session.
|
||||
command-description-any =
|
||||
Returns true if there's any values in the input, otherwise false.
|
||||
command-description-contains =
|
||||
Returns whether the input enumerable contains the specified value.
|
||||
command-description-ArrowCommand =
|
||||
Assigns the input to a variable.
|
||||
command-description-isempty =
|
||||
@@ -119,6 +122,8 @@ command-description-splat =
|
||||
"Splats" a block, value, or variable, creating N copies of it in a list.
|
||||
command-description-val =
|
||||
Casts the given value, block, or variable to the given type. This is mostly a workaround for current limitations of variables.
|
||||
command-description-var =
|
||||
Returns the contents of the given variable. This will attempt to automatically infer a variables type. Compound commands that modify a variable may need to use the 'val' command instead.
|
||||
command-description-actor-controlled =
|
||||
Filters entities by whether or not they're actively controlled.
|
||||
command-description-actor-session =
|
||||
@@ -143,7 +148,7 @@ command-description-max =
|
||||
Returns the maximum of two values.
|
||||
command-description-BitAndCommand =
|
||||
Performs bitwise AND.
|
||||
command-description-BitOrCommand =
|
||||
command-description-bitor =
|
||||
Performs bitwise OR.
|
||||
command-description-BitXorCommand =
|
||||
Performs bitwise XOR.
|
||||
@@ -197,11 +202,11 @@ command-description-mappos =
|
||||
command-description-pos =
|
||||
Returns an entity's coordinates.
|
||||
command-description-tp-coords =
|
||||
Teleports the target to the given coordinates.
|
||||
Teleports the given entities to the target coordinates.
|
||||
command-description-tp-to =
|
||||
Teleports the target to the given other entity.
|
||||
Teleports the given entities to the target entity.
|
||||
command-description-tp-into =
|
||||
Teleports the target "into" the given other entity, attaching it at (0 0) relative to it.
|
||||
Teleports the given entities "into" the target entity, attaching it at (0 0) relative to it.
|
||||
command-description-comp-get =
|
||||
Gets the given component from the given entity.
|
||||
command-description-comp-add =
|
||||
@@ -219,9 +224,9 @@ command-description-MulVecCommand =
|
||||
command-description-DivVecCommand =
|
||||
Divides every element in the input by a scalar (single value).
|
||||
command-description-rng-to =
|
||||
Returns a number from its input to its argument (i.e. n..m inclusive)
|
||||
Returns a number between the input (inclusive) and the argument (exclusive).
|
||||
command-description-rng-from =
|
||||
Returns a number to its input from its argument (i.e. m..n inclusive)
|
||||
Returns a number between the argument (inclusive) and the input (exclusive))
|
||||
command-description-rng-prob =
|
||||
Returns a boolean based on the input probability/chance (from 0 to 1)
|
||||
command-description-sum =
|
||||
@@ -271,7 +276,7 @@ command-description-ModVecCommand =
|
||||
Performs the modulus operation over the input with the given constant right-hand value.
|
||||
command-description-BitAndNotCommand =
|
||||
Performs bitwise AND-NOT over the input.
|
||||
command-description-BitOrNotCommand =
|
||||
command-description-bitornot =
|
||||
Performs bitwise OR-NOT over the input.
|
||||
command-description-BitXnorCommand =
|
||||
Performs bitwise XNOR over the input.
|
||||
|
||||
@@ -136,6 +136,7 @@ cmd-savemap-not-exist = O mapa de destino não existe.
|
||||
cmd-savemap-init-warning = Tentativa de salvar um mapa pós-inicialização sem forçar o salvamento.
|
||||
cmd-savemap-attempt = Tentando salvar o mapa {$mapId} em {$path}.
|
||||
cmd-savemap-success = Mapa salvo com sucesso.
|
||||
cmd-savemap-error = Não foi possível salvar o mapa! Consulte o log do servidor para obter detalhes.
|
||||
cmd-hint-savemap-id = <MapID>
|
||||
cmd-hint-savemap-path = <Path>
|
||||
cmd-hint-savemap-force = [bool]
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using Robust.Analyzers;
|
||||
|
||||
using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.AccessAnalyzer>;
|
||||
using static Microsoft.CodeAnalysis.Testing.DiagnosticResult;
|
||||
using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.AccessAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
@@ -16,7 +12,7 @@ public sealed class AccessAnalyzer_Test
|
||||
{
|
||||
public Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<AccessAnalyzer, NUnitVerifier>()
|
||||
var test = new CSharpAnalyzerTest<AccessAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
|
||||
114
Robust.Analyzers.Tests/ByRefEventAnalyzerTest.cs
Normal file
114
Robust.Analyzers.Tests/ByRefEventAnalyzerTest.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.ByRefEventAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture, TestOf(typeof(ByRefEventAnalyzer))]
|
||||
public sealed class ByRefEventAnalyzerTest
|
||||
{
|
||||
private const string EventBusDef = """
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public readonly struct EntityUid;
|
||||
|
||||
public sealed class EntitySystem
|
||||
{
|
||||
public void RaiseLocalEvent<TEvent>(EntityUid uid, ref TEvent args, bool broadcast = false)
|
||||
where TEvent : notnull { }
|
||||
|
||||
public void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = false)
|
||||
where TEvent : notnull { }
|
||||
}
|
||||
|
||||
public sealed class EntityEventBus
|
||||
{
|
||||
public void RaiseLocalEvent<TEvent>(EntityUid uid, ref TEvent args, bool broadcast = false)
|
||||
where TEvent : notnull { }
|
||||
|
||||
public void RaiseLocalEvent<TEvent>(EntityUid uid, TEvent args, bool broadcast = false)
|
||||
where TEvent : notnull { }
|
||||
}
|
||||
""";
|
||||
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<ByRefEventAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.GameObjects.EventBusAttributes.cs"
|
||||
);
|
||||
|
||||
test.TestState.Sources.Add(("EntityEventBus.cs", EventBusDef));
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestSuccess()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly struct RefEvent;
|
||||
public readonly struct ValueEvent;
|
||||
|
||||
public static class Foo
|
||||
{
|
||||
public static void Bar(EntityEventBus bus)
|
||||
{
|
||||
bus.RaiseLocalEvent(default(EntityUid), new ValueEvent());
|
||||
var refEv = new RefEvent();
|
||||
bus.RaiseLocalEvent(default(EntityUid), ref refEv);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestWrong()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
[ByRefEvent]
|
||||
public readonly struct RefEvent;
|
||||
public readonly struct ValueEvent;
|
||||
|
||||
public static class Foo
|
||||
{
|
||||
public static void Bar(EntityEventBus bus)
|
||||
{
|
||||
bus.RaiseLocalEvent(default(EntityUid), new RefEvent());
|
||||
var valueEv = new ValueEvent();
|
||||
bus.RaiseLocalEvent(default(EntityUid), ref valueEv);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(
|
||||
code,
|
||||
// /0/Test0.cs(11,49): error RA0015: Tried to raise a by-ref event 'RefEvent' by value
|
||||
VerifyCS.Diagnostic(ByRefEventAnalyzer.ByRefEventRaisedByValueRule).WithSpan(11, 49, 11, 63).WithArguments("RefEvent"),
|
||||
// /0/Test0.cs(13,49): error RA0016: Tried to raise a value event 'ValueEvent' by-ref
|
||||
VerifyCS.Diagnostic(ByRefEventAnalyzer.ByValueEventRaisedByRefRule).WithSpan(13, 49, 13, 60).WithArguments("ValueEvent")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
@@ -106,6 +107,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
@@ -147,6 +149,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
@@ -188,6 +191,7 @@ public sealed class ComponentPauseGeneratorTest
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DataDefinitionAnalyzer>;
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.DataDefinitionAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
@@ -14,7 +13,7 @@ public sealed class DataDefinitionAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<DataDefinitionAnalyzer, NUnitVerifier>()
|
||||
var test = new CSharpAnalyzerTest<DataDefinitionAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
@@ -88,4 +87,66 @@ public sealed class DataDefinitionAnalyzerTest
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 17, 35, 50).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReadOnlyFieldTest()
|
||||
{
|
||||
const string code = """
|
||||
using System;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes
|
||||
{
|
||||
public class DataFieldBaseAttribute : Attribute;
|
||||
public class DataFieldAttribute : DataFieldBaseAttribute;
|
||||
public sealed class DataDefinitionAttribute : Attribute;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Foo
|
||||
{
|
||||
[DataField]
|
||||
public readonly int Bad;
|
||||
|
||||
[DataField]
|
||||
public int Good;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(15,12): error RA0019: Data field Bad in data definition Foo is readonly
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldWritableRule).WithSpan(15, 12, 15, 20).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReadOnlyPropertyTest()
|
||||
{
|
||||
const string code = """
|
||||
using System;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes
|
||||
{
|
||||
public class DataFieldBaseAttribute : Attribute;
|
||||
public class DataFieldAttribute : DataFieldBaseAttribute;
|
||||
public sealed class DataDefinitionAttribute : Attribute;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Foo
|
||||
{
|
||||
[DataField]
|
||||
public int Bad { get; }
|
||||
|
||||
[DataField]
|
||||
public int Good { get; private set; }
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(15,20): error RA0020: Data field property Bad in data definition Foo does not have a setter
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldPropertyWritableRule).WithSpan(15, 20, 15, 28).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DependencyAssignAnalyzer>;
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.DependencyAssignAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
@@ -14,7 +13,7 @@ public sealed class DependencyAssignAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<DependencyAssignAnalyzer, NUnitVerifier>()
|
||||
var test = new CSharpAnalyzerTest<DependencyAssignAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
|
||||
62
Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs
Normal file
62
Robust.Analyzers.Tests/DuplicateDependencyAnalyzerTest.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.DuplicateDependencyAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
[TestOf(typeof(DuplicateDependencyAnalyzer))]
|
||||
public sealed class DuplicateDependencyAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<DuplicateDependencyAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.IoC.DependencyAttribute.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
public sealed class Foo
|
||||
{
|
||||
[Dependency]
|
||||
private object? Field;
|
||||
|
||||
[Dependency]
|
||||
private object? Field2;
|
||||
|
||||
[Dependency]
|
||||
private string? DifferentField;
|
||||
|
||||
private string? NonDependency1;
|
||||
private string? NonDependency2;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(9,21): warning RA0032: Another [Dependency] field of type 'object?' already exists in this type as field 'Field'
|
||||
VerifyCS.Diagnostic().WithSpan(9, 21, 9, 27).WithArguments("object?", "Field"));
|
||||
}
|
||||
}
|
||||
189
Robust.Analyzers.Tests/ForbidLiteralAnalyzerTest.cs
Normal file
189
Robust.Analyzers.Tests/ForbidLiteralAnalyzerTest.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.ForbidLiteralAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class ForbidLiteralAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<ForbidLiteralAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code },
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Analyzers.ForbidLiteralAttribute.cs"
|
||||
);
|
||||
|
||||
test.TestState.Sources.Add(("TestTypeDefs.cs", TestTypeDefs));
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
private const string TestTypeDefs = """
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
public sealed class TestClass
|
||||
{
|
||||
public static void OneParameterForbidden([ForbidLiteral] string value) { }
|
||||
public static void TwoParametersFirstForbidden([ForbidLiteral] string first, string second) { }
|
||||
public static void TwoParametersBothForbidden([ForbidLiteral] string first, [ForbidLiteral] string second) { }
|
||||
public static void ListParameterForbidden([ForbidLiteral] List<string> values) { }
|
||||
public static void ParamsListParameterForbidden([ForbidLiteral] params List<string> values) { }
|
||||
}
|
||||
|
||||
public record struct StringWrapper(string value)
|
||||
{
|
||||
private readonly string _value = value;
|
||||
|
||||
public static implicit operator string(StringWrapper wrapper)
|
||||
{
|
||||
return wrapper._value;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public async Task TestOneParameter()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
private static readonly StringWrapper WrappedValue = new("biz");
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.OneParameterForbidden(_constValue);
|
||||
TestClass.OneParameterForbidden(StaticValue);
|
||||
TestClass.OneParameterForbidden(WrappedValue);
|
||||
TestClass.OneParameterForbidden("baz");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(12,41): error RA0033: The "value" parameter of OneParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(12, 41, 12, 46).WithArguments("value", "OneParameterForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestTwoParametersFirstForbidden()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.TwoParametersFirstForbidden(_constValue, "whatever");
|
||||
TestClass.TwoParametersFirstForbidden(_constValue, _constValue);
|
||||
TestClass.TwoParametersFirstForbidden("foo", "whatever");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(9,47): error RA0033: The "first" parameter of TwoParametersFirstForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(9, 47, 9, 52).WithArguments("first", "TwoParametersFirstForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestTwoParametersBothForbidden()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.TwoParametersBothForbidden(_constValue, _constValue);
|
||||
TestClass.TwoParametersBothForbidden(_constValue, StaticValue);
|
||||
TestClass.TwoParametersBothForbidden(_constValue, "whatever");
|
||||
TestClass.TwoParametersBothForbidden("whatever", _constValue);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,59): error RA0033: The "second" parameter of TwoParametersBothForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 59, 10, 69).WithArguments("second", "TwoParametersBothForbidden"),
|
||||
// /0/Test0.cs(11,46): error RA0033: The "first" parameter of TwoParametersBothForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(11, 46, 11, 56).WithArguments("first", "TwoParametersBothForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestListParameter()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
private static readonly StringWrapper WrappedValue = new("biz");
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.ListParameterForbidden([_constValue, StaticValue, WrappedValue]);
|
||||
TestClass.ListParameterForbidden(["foo", _constValue, "bar"]);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,43): warning RA0033: The "values" parameter of ListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 43, 10, 48).WithArguments("values", "ListParameterForbidden"),
|
||||
// /0/Test0.cs(10,63): warning RA0033: The "values" parameter of ListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 63, 10, 68).WithArguments("values", "ListParameterForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestParamsListParameter()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
private static readonly StringWrapper WrappedValue = new("biz");
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.ParamsListParameterForbidden(_constValue, StaticValue, WrappedValue);
|
||||
TestClass.ParamsListParameterForbidden("foo", _constValue, "bar");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,48): warning RA0033: The "values" parameter of ParamsListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 48, 10, 53).WithArguments("values", "ParamsListParameterForbidden"),
|
||||
// /0/Test0.cs(10,68): warning RA0033: The "values" parameter of ParamsListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 68, 10, 73).WithArguments("values", "ParamsListParameterForbidden")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.MustCallBaseAnalyzer>;
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.MustCallBaseAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
@@ -14,7 +13,7 @@ public sealed class MustCallBaseAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<MustCallBaseAnalyzer, NUnitVerifier>()
|
||||
var test = new CSharpAnalyzerTest<MustCallBaseAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.NoUncachedRegexAnalyzer>;
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.NoUncachedRegexAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
@@ -14,7 +13,7 @@ public sealed class NoUncachedRegexAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>()
|
||||
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
|
||||
86
Robust.Analyzers.Tests/ObsoleteInheritanceAnalyzerTest.cs
Normal file
86
Robust.Analyzers.Tests/ObsoleteInheritanceAnalyzerTest.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.ObsoleteInheritanceAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Analyzer that implements <c>[ObsoleteInheritance]</c> checking, to give obsoletion warnings for inheriting types
|
||||
/// that should never have been virtual.
|
||||
/// </summary>
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class ObsoleteInheritanceAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<ObsoleteInheritanceAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code },
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestBasic()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
[ObsoleteInheritance]
|
||||
public class Base;
|
||||
|
||||
public class NotAllowed : Base;
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(6,14): warning RA0034: Type 'NotAllowed' inherits from 'Base', which has obsoleted inheriting from itself
|
||||
VerifyCS.Diagnostic(ObsoleteInheritanceAnalyzer.Rule).WithSpan(6, 14, 6, 24).WithArguments("NotAllowed", "Base")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestMessage()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
[ObsoleteInheritance("Sus")]
|
||||
public class Base;
|
||||
|
||||
public class NotAllowed : Base;
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(6,14): warning RA0034: Type 'NotAllowed' inherits from 'Base', which has obsoleted inheriting from itself: "Sus"
|
||||
VerifyCS.Diagnostic(ObsoleteInheritanceAnalyzer.RuleWithMessage).WithSpan(6, 14, 6, 24).WithArguments("NotAllowed", "Base", "Sus")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestNormal()
|
||||
{
|
||||
const string code = """
|
||||
public class Base;
|
||||
|
||||
public class AllowedAllowed : Base;
|
||||
""";
|
||||
|
||||
await Verifier(code);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferNonGenericVariantForAnalyzer>;
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PreferNonGenericVariantForAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
@@ -14,7 +13,7 @@ public sealed class PreferNonGenericVariantForTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<PreferNonGenericVariantForAnalyzer, NUnitVerifier>()
|
||||
var test = new CSharpAnalyzerTest<PreferNonGenericVariantForAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer>;
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
@@ -14,7 +13,7 @@ public sealed class PreferOtherTypeAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<PreferOtherTypeAnalyzer, NUnitVerifier>()
|
||||
var test = new CSharpAnalyzerTest<PreferOtherTypeAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer>;
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PreferOtherTypeAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
@@ -12,7 +11,7 @@ public sealed class PreferOtherTypeFixerTest
|
||||
{
|
||||
private static Task Verifier(string code, string fixedCode, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpCodeFixTest<PreferOtherTypeAnalyzer, PreferOtherTypeFixer, NUnitVerifier>()
|
||||
var test = new CSharpCodeFixTest<PreferOtherTypeAnalyzer, PreferOtherTypeFixer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\MustCallBaseAttribute.cs" LogicalName="Robust.Shared.IoC.MustCallBaseAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ForbidLiteralAttribute.cs" LogicalName="Robust.Shared.Analyzers.ForbidLiteralAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ObsoleteInheritanceAttribute.cs" LogicalName="Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -27,13 +30,17 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzer.Testing"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.NUnit"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing"/>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing"/>
|
||||
<PackageReference Include="NUnit"/>
|
||||
<PackageReference Include="NUnit3TestAdapter"/>
|
||||
<PackageReference Include="NUnit.Analyzers"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk"/>
|
||||
|
||||
<!-- Needed to fix transitive dependency versions -->
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
|
||||
<PackageReference Include="System.Formats.Asn1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -24,7 +24,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure that methods subscribing to a ref event have the ref keyword for the event argument."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
|
||||
public static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
|
||||
Diagnostics.IdByRefEventRaisedByValue,
|
||||
"By-ref event raised by value",
|
||||
"Tried to raise a by-ref event '{0}' by value",
|
||||
@@ -34,7 +34,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to use the ref keyword when raising ref events."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
|
||||
public static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
|
||||
Diagnostics.IdValueEventRaisedByRef,
|
||||
"Value event raised by-ref",
|
||||
"Tried to raise a value event '{0}' by-ref",
|
||||
@@ -54,32 +54,44 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterOperationAction(CheckEventRaise, OperationKind.Invocation);
|
||||
context.RegisterCompilationStartAction(compilationContext =>
|
||||
{
|
||||
var raiseMethods = compilationContext.Compilation
|
||||
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntitySystem")?
|
||||
.GetMembers()
|
||||
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
|
||||
.Cast<IMethodSymbol>();
|
||||
|
||||
var busRaiseMethods = compilationContext.Compilation
|
||||
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntityEventBus")?
|
||||
.GetMembers()
|
||||
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
|
||||
.Cast<IMethodSymbol>();
|
||||
|
||||
if (raiseMethods == null)
|
||||
return;
|
||||
|
||||
if (busRaiseMethods != null)
|
||||
raiseMethods = raiseMethods.Concat(busRaiseMethods);
|
||||
|
||||
var raiseMethodsArray = raiseMethods.ToArray();
|
||||
|
||||
compilationContext.RegisterOperationAction(
|
||||
ctx => CheckEventRaise(ctx, raiseMethodsArray),
|
||||
OperationKind.Invocation);
|
||||
});
|
||||
}
|
||||
|
||||
private void CheckEventRaise(OperationAnalysisContext context)
|
||||
private static void CheckEventRaise(
|
||||
OperationAnalysisContext context,
|
||||
IReadOnlyCollection<IMethodSymbol> raiseMethods)
|
||||
{
|
||||
if (context.Operation is not IInvocationOperation operation)
|
||||
return;
|
||||
|
||||
var raiseMethods = context.Compilation
|
||||
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntitySystem")?
|
||||
.GetMembers()
|
||||
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
|
||||
.Cast<IMethodSymbol>();
|
||||
|
||||
var busRaiseMethods = context.Compilation
|
||||
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntityEventBus")?
|
||||
.GetMembers()
|
||||
.Where(m => m.Name.Contains("RaiseLocalEvent") && m.Kind == SymbolKind.Method)
|
||||
.Cast<IMethodSymbol>();
|
||||
|
||||
if (raiseMethods == null)
|
||||
if (!operation.TargetMethod.Name.Contains("RaiseLocalEvent"))
|
||||
return;
|
||||
|
||||
if (busRaiseMethods != null)
|
||||
raiseMethods = raiseMethods.Concat(busRaiseMethods);
|
||||
|
||||
if (!raiseMethods.Any(m => m.Equals(operation.TargetMethod.OriginalDefinition, Default)))
|
||||
{
|
||||
// If you try to do this normally by concatenating like busRaiseMethods above
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to mark any type containing a nested data definition as partial."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
|
||||
public static readonly DiagnosticDescriptor DataFieldWritableRule = new(
|
||||
Diagnostics.IdDataFieldWritable,
|
||||
"Data field must not be readonly",
|
||||
"Data field {0} in data definition {1} is readonly",
|
||||
@@ -51,7 +51,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to remove the readonly modifier."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
|
||||
public static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
|
||||
Diagnostics.IdDataFieldPropertyWritable,
|
||||
"Data field property must have a setter",
|
||||
"Data field property {0} in data definition {1} does not have a setter",
|
||||
@@ -149,7 +149,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
if (IsReadOnlyDataField(type, fieldSymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
TryGetModifierLocation(field, SyntaxKind.ReadOnlyKeyword, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, location, fieldSymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(fieldSymbol))
|
||||
@@ -185,7 +186,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
if (IsReadOnlyDataField(type, propertySymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
var location = property.AccessorList != null ? property.AccessorList.GetLocation() : property.GetLocation();
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, location, propertySymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(propertySymbol))
|
||||
@@ -285,6 +287,20 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetModifierLocation(MemberDeclarationSyntax syntax, SyntaxKind modifierKind, out Location location)
|
||||
{
|
||||
foreach (var modifier in syntax.Modifiers)
|
||||
{
|
||||
if (modifier.IsKind(modifierKind))
|
||||
{
|
||||
location = modifier.GetLocation();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
location = syntax.GetLocation();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
|
||||
{
|
||||
if (member is IFieldSymbol field)
|
||||
|
||||
126
Robust.Analyzers/DuplicateDependencyAnalyzer.cs
Normal file
126
Robust.Analyzers/DuplicateDependencyAnalyzer.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Analyzer that detects duplicate <c>[Dependency]</c> fields inside a single type.
|
||||
/// </summary>
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class DuplicateDependencyAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new(
|
||||
Diagnostics.IdDuplicateDependency,
|
||||
"Duplicate dependency field",
|
||||
"Another [Dependency] field of type '{0}' already exists in this type with field '{1}'",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterCompilationStartAction(compilationContext =>
|
||||
{
|
||||
var dependencyAttributeType = compilationContext.Compilation.GetTypeByMetadataName(DependencyAttributeType);
|
||||
if (dependencyAttributeType == null)
|
||||
return;
|
||||
|
||||
compilationContext.RegisterSymbolStartAction(symbolContext =>
|
||||
{
|
||||
var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol;
|
||||
// Only deal with non-static classes, doesn't make sense to have dependencies in anything else.
|
||||
if (typeSymbol.TypeKind != TypeKind.Class || typeSymbol.IsStatic)
|
||||
return;
|
||||
|
||||
var state = new AnalyzerState(dependencyAttributeType);
|
||||
symbolContext.RegisterSyntaxNodeAction(state.AnalyzeField, SyntaxKind.FieldDeclaration);
|
||||
symbolContext.RegisterSymbolEndAction(state.End);
|
||||
},
|
||||
SymbolKind.NamedType);
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class AnalyzerState(INamedTypeSymbol dependencyAttributeType)
|
||||
{
|
||||
private readonly Dictionary<ITypeSymbol, List<IFieldSymbol>> _dependencyFields = new(SymbolEqualityComparer.Default);
|
||||
|
||||
public void AnalyzeField(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
var field = (FieldDeclarationSyntax)context.Node;
|
||||
if (field.AttributeLists.Count == 0)
|
||||
return;
|
||||
|
||||
if (context.ContainingSymbol is not IFieldSymbol fieldSymbol)
|
||||
return;
|
||||
|
||||
// Can't have [Dependency]s for non-reference types.
|
||||
if (!fieldSymbol.Type.IsReferenceType)
|
||||
return;
|
||||
|
||||
if (!IsDependency(context.ContainingSymbol))
|
||||
return;
|
||||
|
||||
lock (_dependencyFields)
|
||||
{
|
||||
if (!_dependencyFields.TryGetValue(fieldSymbol.Type, out var dependencyFields))
|
||||
{
|
||||
dependencyFields = [];
|
||||
_dependencyFields.Add(fieldSymbol.Type, dependencyFields);
|
||||
}
|
||||
|
||||
dependencyFields.Add(fieldSymbol);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDependency(ISymbol symbol)
|
||||
{
|
||||
foreach (var attributeData in symbol.GetAttributes())
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, dependencyAttributeType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void End(SymbolAnalysisContext context)
|
||||
{
|
||||
lock (_dependencyFields)
|
||||
{
|
||||
foreach (var pair in _dependencyFields)
|
||||
{
|
||||
var fieldType = pair.Key;
|
||||
var fields = pair.Value;
|
||||
if (fields.Count <= 1)
|
||||
continue;
|
||||
|
||||
// Sort so we can have deterministic order to skip reporting for a single field.
|
||||
// Whichever sorts first doesn't get reported.
|
||||
fields.Sort(static (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
|
||||
// Start at index 1 to skip first field.
|
||||
var firstField = fields[0];
|
||||
for (var i = 1; i < fields.Count; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(Rule, field.Locations[0], fieldType.ToDisplayString(), firstField.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Robust.Analyzers/ForbidLiteralAnalyzer.cs
Normal file
101
Robust.Analyzers/ForbidLiteralAnalyzer.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class ForbidLiteralAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string ForbidLiteralType = "Robust.Shared.Analyzers.ForbidLiteralAttribute";
|
||||
|
||||
public static DiagnosticDescriptor ForbidLiteralRule = new(
|
||||
Diagnostics.IdForbidLiteral,
|
||||
"Parameter forbids literal values",
|
||||
"The {0} parameter of {1} forbids literal values",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true,
|
||||
"Pass in a validated wrapper type like ProtoId, or a const or static value."
|
||||
);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [ForbidLiteralRule];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterOperationAction(AnalyzeOperation, OperationKind.Invocation);
|
||||
}
|
||||
|
||||
private void AnalyzeOperation(OperationAnalysisContext context)
|
||||
{
|
||||
if (context.Operation is not IInvocationOperation invocationOperation)
|
||||
return;
|
||||
|
||||
// Check each parameter of the method invocation
|
||||
foreach (var argumentOperation in invocationOperation.Arguments)
|
||||
{
|
||||
// Check for our attribute on the parameter
|
||||
if (!AttributeHelper.HasAttribute(argumentOperation.Parameter, ForbidLiteralType, out _))
|
||||
continue;
|
||||
|
||||
// Handle parameters using the params keyword
|
||||
if (argumentOperation.Syntax is InvocationExpressionSyntax subExpressionSyntax)
|
||||
{
|
||||
// Check each param value
|
||||
foreach (var subArgument in subExpressionSyntax.ArgumentList.Arguments)
|
||||
{
|
||||
CheckArgumentSyntax(context, argumentOperation, subArgument);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not params, so just check the single parameter
|
||||
if (argumentOperation.Syntax is not ArgumentSyntax argumentSyntax)
|
||||
continue;
|
||||
|
||||
CheckArgumentSyntax(context, argumentOperation, argumentSyntax);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckArgumentSyntax(OperationAnalysisContext context, IArgumentOperation operation, ArgumentSyntax argumentSyntax)
|
||||
{
|
||||
// Handle collection types
|
||||
if (argumentSyntax.Expression is CollectionExpressionSyntax collectionExpressionSyntax)
|
||||
{
|
||||
// Check each value of the collection
|
||||
foreach (var elementSyntax in collectionExpressionSyntax.Elements)
|
||||
{
|
||||
if (elementSyntax is not ExpressionElementSyntax expressionSyntax)
|
||||
continue;
|
||||
|
||||
// Check if a literal was passed in
|
||||
if (expressionSyntax.Expression is not LiteralExpressionSyntax)
|
||||
continue;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(ForbidLiteralRule,
|
||||
expressionSyntax.GetLocation(),
|
||||
operation.Parameter.Name,
|
||||
(context.Operation as IInvocationOperation).TargetMethod.Name
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a collection, just a single value to check
|
||||
// Check if it's a literal
|
||||
if (argumentSyntax.Expression is not LiteralExpressionSyntax)
|
||||
return;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(ForbidLiteralRule,
|
||||
argumentSyntax.GetLocation(),
|
||||
operation.Parameter.Name,
|
||||
(context.Operation as IInvocationOperation).TargetMethod.Name
|
||||
));
|
||||
}
|
||||
}
|
||||
75
Robust.Analyzers/ObsoleteInheritanceAnalyzer.cs
Normal file
75
Robust.Analyzers/ObsoleteInheritanceAnalyzer.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class ObsoleteInheritanceAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string Attribute = "Robust.Shared.Analyzers.ObsoleteInheritanceAttribute";
|
||||
|
||||
public static readonly DiagnosticDescriptor Rule = new(
|
||||
Diagnostics.IdObsoleteInheritance,
|
||||
"Parent type has obsoleted inheritance",
|
||||
"Type '{0}' inherits from '{1}', which has obsoleted inheriting from itself",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public static readonly DiagnosticDescriptor RuleWithMessage = new(
|
||||
Diagnostics.IdObsoleteInheritanceWithMessage,
|
||||
"Parent type has obsoleted inheritance",
|
||||
"Type '{0}' inherits from '{1}', which has obsoleted inheriting from itself: \"{2}\"",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule, RuleWithMessage];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSymbolAction(CheckClass, SymbolKind.NamedType);
|
||||
}
|
||||
|
||||
private static void CheckClass(SymbolAnalysisContext context)
|
||||
{
|
||||
if (context.Symbol is not INamedTypeSymbol typeSymbol)
|
||||
return;
|
||||
|
||||
if (typeSymbol.IsValueType || typeSymbol.BaseType is not { } baseType)
|
||||
return;
|
||||
|
||||
if (!AttributeHelper.HasAttribute(baseType, Attribute, out var data))
|
||||
return;
|
||||
|
||||
var location = context.Symbol.Locations[0];
|
||||
|
||||
if (GetMessageFromAttributeData(data) is { } message)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
RuleWithMessage,
|
||||
location,
|
||||
[typeSymbol.Name, baseType.Name, message]));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(
|
||||
Rule,
|
||||
location,
|
||||
[typeSymbol.Name, baseType.Name]));
|
||||
}
|
||||
}
|
||||
|
||||
private static string? GetMessageFromAttributeData(AttributeData data)
|
||||
{
|
||||
if (data.ConstructorArguments is not [var message, ..])
|
||||
return null;
|
||||
|
||||
return message.Value as string;
|
||||
}
|
||||
}
|
||||
96
Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs
Normal file
96
Robust.Benchmarks/EntityManager/HasComponentBenchmark.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Engines;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.EntityManager;
|
||||
|
||||
[Virtual]
|
||||
public partial class HasComponentBenchmark
|
||||
{
|
||||
private static readonly Consumer Consumer = new();
|
||||
|
||||
private ISimulation _simulation = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
|
||||
private ComponentRegistration _compReg = default!;
|
||||
|
||||
private A _dummyA = new();
|
||||
|
||||
[UsedImplicitly]
|
||||
[Params(1, 10, 100, 1000)]
|
||||
public int N;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetup()
|
||||
{
|
||||
_simulation = RobustServerSimulation
|
||||
.NewSimulation()
|
||||
.RegisterComponents(f => f.RegisterClass<A>())
|
||||
.InitializeInstance();
|
||||
|
||||
_entityManager = _simulation.Resolve<IEntityManager>();
|
||||
var map = _simulation.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
_compReg = _entityManager.ComponentFactory.GetRegistration(typeof(A));
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
var uid = _entityManager.SpawnEntity(null, coords);
|
||||
_entityManager.AddComponent<A>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentGeneric()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var result = _entityManager.HasComponent<A>(uid);
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentCompReg()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var result = _entityManager.HasComponent(uid, _compReg);
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentType()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var result = _entityManager.HasComponent(uid, typeof(A));
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void HasComponentGetType()
|
||||
{
|
||||
for (var i = 2; i <= N+1; i++)
|
||||
{
|
||||
var uid = new EntityUid(i);
|
||||
var type = _dummyA.GetType();
|
||||
var result = _entityManager.HasComponent(uid, type);
|
||||
Consumer.Consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
[ComponentProtoName("A")]
|
||||
public sealed partial class A : Component
|
||||
{
|
||||
}
|
||||
}
|
||||
96
Robust.Benchmarks/Physics/BoxStackBenchmark.cs
Normal file
96
Robust.Benchmarks/Physics/BoxStackBenchmark.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
[MediumRunJob]
|
||||
public class PhysicsBoxStackBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void BoxStack()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
|
||||
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
|
||||
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
|
||||
|
||||
var xs = new[]
|
||||
{
|
||||
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
|
||||
};
|
||||
|
||||
var columnCount = 1;
|
||||
var rowCount = 15;
|
||||
PolygonShape shape;
|
||||
|
||||
for (var j = 0; j < columnCount; j++)
|
||||
{
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
var x = 0.0f;
|
||||
|
||||
var boxUid = entManager.SpawnEntity(null,
|
||||
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
|
||||
shape = new PolygonShape();
|
||||
shape.SetAsBox(0.5f, 0.5f);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
|
||||
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
}
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
}
|
||||
}
|
||||
92
Robust.Benchmarks/Physics/CircleStackBenchmark.cs
Normal file
92
Robust.Benchmarks/Physics/CircleStackBenchmark.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
public class PhysicsCircleStackBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CircleStack()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 10000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
|
||||
var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
|
||||
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
|
||||
|
||||
var xs = new[]
|
||||
{
|
||||
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
|
||||
};
|
||||
|
||||
var columnCount = 1;
|
||||
var rowCount = 15;
|
||||
PhysShapeCircle shape;
|
||||
|
||||
for (var j = 0; j < columnCount; j++)
|
||||
{
|
||||
for (var i = 0; i < rowCount; i++)
|
||||
{
|
||||
var x = 0.0f;
|
||||
|
||||
var boxUid = entManager.SpawnEntity(null,
|
||||
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
shape = new PhysShapeCircle(0.5f);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
// TODO: Need to detect shape and work out if we need to use fixedrotation
|
||||
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
}
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
}
|
||||
}
|
||||
91
Robust.Benchmarks/Physics/PyramidBenchmark.cs
Normal file
91
Robust.Benchmarks/Physics/PyramidBenchmark.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
public class PhysicsPyramidBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 300; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Pyramid()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 5000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
const byte count = 20;
|
||||
|
||||
// Setup ground
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
|
||||
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
|
||||
// Setup boxes
|
||||
float a = 0.5f;
|
||||
PolygonShape shape = new();
|
||||
shape.SetAsBox(a, a);
|
||||
|
||||
var x = new Vector2(-7.0f, 0.75f);
|
||||
Vector2 y;
|
||||
Vector2 deltaX = new Vector2(0.5625f, 1.25f);
|
||||
Vector2 deltaY = new Vector2(1.125f, 0.0f);
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
y = x;
|
||||
|
||||
for (var j = i; j < count; ++j)
|
||||
{
|
||||
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(y, mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
|
||||
y += deltaY;
|
||||
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
|
||||
x += deltaX;
|
||||
}
|
||||
}
|
||||
}
|
||||
108
Robust.Benchmarks/Physics/TumblerBenchmark.cs
Normal file
108
Robust.Benchmarks/Physics/TumblerBenchmark.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.Benchmarks.Physics;
|
||||
|
||||
[Virtual]
|
||||
public class PhysicsTumblerBenchmark
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
|
||||
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
var mapUid = entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 300; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
|
||||
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
|
||||
physics.SetFixedRotation(boxUid, false, body: box);
|
||||
var shape = new PolygonShape();
|
||||
shape.SetAsBox(0.125f, 0.125f);
|
||||
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
|
||||
if (entManager.TryGetComponent(mapUid, out BroadphaseComponent? mapBroadphase))
|
||||
entManager.System<SharedBroadphaseSystem>().RebuildBottomUp(mapBroadphase);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Tumbler()
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupTumbler(IEntityManager entManager, MapId mapId)
|
||||
{
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
var joints = entManager.System<SharedJointSystem>();
|
||||
|
||||
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId));
|
||||
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
|
||||
// Due to lookup changes fixtureless bodies are invalid, so
|
||||
var cShape = new PhysShapeCircle(1f);
|
||||
fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
|
||||
|
||||
var bodyUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
var body = entManager.AddComponent<PhysicsComponent>(bodyUid);
|
||||
|
||||
physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body);
|
||||
physics.SetSleepingAllowed(bodyUid, body, false);
|
||||
physics.SetFixedRotation(bodyUid, false, body: body);
|
||||
|
||||
|
||||
// TODO: Box2D just deref, bleh shape structs someday
|
||||
var shape1 = new PolygonShape();
|
||||
shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
|
||||
|
||||
var shape2 = new PolygonShape();
|
||||
shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
|
||||
|
||||
var shape3 = new PolygonShape();
|
||||
shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
|
||||
|
||||
var shape4 = new PolygonShape();
|
||||
shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
|
||||
fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
|
||||
|
||||
physics.WakeBody(groundUid, body: ground);
|
||||
physics.WakeBody(bodyUid, body: body);
|
||||
var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
|
||||
revolute.LocalAnchorA = new Vector2(0f, 10f);
|
||||
revolute.LocalAnchorB = new Vector2(0f, 0f);
|
||||
revolute.ReferenceAngle = 0f;
|
||||
revolute.MotorSpeed = 0.05f * MathF.PI;
|
||||
revolute.MaxMotorTorque = 100000000f;
|
||||
revolute.EnableMotor = true;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,10 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
|
||||
|
||||
<!-- Needed to pin transitive dependency versions. -->
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" />
|
||||
<PackageReference Include="System.Formats.Asn1" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
|
||||
@@ -42,8 +42,8 @@ public class RecursiveMoveBenchmark : RobustIntegrationTest
|
||||
public void GlobalSetup()
|
||||
{
|
||||
ProgramShared.PathOffset = "../../../../";
|
||||
var server = StartServer();
|
||||
var client = StartClient();
|
||||
var server = StartServer(new() {Pool = false});
|
||||
var client = StartClient(new() {Pool = false});
|
||||
|
||||
Task.WhenAll(client.WaitIdleAsync(), server.WaitIdleAsync()).Wait();
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ using XamlX.Transform;
|
||||
using XamlX.Transform.Transformers;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
// Yes dude I know this source generator isn't incremental, I'll fix it eventually.
|
||||
#pragma warning disable RS1035
|
||||
|
||||
namespace Robust.Client.NameGenerator
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -64,6 +64,8 @@ internal abstract class BaseRobustCefClient : CefClient
|
||||
string title,
|
||||
string defaultFilePath,
|
||||
string[] acceptFilters,
|
||||
string[] acceptExtensions,
|
||||
string[] acceptDescriptions,
|
||||
CefFileDialogCallback callback)
|
||||
{
|
||||
callback.Cancel();
|
||||
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace Robust.Client.WebView.Cef
|
||||
//commandLine.AppendSwitch("--disable-gpu-compositing");
|
||||
//commandLine.AppendSwitch("--in-process-gpu");
|
||||
|
||||
commandLine.AppendSwitch("--off-screen-rendering-enabled");
|
||||
|
||||
commandLine.AppendSwitch("disable-threaded-scrolling", "1");
|
||||
commandLine.AppendSwitch("disable-features", "TouchpadAndWheelScrollLatching,AsyncWheelEvents");
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Robust.Client.WebView.Cef
|
||||
var info = CefWindowInfo.Create();
|
||||
info.Bounds = new CefRectangle(0, 0, createParams.Width, createParams.Height);
|
||||
info.SetAsPopup(mainHWnd, "ss14cef");
|
||||
info.RuntimeStyle = CefRuntimeStyle.Alloy;
|
||||
|
||||
var impl = new WebViewWindowImpl(this);
|
||||
|
||||
|
||||
@@ -484,27 +484,27 @@ namespace Robust.Client.WebView.Cef
|
||||
public void FocusEntered()
|
||||
{
|
||||
if (_textInputActive)
|
||||
_clyde.TextInputStart();
|
||||
Owner.Root?.Window?.TextInputStart();
|
||||
}
|
||||
|
||||
public void FocusExited()
|
||||
{
|
||||
if (_textInputActive)
|
||||
_clyde.TextInputStop();
|
||||
Owner.Root?.Window?.TextInputStop();
|
||||
}
|
||||
|
||||
public void TextInputStart()
|
||||
{
|
||||
_textInputActive = true;
|
||||
if (Owner.HasKeyboardFocus())
|
||||
_clyde.TextInputStart();
|
||||
Owner.Root?.Window?.TextInputStart();
|
||||
}
|
||||
|
||||
public void TextInputStop()
|
||||
{
|
||||
_textInputActive = false;
|
||||
if (Owner.HasKeyboardFocus())
|
||||
_clyde.TextInputStop();
|
||||
Owner.Root?.Window?.TextInputStop();
|
||||
}
|
||||
|
||||
private sealed class LiveData
|
||||
@@ -587,8 +587,11 @@ namespace Robust.Client.WebView.Cef
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
|
||||
CefRectangle[] dirtyRects, IntPtr sharedHandle)
|
||||
protected override void OnAcceleratedPaint(
|
||||
CefBrowser browser,
|
||||
CefPaintElementType type,
|
||||
CefRectangle[] dirtyRects,
|
||||
in CefAcceleratedPaintInfo info)
|
||||
{
|
||||
// Unused, but we're forced to implement it so.. NOOP.
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
|
||||
<CETCompat>false</CETCompat>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -40,11 +40,7 @@ namespace Robust.Client.Animations
|
||||
var keyFrame = KeyFrames[keyFrameIndex];
|
||||
|
||||
var audioParams = keyFrame.AudioParamsFunc.Invoke();
|
||||
var audio = new SoundPathSpecifier(keyFrame.Resource)
|
||||
{
|
||||
Params = audioParams
|
||||
};
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().PlayEntity(audio, Filter.Local(), entity, true);
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().PlayEntity(keyFrame.Specifier, Filter.Local(), entity, true, audioParams);
|
||||
}
|
||||
|
||||
return (keyFrameIndex, playingTime);
|
||||
@@ -55,7 +51,7 @@ namespace Robust.Client.Animations
|
||||
/// <summary>
|
||||
/// The RSI state to play when this keyframe gets triggered.
|
||||
/// </summary>
|
||||
public readonly string Resource;
|
||||
public readonly ResolvedSoundSpecifier Specifier;
|
||||
|
||||
/// <summary>
|
||||
/// A function that returns the audio parameter to be used.
|
||||
@@ -69,9 +65,9 @@ namespace Robust.Client.Animations
|
||||
/// </summary>
|
||||
public readonly float KeyTime;
|
||||
|
||||
public KeyFrame(string resource, float keyTime, Func<AudioParams>? audioParams = null)
|
||||
public KeyFrame(ResolvedSoundSpecifier specifier, float keyTime, Func<AudioParams>? audioParams = null)
|
||||
{
|
||||
Resource = resource;
|
||||
Specifier = specifier;
|
||||
KeyTime = keyTime;
|
||||
AudioParamsFunc = audioParams ?? (() => AudioParams.Default);
|
||||
}
|
||||
|
||||
@@ -84,6 +84,19 @@ internal partial class AudioManager
|
||||
AL.Listener(ALListenerfv.Orientation, ref at, ref up);
|
||||
}
|
||||
|
||||
void IAudioInternal.Remove(AudioStream stream)
|
||||
{
|
||||
if (stream.ClydeHandle == null)
|
||||
return;
|
||||
|
||||
if (!_audioSampleBuffers.Remove(stream.BufferId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AL.DeleteBuffer(stream.BufferId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
|
||||
{
|
||||
@@ -120,9 +133,9 @@ internal partial class AudioManager
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
|
||||
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
return new AudioStream(this, buffer, handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -179,9 +192,9 @@ internal partial class AudioManager
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(wav.Data.Length / (double) wav.BlockAlign / wav.SampleRate);
|
||||
return new AudioStream(handle, length, wav.NumChannels, name);
|
||||
return new AudioStream(this, buffer, handle, length, wav.NumChannels, name);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -210,8 +223,8 @@ internal partial class AudioManager
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
|
||||
return new AudioStream(this, buffer, handle, length, channels, name);
|
||||
}
|
||||
|
||||
public void SetMasterGain(float newGain)
|
||||
@@ -293,7 +306,7 @@ internal partial class AudioManager
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
// TODO: This really shouldn't be indexing based on the ClydeHandle...
|
||||
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[(int) stream.ClydeHandle!.Value].BufferHandle);
|
||||
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[stream.BufferId].BufferHandle);
|
||||
|
||||
var audioSource = new AudioSource(this, source, stream);
|
||||
_audioSources.Add(source, new WeakReference<BaseAudioSource>(audioSource));
|
||||
@@ -370,5 +383,12 @@ internal partial class AudioManager
|
||||
}
|
||||
|
||||
_bufferedAudioSources.Clear();
|
||||
|
||||
foreach (var buffer in _audioSampleBuffers.Values)
|
||||
{
|
||||
DeleteAudioBufferOnMainThread(buffer.BufferHandle);
|
||||
}
|
||||
|
||||
_audioSampleBuffers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Threading;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -17,13 +18,15 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Shared.IoC.Dependency] private readonly ILogManager _logMan = default!;
|
||||
[Shared.IoC.Dependency] private readonly IReloadManager _reload = default!;
|
||||
[Shared.IoC.Dependency] private readonly IResourceCache _cache = default!;
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
private ALDevice _openALDevice;
|
||||
private ALContext _openALContext;
|
||||
|
||||
private readonly List<LoadedAudioSample> _audioSampleBuffers = new();
|
||||
private readonly Dictionary<int, LoadedAudioSample> _audioSampleBuffers = new();
|
||||
|
||||
private readonly Dictionary<int, WeakReference<BaseAudioSource>> _audioSources =
|
||||
new();
|
||||
@@ -116,6 +119,22 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterGain, true);
|
||||
|
||||
_reload.Register("/Audio", "*.ogg");
|
||||
_reload.Register("/Audio", "*.wav");
|
||||
|
||||
_reload.OnChanged += OnReload;
|
||||
}
|
||||
|
||||
private void OnReload(ResPath args)
|
||||
{
|
||||
if (args.Extension != "ogg" &&
|
||||
args.Extension != "wav")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cache.ReloadResource<AudioResource>(args);
|
||||
}
|
||||
|
||||
internal bool IsMainThread()
|
||||
@@ -140,6 +159,11 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
}
|
||||
}
|
||||
|
||||
internal void LogError(string message)
|
||||
{
|
||||
OpenALSawmill.Error(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like _checkAlError but allows custom data to be passed in as relevant.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,8 +6,15 @@ namespace Robust.Client.Audio;
|
||||
/// <summary>
|
||||
/// Has the metadata for a particular audio stream as well as the relevant internal handle to it.
|
||||
/// </summary>
|
||||
public sealed class AudioStream
|
||||
public sealed class AudioStream : IDisposable
|
||||
{
|
||||
private IAudioInternal _audio;
|
||||
|
||||
/// <summary>
|
||||
/// Buffer ID for this audio in AL.
|
||||
/// </summary>
|
||||
internal int BufferId { get; }
|
||||
|
||||
public TimeSpan Length { get; }
|
||||
internal IClydeHandle? ClydeHandle { get; }
|
||||
public string? Name { get; }
|
||||
@@ -15,8 +22,10 @@ public sealed class AudioStream
|
||||
public string? Artist { get; }
|
||||
public int ChannelCount { get; }
|
||||
|
||||
internal AudioStream(IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
|
||||
internal AudioStream(IAudioInternal internalAudio, int bufferId, IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
|
||||
{
|
||||
_audio = internalAudio;
|
||||
BufferId = bufferId;
|
||||
ClydeHandle = handle;
|
||||
Length = length;
|
||||
ChannelCount = channelCount;
|
||||
@@ -24,4 +33,9 @@ public sealed class AudioStream
|
||||
Title = title;
|
||||
Artist = artist;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_audio.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
@@ -56,6 +57,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
|
||||
private float _maxRayLength;
|
||||
private float _zOffset;
|
||||
private float _audioEndBuffer;
|
||||
|
||||
public override float ZOffset
|
||||
{
|
||||
@@ -79,8 +82,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
}
|
||||
|
||||
private float _zOffset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -108,20 +109,31 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
SubscribeNetworkEvent<PlayAudioEntityMessage>(OnEntityAudio);
|
||||
SubscribeNetworkEvent<PlayAudioPositionalMessage>(OnEntityCoordinates);
|
||||
|
||||
Subs.CVar(CfgManager, CVars.AudioEndBuffer, OnAudioBuffer, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioTickRate, OnAudioTickRate, true);
|
||||
InitializeLimit();
|
||||
}
|
||||
|
||||
private void OnAudioBuffer(float value)
|
||||
{
|
||||
_audioEndBuffer = value;
|
||||
}
|
||||
|
||||
private void OnAudioTickRate(int obj)
|
||||
{
|
||||
_audioFrameTime = 1f / obj;
|
||||
_audioFrameTimeRemaining = MathF.Min(_audioFrameTimeRemaining, _audioFrameTime);
|
||||
}
|
||||
|
||||
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
private void OnAudioState(Entity<AudioComponent> entity, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
var component = entity.Comp;
|
||||
|
||||
if (component.LifeStage < ComponentLifeStage.Initialized)
|
||||
return;
|
||||
|
||||
ApplyAudioParams(component.Params, component);
|
||||
component.Source.Global = component.Global;
|
||||
|
||||
@@ -145,21 +157,29 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
case AudioState.Stopped:
|
||||
component.StopPlaying();
|
||||
component.PlaybackPosition = 0f;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
// If playback position changed then update it.
|
||||
if (!string.IsNullOrEmpty(component.FileName))
|
||||
{
|
||||
var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
|
||||
var currentPosition = component.Source.PlaybackPosition;
|
||||
var diff = Math.Abs(position - currentPosition);
|
||||
var position = (float) ((entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart).TotalSeconds;
|
||||
var currentPosition = entity.Comp.Source.PlaybackPosition;
|
||||
var diff = Math.Abs(position - currentPosition);
|
||||
|
||||
if (diff > 0.1f)
|
||||
// Don't try to set the audio too far ahead.
|
||||
if (!string.IsNullOrEmpty(entity.Comp.FileName))
|
||||
{
|
||||
if (position > GetAudioLengthImpl(entity.Comp.FileName).TotalSeconds - _audioEndBuffer)
|
||||
{
|
||||
component.PlaybackPosition = position;
|
||||
entity.Comp.StopPlaying();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the difference is minor then we'll just keep playing it.
|
||||
if (diff > 0.1f)
|
||||
{
|
||||
entity.Comp.PlaybackPosition = position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,6 +227,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private void SetupSource(Entity<AudioComponent> entity, AudioResource audioResource, TimeSpan? length = null)
|
||||
{
|
||||
var component = entity.Comp;
|
||||
length ??= GetAudioLength(component.FileName);
|
||||
|
||||
// If audio came into range then start playback at the correct position.
|
||||
var offset = ((entity.Comp.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
|
||||
|
||||
if (TryAudioLimit(component.FileName))
|
||||
{
|
||||
@@ -230,10 +254,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
// Don't play until first frame so occlusion etc. are correct.
|
||||
component.Gain = 0f;
|
||||
|
||||
length ??= GetAudioLength(component.FileName);
|
||||
|
||||
// If audio came into range then start playback at the correct position.
|
||||
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % length.Value.TotalSeconds;
|
||||
// If the offset < buffer than just play it from the start.
|
||||
if (offset < AudioDespawnBuffer)
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
// Not enough audio to play
|
||||
else if (offset > length.Value.TotalSeconds - _audioEndBuffer)
|
||||
{
|
||||
component.StopPlaying();
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
@@ -415,6 +446,16 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return occlusion;
|
||||
}
|
||||
|
||||
private bool TryGetAudio(ResolvedSoundSpecifier specifier, [NotNullWhen(true)] out AudioResource? audio)
|
||||
{
|
||||
var filename = GetAudioPath(specifier);
|
||||
if (_resourceCache.TryGetResource(new ResPath(filename), out audio))
|
||||
return true;
|
||||
|
||||
Log.Error($"Server tried to play audio file {filename} which does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetAudio(string filename, [NotNullWhen(true)] out AudioResource? audio)
|
||||
{
|
||||
if (_resourceCache.TryGetResource(new ResPath(filename), out audio))
|
||||
@@ -433,15 +474,15 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams);
|
||||
return PlayStatic(specifier, Filter.Local(), coordinates, true, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, Filter.Local(), uid, true, audioParams);
|
||||
return PlayEntity(specifier, Filter.Local(), uid, true, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -453,6 +494,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayLocal(
|
||||
SoundSpecifier? sound,
|
||||
EntityUid source,
|
||||
EntityUid? soundInitiator,
|
||||
AudioParams? audioParams = null
|
||||
)
|
||||
{
|
||||
return PlayPredicted(sound, source, soundInitiator, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted && sound != null)
|
||||
@@ -466,21 +518,21 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Specifier = specifier,
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? PlayGlobal(audio, audioParams) : default;
|
||||
return TryGetAudio(specifier, out var audio) ? PlayGlobal(audio, specifier, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -488,12 +540,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null)
|
||||
{
|
||||
var (entity, component) = CreateAndStartPlayingStream(audioParams, stream);
|
||||
var (entity, component) = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
component.Global = true;
|
||||
component.Source.Global = true;
|
||||
Dirty(entity, component);
|
||||
DirtyField(entity, component, nameof(AudioComponent.Global));
|
||||
return (entity, component);
|
||||
}
|
||||
|
||||
@@ -502,22 +554,22 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Specifier = specifier,
|
||||
NetEntity = GetNetEntity(entity),
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? PlayEntity(audio, entity, audioParams) : default;
|
||||
return TryGetAudio(specifier, out var audio) ? PlayEntity(audio, entity, specifier, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -526,7 +578,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(entity))
|
||||
{
|
||||
@@ -534,7 +586,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
|
||||
|
||||
return playing;
|
||||
@@ -546,22 +598,22 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Specifier = specifier,
|
||||
Coordinates = GetNetCoordinates(coordinates),
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? PlayStatic(audio, coordinates, audioParams) : default;
|
||||
return TryGetAudio(specifier, out var audio) ? PlayStatic(audio, coordinates, specifier, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -570,7 +622,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
@@ -578,33 +630,33 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, coordinates);
|
||||
return playing;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
return PlayGlobal(specifier, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, entity, audioParams);
|
||||
return PlayEntity(specifier, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
return PlayStatic(specifier, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
return PlayGlobal(specifier, audioParams);
|
||||
}
|
||||
|
||||
public override void LoadStream<T>(Entity<AudioComponent> entity, T stream)
|
||||
@@ -618,39 +670,39 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
return PlayGlobal(specifier, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, uid, audioParams);
|
||||
return PlayEntity(specifier, uid, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, uid, audioParams);
|
||||
return PlayEntity(specifier, uid, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
return PlayStatic(specifier, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
return PlayStatic(specifier, coordinates, audioParams);
|
||||
}
|
||||
|
||||
private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream)
|
||||
private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, ResolvedSoundSpecifier? specifier, AudioStream stream)
|
||||
{
|
||||
var audioP = audioParams ?? AudioParams.Default;
|
||||
var entity = SetupAudio(null, audioP, initialize: false, length: stream.Length);
|
||||
var entity = SetupAudio(specifier, audioP, initialize: false, length: stream.Length);
|
||||
LoadStream(entity, stream);
|
||||
EntityManager.InitializeAndStartEntity(entity);
|
||||
var comp = entity.Comp;
|
||||
@@ -658,7 +710,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
// TODO clamp the offset inside of SetPlaybackPosition() itself.
|
||||
var offset = audioP.PlayOffsetSeconds;
|
||||
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
|
||||
var maxOffset = Math.Max((float) stream.Length.TotalSeconds - 0.01f, 0f);
|
||||
offset = Math.Clamp(offset, 0f, maxOffset);
|
||||
source.PlaybackPosition = offset;
|
||||
|
||||
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
|
||||
@@ -682,17 +735,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
private void OnEntityCoordinates(PlayAudioPositionalMessage ev)
|
||||
{
|
||||
PlayStatic(ev.FileName, GetCoordinates(ev.Coordinates), ev.AudioParams, false);
|
||||
PlayStatic(ev.Specifier, GetCoordinates(ev.Coordinates), ev.AudioParams, false);
|
||||
}
|
||||
|
||||
private void OnEntityAudio(PlayAudioEntityMessage ev)
|
||||
{
|
||||
PlayEntity(ev.FileName, GetEntity(ev.NetEntity), ev.AudioParams, false);
|
||||
PlayEntity(ev.Specifier, GetEntity(ev.NetEntity), ev.AudioParams, false);
|
||||
}
|
||||
|
||||
private void OnGlobalAudio(PlayAudioGlobalMessage ev)
|
||||
{
|
||||
PlayGlobal(ev.FileName, ev.AudioParams, false);
|
||||
PlayGlobal(ev.Specifier, ev.AudioParams, false);
|
||||
}
|
||||
|
||||
protected override TimeSpan GetAudioLengthImpl(string filename)
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Robust.Client.Audio;
|
||||
/// </summary>
|
||||
internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
{
|
||||
private int _audioBuffer;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void InitializePostWindowing()
|
||||
{
|
||||
@@ -65,6 +67,11 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(AudioStream stream)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopAllAudio()
|
||||
{
|
||||
@@ -101,11 +108,11 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
return new AudioStream(null, length, channels, name);
|
||||
return new AudioStream(this, _audioBuffer++, null, length, channels, name);
|
||||
}
|
||||
|
||||
private static AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
|
||||
private AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
|
||||
{
|
||||
return new AudioStream(null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
|
||||
return new AudioStream(this, _audioBuffer++, null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ internal interface IAudioInternal : IAudioManager
|
||||
|
||||
void SetAttenuation(Attenuation attenuation);
|
||||
|
||||
void Remove(AudioStream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Stops all audio from playing.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
@@ -11,7 +10,7 @@ namespace Robust.Client.Audio;
|
||||
public interface IAudioManager
|
||||
{
|
||||
IAudioSource? CreateAudioSource(AudioStream stream);
|
||||
|
||||
|
||||
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
|
||||
|
||||
AudioStream LoadAudioWav(Stream stream, string? name = null);
|
||||
|
||||
@@ -33,12 +33,6 @@ public interface IMidiRenderer : IDisposable
|
||||
/// </summary>
|
||||
bool LoopMidi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This increases all note on velocities to 127.
|
||||
/// </summary>
|
||||
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
|
||||
bool VolumeBoost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The midi program (instrument) the renderer is using.
|
||||
/// </summary>
|
||||
|
||||
@@ -205,14 +205,6 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
|
||||
public bool VolumeBoost
|
||||
{
|
||||
get => VelocityOverride == 127;
|
||||
set => VelocityOverride = value ? 127 : null;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? TrackingEntity { get; set; } = null;
|
||||
|
||||
@@ -234,6 +226,9 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
if (value == _master)
|
||||
return;
|
||||
|
||||
if (CheckMasterCycle(value))
|
||||
throw new InvalidOperationException("Tried to set master to a child of this renderer!");
|
||||
|
||||
if (_master is { Disposed: false })
|
||||
{
|
||||
try
|
||||
@@ -737,4 +732,22 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
_synth?.Dispose();
|
||||
_player?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check that a given renderer is not already a child of this renderer, i.e. it would introduce a cycle if set as master of this renderer.
|
||||
/// </summary>
|
||||
private bool CheckMasterCycle(IMidiRenderer? otherRenderer)
|
||||
{
|
||||
// Doesn't inside drift, cringe.
|
||||
|
||||
while (otherRenderer != null)
|
||||
{
|
||||
if (otherRenderer == this)
|
||||
return true;
|
||||
|
||||
otherRenderer = otherRenderer.Master;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ internal sealed class AudioSource : BaseAudioSource
|
||||
/// <summary>
|
||||
/// Underlying stream to the audio.
|
||||
/// </summary>
|
||||
private readonly AudioStream _sourceStream;
|
||||
internal readonly AudioStream SourceStream;
|
||||
|
||||
#if DEBUG
|
||||
private bool _didPositionWarning;
|
||||
@@ -21,7 +21,7 @@ internal sealed class AudioSource : BaseAudioSource
|
||||
|
||||
public AudioSource(AudioManager master, int sourceHandle, AudioStream sourceStream) : base(master, sourceHandle)
|
||||
{
|
||||
_sourceStream = sourceStream;
|
||||
SourceStream = sourceStream;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -47,13 +47,13 @@ internal sealed class AudioSource : BaseAudioSource
|
||||
#if DEBUG
|
||||
// OpenAL doesn't seem to want to play stereo positionally.
|
||||
// Log a warning if people try to.
|
||||
if (_sourceStream.ChannelCount > 1 && !_didPositionWarning)
|
||||
if (SourceStream.ChannelCount > 1 && !_didPositionWarning)
|
||||
{
|
||||
_didPositionWarning = true;
|
||||
Master.OpenALSawmill.Warning("Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.",
|
||||
_sourceStream.Name);
|
||||
SourceStream.Name);
|
||||
// warning isn't enough, people just ignore it :(
|
||||
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo.");
|
||||
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{SourceStream.Name}'. Make sure the audio is MONO, not stereo.");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -208,6 +208,12 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
}
|
||||
set
|
||||
{
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
Master.LogError($"Tried to set NaN gain, setting audio source to 0f: {Environment.StackTrace}");
|
||||
value = 0f;
|
||||
}
|
||||
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
|
||||
@@ -88,10 +88,10 @@ namespace Robust.Client
|
||||
{
|
||||
if (GameInfo != null)
|
||||
{
|
||||
GameInfo.TickRate = (byte) tickrate;
|
||||
GameInfo.TickRate = (ushort) tickrate;
|
||||
}
|
||||
|
||||
_timing.SetTickRateAt((byte) tickrate, info.TickChanged);
|
||||
_timing.SetTickRateAt((ushort) tickrate, info.TickChanged);
|
||||
_logger.Info($"Tickrate changed to: {tickrate} on tick {_timing.CurTick}");
|
||||
}
|
||||
|
||||
@@ -115,10 +115,6 @@ namespace Robust.Client
|
||||
/// <inheritdoc />
|
||||
public void DisconnectFromServer(string reason)
|
||||
{
|
||||
DebugTools.Assert(RunLevel > ClientRunLevel.Initialize);
|
||||
DebugTools.Assert(_net.IsConnected);
|
||||
// run level changed in OnNetDisconnect()
|
||||
// are both of these *really* needed?
|
||||
_net.ClientDisconnect(reason);
|
||||
}
|
||||
|
||||
@@ -395,6 +391,6 @@ namespace Robust.Client
|
||||
/// </summary>
|
||||
public int ServerMaxPlayers { get; set; }
|
||||
|
||||
public byte TickRate { get; internal set; }
|
||||
public uint TickRate { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Client.HWId;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Localization;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Player;
|
||||
@@ -35,6 +37,7 @@ using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -45,6 +48,7 @@ using Robust.Shared.Replays;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Upload;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client
|
||||
@@ -101,6 +105,9 @@ namespace Robust.Client
|
||||
deps.Register<ProfViewManager>();
|
||||
deps.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
|
||||
deps.Register<NetworkResourceManager>();
|
||||
deps.Register<IReloadManager, ReloadManager>();
|
||||
deps.Register<ILocalizationManager, ClientLocalizationManager>();
|
||||
deps.Register<ILocalizationManagerInternal, ClientLocalizationManager>();
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
@@ -158,6 +165,7 @@ namespace Robust.Client
|
||||
|
||||
deps.Register<IXamlProxyHelper, XamlProxyHelper>();
|
||||
deps.Register<MarkupTagManager>();
|
||||
deps.Register<IHWId, BasicHWId>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,29 +173,51 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowPositionsCommand : LocalizedCommands
|
||||
internal sealed class ShowPositionsCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
|
||||
public override string Command => "showpos";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
|
||||
mgr.DebugPositions = !mgr.DebugPositions;
|
||||
_debugDrawing.DebugPositions = !_debugDrawing.DebugPositions;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowRotationsCommand : LocalizedCommands
|
||||
internal sealed class ShowRotationsCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
|
||||
public override string Command => "showrot";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
|
||||
mgr.DebugRotations = !mgr.DebugRotations;
|
||||
_debugDrawing.DebugRotations = !_debugDrawing.DebugRotations;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowVelocitiesCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
|
||||
public override string Command => "showvel";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_debugDrawing.DebugVelocities = !_debugDrawing.DebugVelocities;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowAngularVelocitiesCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
|
||||
public override string Command => "showangvel";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_debugDrawing.DebugAngularVelocities = !_debugDrawing.DebugAngularVelocities;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class DebugAnchoredCommand : LocalizedCommands
|
||||
public sealed class DebugAnchoredCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly DebugAnchoringSystem _system = default!;
|
||||
|
||||
public override string Command => "showanchored";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugAnchoringSystem>().Enabled ^= true;
|
||||
_system.Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
var wantName = args.Length > 0 ? args[0] : null;
|
||||
|
||||
var basePath = Path.GetDirectoryName(UserDataDir.GetUserDataDir(_gameController))!;
|
||||
var dbPath = Path.Combine(basePath, "launcher", "settings.db");
|
||||
var basePath = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
var launcherDirName = Environment.GetEnvironmentVariable("SS14_LAUNCHER_APPDATA_NAME") ?? "launcher";
|
||||
var dbPath = Path.Combine(basePath, launcherDirName, "settings.db");
|
||||
|
||||
#if USE_SYSTEM_SQLITE
|
||||
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
#if DEBUG
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
internal sealed class LightDebugCommand : LocalizedCommands
|
||||
internal sealed class LightDebugCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly DebugLightTreeSystem _system = default!;
|
||||
|
||||
public override string Command => "lightbb";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugLightTreeSystem>().Enabled ^= true;
|
||||
_system.Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
Robust.Client/Console/Commands/LocalizationCommands.cs
Normal file
67
Robust.Client/Console/Commands/LocalizationCommands.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Console.Commands;
|
||||
|
||||
[UsedImplicitly]
|
||||
internal sealed class LocalizationSetCulture : LocalizedCommands
|
||||
{
|
||||
private const string Name = "localization_set_culture";
|
||||
private const int ArgumentCount = 1;
|
||||
|
||||
public override string Command => Name;
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != ArgumentCount)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
CultureInfo culture;
|
||||
try
|
||||
{
|
||||
culture = CultureInfo.GetCultureInfo(args[0], predefinedOnly: false);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-cultureinfo", ("arg", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
LocalizationManager.SetCulture(culture);
|
||||
shell.WriteLine(LocalizationManager.GetString("cmd-localization_set_culture-changed",
|
||||
("code", culture.Name),
|
||||
("nativeName", culture.NativeName),
|
||||
("englishName", culture.EnglishName)));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return args.Length switch
|
||||
{
|
||||
1 => CompletionResult.FromHintOptions(GetCultureNames(),
|
||||
LocalizationManager.GetString("cmd-localization_set_culture-culture-name")),
|
||||
_ => CompletionResult.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static HashSet<string> GetCultureNames()
|
||||
{
|
||||
var cultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures)
|
||||
.Where(x => !string.IsNullOrEmpty(x.Name))
|
||||
.ToArray();
|
||||
|
||||
var allNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
allNames.UnionWith(cultureInfos.Select(x => x.TwoLetterISOLanguageName));
|
||||
allNames.UnionWith(cultureInfos.Select(x => x.Name));
|
||||
|
||||
return allNames;
|
||||
}
|
||||
}
|
||||
18
Robust.Client/Console/Commands/ShowPlayerVelocityCommand.cs
Normal file
18
Robust.Client/Console/Commands/ShowPlayerVelocityCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class ShowPlayerVelocityCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly ShowPlayerVelocityDebugSystem _system = default!;
|
||||
|
||||
public override string Command => "showplayervelocity";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_system.Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class VelocitiesCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
|
||||
public override string Command => "showvelocities";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_entitySystems.GetEntitySystem<VelocityDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,6 @@ namespace Robust.Client.Credits
|
||||
/// </summary>
|
||||
public static class CreditsManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of open source software used in the engine and their license.
|
||||
/// </summary>
|
||||
[Obsolete("Use overload that takes in an explicit resource manager instead.")]
|
||||
public static IEnumerable<LicenseEntry> GetLicenses()
|
||||
{
|
||||
return GetLicenses(IoCManager.Resolve<IResourceManager>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of open source software used in the engine and their license.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,140 +1,221 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
namespace Robust.Client.Debugging;
|
||||
|
||||
/// <summary>
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// </summary>
|
||||
public sealed class DebugDrawingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private bool _debugPositions;
|
||||
private bool _debugRotations;
|
||||
private bool _debugVelocities;
|
||||
private bool _debugAngularVelocities;
|
||||
|
||||
/// <summary>
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
public sealed class DebugDrawingSystem : EntitySystem
|
||||
public bool DebugPositions
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
|
||||
private bool _debugPositions;
|
||||
private bool _debugRotations;
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugPositions
|
||||
get => _debugPositions;
|
||||
set
|
||||
{
|
||||
get => _debugPositions;
|
||||
set
|
||||
if (value == DebugPositions)
|
||||
{
|
||||
if (value == DebugPositions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_debugPositions = value;
|
||||
_debugPositions = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, EntityManager, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
|
||||
}
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local rotation.
|
||||
/// </summary>
|
||||
public bool DebugRotations
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the rotation for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugRotations
|
||||
{
|
||||
get => _debugRotations;
|
||||
set
|
||||
{
|
||||
get => _debugRotations;
|
||||
set
|
||||
if (value == DebugRotations)
|
||||
{
|
||||
if (value == DebugRotations)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_debugRotations = value;
|
||||
_debugRotations = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, EntityManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
|
||||
}
|
||||
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityPositionOverlay : Overlay
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local velocity for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugVelocities
|
||||
{
|
||||
get => _debugVelocities;
|
||||
set
|
||||
{
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityPositionOverlay(EntityLookupSystem lookup, IEntityManager entityManager, SharedTransformSystem transform)
|
||||
if (value == DebugVelocities)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_entityManager = entityManager;
|
||||
_transform = transform;
|
||||
return;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
_debugVelocities = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityVelocityOverlay>())
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = _transform.GetWorldPositionRotation(entity);
|
||||
|
||||
var xLine = worldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = worldRotation.RotateVec(Vector2.UnitY);
|
||||
|
||||
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
|
||||
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
|
||||
}
|
||||
_overlayManager.AddOverlay(new EntityVelocityOverlay(EntityManager, _lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityVelocityOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityRotationOverlay : Overlay
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the angular velocity for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugAngularVelocities
|
||||
{
|
||||
get => _debugAngularVelocities;
|
||||
set
|
||||
{
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityRotationOverlay(EntityLookupSystem lookup, IEntityManager entityManager)
|
||||
if (value == DebugAngularVelocities)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_entityManager = entityManager;
|
||||
return;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
_debugAngularVelocities = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityAngularVelocityOverlay>())
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
_overlayManager.AddOverlay(new EntityAngularVelocityOverlay(EntityManager, _lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityAngularVelocityOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
private sealed class EntityPositionOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
|
||||
var drawLine = worldRotation.RotateVec(-Vector2.UnitY);
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red);
|
||||
}
|
||||
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = _transform.GetWorldPositionRotation(uid);
|
||||
|
||||
var xLine = worldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = worldRotation.RotateVec(Vector2.UnitY);
|
||||
|
||||
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
|
||||
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityRotationOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = _transform.GetWorldPositionRotation(uid);
|
||||
|
||||
var drawLine = worldRotation.RotateVec(-Vector2.UnitY);
|
||||
|
||||
worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float multiplier = 0.2f;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
if(!physicsQuery.TryGetComponent(uid, out var physicsComp))
|
||||
continue;
|
||||
|
||||
var center = _transform.GetWorldPosition(uid);
|
||||
var localVelocity = physicsComp.LinearVelocity;
|
||||
|
||||
if (localVelocity != Vector2.Zero)
|
||||
worldHandle.DrawLine(center, center + localVelocity * multiplier, Color.Yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityAngularVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float multiplier = (float)(0.2 / (2 * System.Math.PI));
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
if(!physicsQuery.TryGetComponent(uid, out var physicsComp))
|
||||
continue;
|
||||
|
||||
var center = _transform.GetWorldPosition(uid);
|
||||
var angularVelocity = physicsComp.AngularVelocity;
|
||||
|
||||
if (angularVelocity != 0.0f)
|
||||
worldHandle.DrawCircle(center, angularVelocity * multiplier, angularVelocity > 0 ? Color.Magenta : Color.Blue, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -413,8 +413,9 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
var body = bodyEnt.Comp;
|
||||
var meta = _entityManager.GetComponent<MetaDataComponent>(bodyEnt);
|
||||
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {bodyEnt.Owner}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {bodyEnt.Owner} ({meta.EntityName})");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
@@ -544,7 +545,7 @@ namespace Robust.Client.Debugging
|
||||
switch (joint)
|
||||
{
|
||||
case DistanceJoint:
|
||||
worldHandle.DrawLine(xf1, xf2, JointColor);
|
||||
worldHandle.DrawLine(p1, p2, JointColor);
|
||||
break;
|
||||
case PrismaticJoint prisma:
|
||||
var pA = Transform.Mul(xfa, joint.LocalAnchorA);
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Robust.Client
|
||||
private Thread? _gameThread;
|
||||
private ISawmill _logger = default!;
|
||||
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Start(args, new GameControllerOptions());
|
||||
|
||||
@@ -31,6 +31,7 @@ using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
@@ -93,6 +94,8 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
|
||||
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IReloadManager _reload = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
|
||||
private IWebViewManagerHook? _webViewHook;
|
||||
|
||||
@@ -112,14 +115,28 @@ namespace Robust.Client
|
||||
_commandLineArgs = args;
|
||||
}
|
||||
|
||||
public string GameTitle()
|
||||
{
|
||||
return Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox";
|
||||
}
|
||||
|
||||
public string WindowIconSet()
|
||||
{
|
||||
return Options.WindowIconSet?.ToString() ?? _resourceManifest!.WindowIconSet ?? "";
|
||||
}
|
||||
|
||||
public string SplashLogo()
|
||||
{
|
||||
return Options.SplashLogo?.ToString() ?? _resourceManifest!.SplashLogo ?? "";
|
||||
}
|
||||
|
||||
internal bool StartupContinue(DisplayMode displayMode)
|
||||
{
|
||||
DebugTools.AssertNotNull(_resourceManifest);
|
||||
|
||||
_clyde.InitializePostWindowing();
|
||||
_audio.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(
|
||||
Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
|
||||
_clyde.SetWindowTitle(GameTitle());
|
||||
|
||||
_taskManager.Initialize();
|
||||
_parallelMgr.Initialize();
|
||||
@@ -165,12 +182,14 @@ namespace Robust.Client
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_console.Initialize();
|
||||
_loc.Initialize();
|
||||
|
||||
// Make sure this is done before we try to load prototypes,
|
||||
// avoid any possibility of race conditions causing the check to not finish
|
||||
// before prototype load.
|
||||
ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions);
|
||||
|
||||
_reload.Initialize();
|
||||
_reflectionManager.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDefaultPrototypes();
|
||||
@@ -368,7 +387,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
@@ -399,10 +418,8 @@ namespace Robust.Client
|
||||
// Handle GameControllerOptions implicit CVar overrides.
|
||||
_configurationManager.OverrideConVars(new[]
|
||||
{
|
||||
(CVars.DisplayWindowIconSet.Name,
|
||||
options.WindowIconSet?.ToString() ?? _resourceManifest.WindowIconSet ?? ""),
|
||||
(CVars.DisplaySplashLogo.Name,
|
||||
options.SplashLogo?.ToString() ?? _resourceManifest.SplashLogo ?? "")
|
||||
(CVars.DisplayWindowIconSet.Name, WindowIconSet()),
|
||||
(CVars.DisplaySplashLogo.Name, SplashLogo())
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
123
Robust.Client/GameObjects/ClientEntityManager.Spawn.cs
Normal file
123
Robust.Client/GameObjects/ClientEntityManager.Spawn.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed partial class ClientEntityManager
|
||||
{
|
||||
public override EntityUid PredictedSpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
|
||||
{
|
||||
var ent = SpawnAttachedTo(protoName, coordinates, overrides, rotation);
|
||||
FlagPredicted(ent);
|
||||
return ent;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override EntityUid PredictedSpawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true)
|
||||
{
|
||||
var ent = Spawn(protoName, overrides, doMapInit);
|
||||
FlagPredicted(ent);
|
||||
return ent;
|
||||
}
|
||||
|
||||
public override EntityUid PredictedSpawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
|
||||
{
|
||||
var ent = Spawn(protoName, coordinates, overrides, rotation);
|
||||
FlagPredicted(ent);
|
||||
return ent;
|
||||
}
|
||||
|
||||
public override EntityUid PredictedSpawnAtPosition(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
|
||||
{
|
||||
var ent = SpawnAtPosition(protoName, coordinates, overrides);
|
||||
FlagPredicted(ent);
|
||||
return ent;
|
||||
}
|
||||
|
||||
public override bool PredictedTrySpawnNextTo(
|
||||
string? protoName,
|
||||
EntityUid target,
|
||||
[NotNullWhen(true)] out EntityUid? uid,
|
||||
TransformComponent? xform = null,
|
||||
ComponentRegistry? overrides = null)
|
||||
{
|
||||
if (!TrySpawnNextTo(protoName, target, out uid, xform, overrides))
|
||||
return false;
|
||||
|
||||
FlagPredicted(uid.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool PredictedTrySpawnInContainer(
|
||||
string? protoName,
|
||||
EntityUid containerUid,
|
||||
string containerId,
|
||||
[NotNullWhen(true)] out EntityUid? uid,
|
||||
ContainerManagerComponent? containerComp = null,
|
||||
ComponentRegistry? overrides = null)
|
||||
{
|
||||
if (!TrySpawnInContainer(protoName, containerUid, containerId, out uid, containerComp, overrides))
|
||||
return false;
|
||||
|
||||
FlagPredicted(uid.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override EntityUid PredictedSpawnNextToOrDrop(string? protoName, EntityUid target, TransformComponent? xform = null, ComponentRegistry? overrides = null)
|
||||
{
|
||||
var ent = SpawnNextToOrDrop(protoName, target, xform, overrides);
|
||||
FlagPredicted(ent);
|
||||
return ent;
|
||||
}
|
||||
|
||||
public override EntityUid PredictedSpawnInContainerOrDrop(
|
||||
string? protoName,
|
||||
EntityUid containerUid,
|
||||
string containerId,
|
||||
TransformComponent? xform = null,
|
||||
ContainerManagerComponent? containerComp = null,
|
||||
ComponentRegistry? overrides = null)
|
||||
{
|
||||
var ent = SpawnInContainerOrDrop(protoName, containerUid, containerId, xform, containerComp, overrides);
|
||||
FlagPredicted(ent);
|
||||
return ent;
|
||||
}
|
||||
|
||||
public override EntityUid PredictedSpawnInContainerOrDrop(
|
||||
string? protoName,
|
||||
EntityUid containerUid,
|
||||
string containerId,
|
||||
out bool inserted,
|
||||
TransformComponent? xform = null,
|
||||
ContainerManagerComponent? containerComp = null,
|
||||
ComponentRegistry? overrides = null)
|
||||
{
|
||||
var ent = SpawnInContainerOrDrop(protoName,
|
||||
containerUid,
|
||||
containerId,
|
||||
out inserted,
|
||||
xform,
|
||||
containerComp,
|
||||
overrides);
|
||||
|
||||
FlagPredicted(ent);
|
||||
return ent;
|
||||
}
|
||||
|
||||
public override void FlagPredicted(Entity<MetaDataComponent?> ent)
|
||||
{
|
||||
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(IsClientSide(ent.Owner, ent.Comp));
|
||||
EnsureComponent<PredictedSpawnComponent>(ent.Owner);
|
||||
|
||||
// TODO: Need to map call site or something, needs to be consistent between client and server.
|
||||
}
|
||||
}
|
||||
@@ -101,11 +101,33 @@ namespace Robust.Client.GameObjects
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T>(Entity<T> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
// Client only dirties during prediction
|
||||
// Client only dirties during prediction
|
||||
if (_gameTiming.InPrediction)
|
||||
base.Dirty(ent, meta);
|
||||
}
|
||||
|
||||
public override void DirtyField<T>(EntityUid uid, T comp, string fieldName, MetaDataComponent? metadata = null)
|
||||
{
|
||||
// TODO Prediction
|
||||
// does the client actually need to dirty the field?
|
||||
// I.e., can't it just dirty the whole component to trigger a reset?
|
||||
|
||||
// Client only dirties during prediction
|
||||
if (_gameTiming.InPrediction)
|
||||
base.DirtyField(uid, comp, fieldName, metadata);
|
||||
}
|
||||
|
||||
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
|
||||
{
|
||||
// TODO Prediction
|
||||
// does the client actually need to dirty the field?
|
||||
// I.e., can't it just dirty the whole component to trigger a reset?
|
||||
|
||||
// Client only dirties during prediction
|
||||
if (_gameTiming.InPrediction)
|
||||
base.DirtyFields(uid, comp, meta, fields);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Dirty<T1, T2>(Entity<T1, T2> ent, MetaDataComponent? meta = null)
|
||||
{
|
||||
@@ -269,5 +291,54 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
|
||||
{
|
||||
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|
||||
|| ent.Comp1.EntityDeleted
|
||||
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// So there's 3 scenarios:
|
||||
// 1. Networked entity we just move to nullspace and rely on state handling.
|
||||
// 2. Clientside predicted entity we delete and rely on state handling.
|
||||
// 3. Clientside only entity that actually needs deleting here.
|
||||
|
||||
if (ent.Comp1.NetEntity.IsClientSide())
|
||||
{
|
||||
DeleteEntity(ent, ent.Comp1, ent.Comp2);
|
||||
}
|
||||
else
|
||||
{
|
||||
_xforms.DetachEntity(ent, ent.Comp2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void PredictedQueueDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
|
||||
{
|
||||
if (IsQueuedForDeletion(ent.Owner)
|
||||
|| !MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|
||||
|| ent.Comp1.EntityDeleted
|
||||
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ent.Comp1.NetEntity.IsClientSide())
|
||||
{
|
||||
// client-side QueueDeleteEntity re-fetches MetadataComp and checks IsClientSide().
|
||||
// base call to skip that.
|
||||
// TODO create override that takes in metadata comp
|
||||
base.QueueDeleteEntity(ent);
|
||||
}
|
||||
else
|
||||
{
|
||||
_xforms.DetachEntity(ent.Owner, ent.Comp2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,21 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class ShowSpriteBBCommand : LocalizedCommands
|
||||
public sealed class ShowSpriteBBCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly SpriteBoundsSystem _system = default!;
|
||||
|
||||
public override string Command => "showspritebb";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<SpriteBoundsSystem>().Enabled ^= true;
|
||||
_system.Enabled ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SpriteBoundsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly SpriteTreeSystem _spriteTree = default!;
|
||||
|
||||
@@ -40,7 +42,7 @@ namespace Robust.Client.GameObjects
|
||||
if (_enabled)
|
||||
{
|
||||
DebugTools.AssertNull(_overlay);
|
||||
_overlay = new SpriteBoundsOverlay(_spriteTree, _entityManager);
|
||||
_overlay = new SpriteBoundsOverlay(_spriteTree, _xformSystem);
|
||||
_overlayManager.AddOverlay(_overlay);
|
||||
}
|
||||
else
|
||||
@@ -59,13 +61,13 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly SharedTransformSystem _xformSystem;
|
||||
private SpriteTreeSystem _renderTree;
|
||||
|
||||
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, IEntityManager entityManager)
|
||||
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, SharedTransformSystem xformSystem)
|
||||
{
|
||||
_renderTree = renderTree;
|
||||
_entityManager = entityManager;
|
||||
_xformSystem = xformSystem;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
@@ -76,7 +78,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var (sprite, xform) in _renderTree.QueryAabb(currentMap, viewport))
|
||||
{
|
||||
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
|
||||
var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform);
|
||||
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
|
||||
|
||||
// Get scaled down bounds used to indicate the "south" of a sprite.
|
||||
|
||||
@@ -30,6 +30,7 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
using SysVec4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -753,12 +754,20 @@ namespace Robust.Client.GameObjects
|
||||
if (layerDatum.Shader == string.Empty)
|
||||
{
|
||||
layer.ShaderPrototype = null;
|
||||
layer.UnShaded = false;
|
||||
layer.Shader = null;
|
||||
}
|
||||
else if (layerDatum.Shader == SpriteSystem.UnshadedId.Id)
|
||||
{
|
||||
layer.ShaderPrototype = SpriteSystem.UnshadedId;
|
||||
layer.UnShaded = true;
|
||||
layer.Shader = null;
|
||||
}
|
||||
else if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
||||
{
|
||||
layer.ShaderPrototype = layerDatum.Shader;
|
||||
layer.Shader = prototype.Instance();
|
||||
layer.UnShaded = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -835,11 +844,28 @@ namespace Robust.Client.GameObjects
|
||||
if (!TryGetLayer(layer, out var theLayer, true))
|
||||
return;
|
||||
|
||||
if (shader == null)
|
||||
{
|
||||
theLayer.UnShaded = false;
|
||||
theLayer.Shader = null;
|
||||
theLayer.ShaderPrototype = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (prototype == SpriteSystem.UnshadedId.Id)
|
||||
{
|
||||
theLayer.UnShaded = true;
|
||||
theLayer.ShaderPrototype = SpriteSystem.UnshadedId;
|
||||
theLayer.Shader = null;
|
||||
return;
|
||||
}
|
||||
|
||||
theLayer.UnShaded = false;
|
||||
theLayer.Shader = shader;
|
||||
theLayer.ShaderPrototype = prototype;
|
||||
}
|
||||
|
||||
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
|
||||
public void LayerSetShader(object layerKey, ShaderInstance? shader, string? prototype = null)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer, true))
|
||||
return;
|
||||
@@ -1493,10 +1519,18 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ViewVariables] private readonly SpriteComponent _parent;
|
||||
|
||||
[ViewVariables] public string? ShaderPrototype;
|
||||
[ViewVariables] public ProtoId<ShaderPrototype>? ShaderPrototype;
|
||||
[ViewVariables] public ShaderInstance? Shader;
|
||||
[ViewVariables] public Texture? Texture;
|
||||
|
||||
/// <summary>
|
||||
/// If true, then this layer is drawn without lighting applied.
|
||||
/// Unshaded layers are given special treatment and don't just use the unshaded-shader to avoid having to
|
||||
/// unnecessarily swap out the light texture. This helps the number of batches that need to be sent to the
|
||||
/// GPU while drawing sprites.
|
||||
/// </summary>
|
||||
[ViewVariables] internal bool UnShaded;
|
||||
|
||||
private RSI? _rsi;
|
||||
[ViewVariables] public RSI? RSI
|
||||
{
|
||||
@@ -1663,6 +1697,7 @@ namespace Robust.Client.GameObjects
|
||||
if (toClone.Shader != null)
|
||||
{
|
||||
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
|
||||
UnShaded = toClone.UnShaded;
|
||||
ShaderPrototype = toClone.ShaderPrototype;
|
||||
}
|
||||
Texture = toClone.Texture;
|
||||
@@ -2078,6 +2113,20 @@ namespace Robust.Client.GameObjects
|
||||
drawingHandle.UseShader(Shader);
|
||||
|
||||
var layerColor = _parent.color * Color;
|
||||
|
||||
DebugTools.Assert(layerColor is {R: >= 0, G: >= 0, B: >= 0, A: >= 0}, "Negative colour modulation");
|
||||
|
||||
if (UnShaded)
|
||||
{
|
||||
DebugTools.AssertNull(Shader);
|
||||
|
||||
// Negative modulation values are used to disable light shading in the default shader.
|
||||
// Specifically we set colour = -1 - colour
|
||||
// This ensures that non-negative values become negative & is trivially invertible.
|
||||
// Alternatively we could just clamp the colour to [0,1] and subtract a constant.
|
||||
layerColor = new(new SysVec4(-1) - layerColor.RGBA);
|
||||
}
|
||||
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(textureSize/-2, textureSize);
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Robust.Client.GameObjects
|
||||
foreach (var key in remie)
|
||||
{
|
||||
component.PlayingAnimations.Remove(key);
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = uid, Key = key, Finished = true};
|
||||
var completedEvent = new AnimationCompletedEvent(uid, component, key, true);
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
|
||||
}
|
||||
|
||||
@@ -99,15 +99,6 @@ namespace Robust.Client.GameObjects
|
||||
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start playing an animation.
|
||||
/// </summary>
|
||||
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
|
||||
public void Play(AnimationPlayerComponent component, Animation animation, string key)
|
||||
{
|
||||
Play(new Entity<AnimationPlayerComponent>(component.Owner, component), animation, key);
|
||||
}
|
||||
|
||||
public void Play(Entity<AnimationPlayerComponent> ent, Animation animation, string key)
|
||||
{
|
||||
AddComponent(ent);
|
||||
@@ -152,6 +143,14 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
#endif
|
||||
|
||||
foreach (var track in animation.AnimationTracks)
|
||||
{
|
||||
if (track is not AnimationTrackSpriteFlick)
|
||||
continue;
|
||||
|
||||
track.AdvancePlayback(ent.Owner, 0, 0, 0f);
|
||||
}
|
||||
|
||||
ent.Comp.PlayingAnimations.Add(key, playback);
|
||||
}
|
||||
|
||||
@@ -188,7 +187,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = entity.Owner, Key = key, Finished = false};
|
||||
var completedEvent = new AnimationCompletedEvent(entity.Owner, entity.Comp, key, false);
|
||||
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
|
||||
}
|
||||
|
||||
@@ -203,13 +202,33 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public sealed class AnimationCompletedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity associated with the event.
|
||||
/// </summary>
|
||||
public EntityUid Uid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The animation player component associated with the entity this event was raised on.
|
||||
/// </summary>
|
||||
public AnimationPlayerComponent AnimationPlayer { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The key associated with the animation that was completed.
|
||||
/// </summary>
|
||||
public string Key { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the animation finished by getting to its natural end.
|
||||
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(Robust.Client.GameObjects.AnimationPlayerComponent,string)"/> or similar overloads.
|
||||
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(EntityUid,AnimationPlayerComponent,string)"/> or similar overloads.
|
||||
/// </summary>
|
||||
public bool Finished { get; init; }
|
||||
|
||||
public AnimationCompletedEvent(EntityUid uid, AnimationPlayerComponent animationPlayer, string key, bool finished = true)
|
||||
{
|
||||
Uid = uid;
|
||||
AnimationPlayer = animationPlayer;
|
||||
Key = key;
|
||||
Finished = finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
protected override void OnAppearanceGetState(EntityUid uid, AppearanceComponent component, ref ComponentGetState args)
|
||||
{
|
||||
// TODO Game State
|
||||
// Force the client to serialize & de-serialize implicitly generated component states.
|
||||
var clone = CloneAppearanceData(component.AppearanceData);
|
||||
args.State = new AppearanceComponentState(clone);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -17,7 +16,6 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class ContainerSystem : SharedContainerSystem
|
||||
{
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
[Dependency] private readonly PointLightSystem _lightSys = default!;
|
||||
@@ -242,7 +240,7 @@ namespace Robust.Client.GameObjects
|
||||
#if DEBUG
|
||||
var uid = GetEntity(netEntity);
|
||||
|
||||
if (TryComp<MetaDataComponent>(uid, out var meta))
|
||||
if (TryComp(uid, out MetaDataComponent? meta))
|
||||
{
|
||||
DebugTools.Assert((meta.Flags & ( MetaDataFlags.Detached | MetaDataFlags.InContainer) ) == MetaDataFlags.Detached,
|
||||
$"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container.");
|
||||
@@ -305,7 +303,7 @@ namespace Robust.Client.GameObjects
|
||||
while (parent.IsValid() && (!spriteOccluded || !lightOccluded))
|
||||
{
|
||||
var parentXform = TransformQuery.GetComponent(parent);
|
||||
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
|
||||
if (TryComp<ContainerManagerComponent>(parent, out var manager) && TryGetContainingContainer(parent, child, out var container, manager))
|
||||
{
|
||||
spriteOccluded = spriteOccluded || !container.ShowContents;
|
||||
lightOccluded = lightOccluded || container.OccludesLight;
|
||||
@@ -346,7 +344,7 @@ namespace Robust.Client.GameObjects
|
||||
var childLightOccluded = lightOccluded;
|
||||
|
||||
// We already know either sprite or light is not occluding so need to check container.
|
||||
if (manager.TryGetContainer(child, out var container))
|
||||
if (TryGetContainingContainer(entity, child, out var container, manager))
|
||||
{
|
||||
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
|
||||
childLightOccluded = childLightOccluded || container.OccludesLight;
|
||||
|
||||
@@ -2,24 +2,24 @@ using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class DebugEntityLookupCommand : LocalizedCommands
|
||||
public sealed class DebugEntityLookupCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly DebugEntityLookupSystem _system = default!;
|
||||
|
||||
public override string Command => "togglelookup";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugEntityLookupSystem>().Enabled ^= true;
|
||||
_system.Enabled ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@ namespace Robust.Client.GameObjects
|
||||
if (_enabled)
|
||||
{
|
||||
_lightOverlay = new DebugLightOverlay(
|
||||
EntitySystem.Get<EntityLookupSystem>(),
|
||||
EntityManager.System<EntityLookupSystem>(),
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
Get<LightTreeSystem>());
|
||||
EntityManager.System<LightTreeSystem>());
|
||||
|
||||
overlayManager.AddOverlay(_lightOverlay);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ public sealed class EyeSystem : SharedEyeSystem
|
||||
eyeComponent.Target = null;
|
||||
}
|
||||
|
||||
eyeComponent.Eye.Position = xform.MapPosition;
|
||||
eyeComponent.Eye.Position = TransformSystem.GetMapCoordinates(xform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public sealed class InputSystem : SharedInputSystem, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
|
||||
|
||||
@@ -9,30 +9,14 @@ namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
protected override MapId GetNextMapId()
|
||||
{
|
||||
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
|
||||
var id = new MapId(--LastMapId);
|
||||
while (MapManager.MapExists(id))
|
||||
while (MapExists(id) || UsedIds.Contains(id))
|
||||
{
|
||||
id = new MapId(--LastMapId);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class ShowPlayerVelocityDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
|
||||
internal bool Enabled
|
||||
{
|
||||
get => _label.Parent != null;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
_uiManager.WindowRoot.AddChild(_label);
|
||||
}
|
||||
else
|
||||
{
|
||||
_label.Orphan();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Label _label = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_label = new Label();
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
if (!Enabled)
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
|
||||
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
|
||||
_label.Visible = true;
|
||||
|
||||
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
|
||||
public static readonly ProtoId<ShaderPrototype> UnshadedId = "unshaded";
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
private Dictionary<EntityUid, Dictionary<Enum, Vector2>> _savedPositions = new();
|
||||
private Dictionary<BoundUserInterface, Control> _registeredControls = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -17,6 +25,53 @@ public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
ProtoManager.PrototypesReloaded -= OnProtoReload;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OpenUi(Entity<UserInterfaceComponent?> entity, Enum key, bool predicted = false)
|
||||
{
|
||||
var player = Player.LocalEntity;
|
||||
|
||||
if (player == null)
|
||||
return;
|
||||
|
||||
OpenUi(entity, key, player.Value, predicted);
|
||||
}
|
||||
|
||||
protected override void SavePosition(BoundUserInterface bui)
|
||||
{
|
||||
if (!_registeredControls.Remove(bui, out var control))
|
||||
return;
|
||||
|
||||
var keyed = _savedPositions[bui.Owner];
|
||||
keyed[bui.UiKey] = control.Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a control so it will later have its position stored by <see cref="SavePosition"/> when the BUI is closed.
|
||||
/// </summary>
|
||||
public void RegisterControl(BoundUserInterface bui, Control control)
|
||||
{
|
||||
DebugTools.Assert(!_registeredControls.ContainsKey(bui));
|
||||
_registeredControls[bui] = control;
|
||||
_savedPositions.GetOrNew(bui.Owner);
|
||||
}
|
||||
|
||||
public override bool TryGetPosition(Entity<UserInterfaceComponent?> entity, Enum key, out Vector2 position)
|
||||
{
|
||||
position = default;
|
||||
|
||||
if (!_savedPositions.TryGetValue(entity.Owner, out var keyed))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!keyed.TryGetValue(key, out position))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
var player = Player.LocalEntity;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class VelocityDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
internal bool Enabled { get; set; }
|
||||
|
||||
private Label _label = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_label = new Label();
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
if (!Enabled)
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
|
||||
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
|
||||
_label.Visible = true;
|
||||
|
||||
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class VisibilitySystem : SharedVisibilitySystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ using Robust.Client.Physics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -23,7 +24,6 @@ using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Profiling;
|
||||
@@ -42,13 +42,13 @@ namespace Robust.Client.GameStates
|
||||
private uint _nextInputCmdSeq = 1;
|
||||
private readonly Queue<FullInputCmdMessage> _pendingInputs = new();
|
||||
|
||||
private readonly Queue<(uint sequence, GameTick sourceTick, EntityEventArgs msg, object sessionMsg)>
|
||||
private readonly Queue<(uint sequence, GameTick sourceTick, object msg, object sessionMsg)>
|
||||
_pendingSystemMessages
|
||||
= new();
|
||||
|
||||
// Game state dictionaries that get used every tick.
|
||||
private readonly Dictionary<EntityUid, (NetEntity NetEntity, MetaDataComponent Meta, bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState? nextState)> _toApply = new();
|
||||
private readonly Dictionary<NetEntity, EntityState> _toCreate = new();
|
||||
private readonly Dictionary<EntityUid, StateData> _toApply = new();
|
||||
private StateData[] _toApplySorted = default!;
|
||||
private readonly Dictionary<ushort, (IComponent Component, IComponentState? curState, IComponentState? nextState)> _compStateWork = new();
|
||||
private readonly Dictionary<EntityUid, HashSet<Type>> _pendingReapplyNetStates = new();
|
||||
private readonly HashSet<NetEntity> _stateEnts = new();
|
||||
@@ -56,15 +56,29 @@ namespace Robust.Client.GameStates
|
||||
private readonly List<IComponent> _toRemove = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _outputData = new();
|
||||
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
|
||||
private readonly HashSet<EntityUid> _sorted = new();
|
||||
private readonly List<NetEntity> _created = new();
|
||||
private readonly List<NetEntity> _detached = new();
|
||||
|
||||
private readonly record struct StateData(
|
||||
EntityUid Uid,
|
||||
NetEntity NetEntity,
|
||||
MetaDataComponent Meta,
|
||||
bool Created,
|
||||
bool EnteringPvs,
|
||||
GameTick LastApplied,
|
||||
EntityState? CurState,
|
||||
EntityState? NextState,
|
||||
HashSet<Type>? PendingReapply);
|
||||
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState?>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState?>>(new DictPolicy<ushort, IComponentState?>(), 256);
|
||||
|
||||
private uint _metaCompNetId;
|
||||
private uint _xformCompNetId;
|
||||
|
||||
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
|
||||
[Dependency] private readonly IComponentFactory _compFactory = default!;
|
||||
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
|
||||
[Dependency] private readonly IPlayerManager _players = default!;
|
||||
[Dependency] private readonly IClientNetManager _network = default!;
|
||||
[Dependency] private readonly IBaseClient _client = default!;
|
||||
@@ -72,7 +86,7 @@ namespace Robust.Client.GameStates
|
||||
[Dependency] private readonly INetConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly ClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly ClientEntityManager _entities = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly ProfManager _prof = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
@@ -126,7 +140,6 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private bool _resettingPredictedEntities;
|
||||
private readonly List<EntityUid> _brokenEnts = new();
|
||||
private readonly List<(EntityUid, NetEntity)> _toStart = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
@@ -172,6 +185,12 @@ namespace Robust.Client.GameStates
|
||||
throw new InvalidOperationException("MetaDataComponent does not have a NetId.");
|
||||
|
||||
_metaCompNetId = metaId.Value;
|
||||
|
||||
var xformId = _compFactory.GetRegistration(typeof(TransformComponent)).NetID;
|
||||
if (!xformId.HasValue)
|
||||
throw new InvalidOperationException("TransformComponent does not have a NetId.");
|
||||
|
||||
_xformCompNetId = xformId.Value;
|
||||
}
|
||||
|
||||
private void OnComponentAdded(AddedComponentEventArgs args)
|
||||
@@ -183,11 +202,11 @@ namespace Robust.Client.GameStates
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
|
||||
if (_entityManager.IsClientSide(args.BaseArgs.Owner))
|
||||
if (_entities.IsClientSide(args.BaseArgs.Owner))
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} to entity {_entityManager.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Added component {comp.Name} to entity {_entities.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
@@ -366,7 +385,6 @@ namespace Robust.Client.GameStates
|
||||
_processor.UpdateFullRep(curState);
|
||||
}
|
||||
|
||||
IEnumerable<NetEntity> createdEntities;
|
||||
using (_prof.Group("ApplyGameState"))
|
||||
{
|
||||
if (_timing.LastProcessedTick < targetProcessedTick && nextState != null)
|
||||
@@ -385,7 +403,7 @@ namespace Robust.Client.GameStates
|
||||
try
|
||||
{
|
||||
#endif
|
||||
createdEntities = ApplyGameState(curState, nextState);
|
||||
ApplyGameState(curState, nextState);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (MissingMetadataException e)
|
||||
@@ -399,7 +417,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("MergeImplicitData"))
|
||||
{
|
||||
MergeImplicitData(createdEntities);
|
||||
MergeImplicitData();
|
||||
}
|
||||
|
||||
if (_lastProcessedInput < curState.LastProcessedInput)
|
||||
@@ -456,7 +474,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("Tick"))
|
||||
{
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
|
||||
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled, histogram: null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,9 +522,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
while (hasPendingMessage && pendingMessagesEnumerator.Current.sourceTick <= _timing.CurTick)
|
||||
{
|
||||
var msg = pendingMessagesEnumerator.Current.msg;
|
||||
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.msg);
|
||||
_entities.EventBus.RaiseEvent(EventSource.Local, pendingMessagesEnumerator.Current.sessionMsg);
|
||||
hasPendingMessage = pendingMessagesEnumerator.MoveNext();
|
||||
}
|
||||
@@ -545,9 +561,24 @@ namespace Robust.Client.GameStates
|
||||
PredictionNeedsResetting = false;
|
||||
var countReset = 0;
|
||||
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
|
||||
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
|
||||
var metaQuery = _entities.GetEntityQuery<MetaDataComponent>();
|
||||
RemQueue<IComponent> toRemove = new();
|
||||
|
||||
// Handle predicted entity spawns.
|
||||
var predicted = new ValueList<EntityUid>();
|
||||
var predictedQuery = _entities.AllEntityQueryEnumerator<PredictedSpawnComponent>();
|
||||
|
||||
while (predictedQuery.MoveNext(out var uid, out var _))
|
||||
{
|
||||
predicted.Add(uid);
|
||||
}
|
||||
|
||||
// Entity will get re-created as part of the tick.
|
||||
foreach (var ent in predicted)
|
||||
{
|
||||
_entities.DeleteEntity(ent);
|
||||
}
|
||||
|
||||
foreach (var entity in system.DirtyEntities)
|
||||
{
|
||||
DebugTools.Assert(toRemove.Count == 0);
|
||||
@@ -632,7 +663,7 @@ namespace Robust.Client.GameStates
|
||||
if (!last.TryGetValue(netId, out var state))
|
||||
continue;
|
||||
|
||||
var comp = _entityManager.AddComponent(entity, netId, meta);
|
||||
var comp = _entities.AddComponent(entity, netId, meta);
|
||||
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was removed: {comp.GetType()}");
|
||||
@@ -652,7 +683,7 @@ namespace Robust.Client.GameStates
|
||||
meta.EntityLastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
|
||||
_entityManager.System<PhysicsSystem>().ResetContacts();
|
||||
_entities.System<PhysicsSystem>().ResetContacts();
|
||||
|
||||
// TODO maybe reset more of physics?
|
||||
// E.g., warm impulses for warm starting?
|
||||
@@ -671,21 +702,22 @@ namespace Robust.Client.GameStates
|
||||
/// initial server state for any newly created entity. It does this by simply using the standard <see
|
||||
/// cref="IEntityManager.GetComponentState"/>.
|
||||
/// </remarks>
|
||||
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
|
||||
public void MergeImplicitData()
|
||||
{
|
||||
var bus = _entityManager.EventBus;
|
||||
var bus = _entities.EventBus;
|
||||
|
||||
foreach (var netEntity in createdEntities)
|
||||
foreach (var netEntity in _created)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta))
|
||||
if (!_entities.TryGetEntityData(netEntity, out _, out var meta))
|
||||
{
|
||||
_sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}");
|
||||
continue;
|
||||
}
|
||||
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw new KeyNotFoundException();
|
||||
#else
|
||||
var (_, meta) = _entityManager.GetEntityData(netEntity);
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
@@ -694,12 +726,13 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
var state = _entities.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
compData.Add(netId, state);
|
||||
}
|
||||
}
|
||||
|
||||
_created.Clear();
|
||||
_processor.MergeImplicitData(_outputData);
|
||||
|
||||
foreach (var data in _outputData.Values)
|
||||
@@ -735,10 +768,9 @@ namespace Robust.Client.GameStates
|
||||
_config.TickProcessMessages();
|
||||
}
|
||||
|
||||
(IEnumerable<NetEntity> Created, List<NetEntity> Detached) output;
|
||||
using (_prof.Group("Entity"))
|
||||
{
|
||||
output = ApplyEntityStates(curState, nextState);
|
||||
ApplyEntityStates(curState, nextState);
|
||||
}
|
||||
|
||||
using (_prof.Group("Player"))
|
||||
@@ -748,13 +780,13 @@ namespace Robust.Client.GameStates
|
||||
|
||||
using (_prof.Group("Callback"))
|
||||
{
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, output.Detached));
|
||||
GameStateApplied?.Invoke(new GameStateAppliedArgs(curState, _detached));
|
||||
}
|
||||
|
||||
return output.Created;
|
||||
return _created;
|
||||
}
|
||||
|
||||
private (IEnumerable<NetEntity> Created, List<NetEntity> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
|
||||
private void ApplyEntityStates(GameState curState, GameState? nextState)
|
||||
{
|
||||
var metas = _entities.GetEntityQuery<MetaDataComponent>();
|
||||
var xforms = _entities.GetEntityQuery<TransformComponent>();
|
||||
@@ -762,90 +794,74 @@ namespace Robust.Client.GameStates
|
||||
|
||||
var enteringPvs = 0;
|
||||
_toApply.Clear();
|
||||
_toCreate.Clear();
|
||||
_created.Clear();
|
||||
_pendingReapplyNetStates.Clear();
|
||||
var curSpan = curState.EntityStates.Span;
|
||||
|
||||
// Create new entities
|
||||
// This is done BEFORE state application to ensure any new parents exist before existing children have their states applied, otherwise, we may have issues with entity transforms!
|
||||
{
|
||||
using var _ = _prof.Group("Create uninitialized entities");
|
||||
var count = 0;
|
||||
|
||||
using (_prof.Group("Create uninitialized entities"))
|
||||
{
|
||||
var created = 0;
|
||||
foreach (var es in curSpan)
|
||||
{
|
||||
if (_entityManager.TryGetEntity(es.NetEntity, out var nUid))
|
||||
if (_entities.TryGetEntity(es.NetEntity, out var nUid))
|
||||
{
|
||||
DebugTools.Assert(_entityManager.EntityExists(nUid));
|
||||
DebugTools.Assert(_entities.EntityExists(nUid));
|
||||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
|
||||
if (metaState == null)
|
||||
throw new MissingMetadataException(es.NetEntity);
|
||||
|
||||
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
|
||||
_toCreate.Add(es.NetEntity, es);
|
||||
_toApply.Add(uid, (es.NetEntity, newMeta, false, GameTick.Zero, es, null));
|
||||
|
||||
// Client creates a client-side net entity for the newly created entity.
|
||||
// We need to clear this mapping before assigning the real net id.
|
||||
// TODO NetEntity Jank: prevent the client from creating this in the first place.
|
||||
_entityManager.ClearNetEntity(newMeta.NetEntity);
|
||||
|
||||
_entityManager.SetNetEntity(uid, es.NetEntity, newMeta);
|
||||
newMeta.LastStateApplied = curState.ToSequence;
|
||||
|
||||
// Check if there's any component states awaiting this entity.
|
||||
if (_entityManager.PendingNetEntityStates.Remove(es.NetEntity, out var value))
|
||||
{
|
||||
foreach (var (type, owner) in value)
|
||||
{
|
||||
var pending = _pendingReapplyNetStates.GetOrNew(owner);
|
||||
pending.Add(type);
|
||||
}
|
||||
}
|
||||
created++;
|
||||
CreateNewEntity(es, curState.ToSequence);
|
||||
}
|
||||
|
||||
_prof.WriteValue("Count", ProfData.Int32(count));
|
||||
_prof.WriteValue("Count", ProfData.Int32(created));
|
||||
}
|
||||
|
||||
// Add entity entities that aren't new to _toCreate.
|
||||
// In the process, we also check if these entities are re-entering PVS range.
|
||||
foreach (var es in curSpan)
|
||||
{
|
||||
if (_toCreate.ContainsKey(es.NetEntity))
|
||||
if (!_entities.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
continue;
|
||||
|
||||
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
continue;
|
||||
|
||||
bool isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
|
||||
var isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
|
||||
if (isEnteringPvs)
|
||||
{
|
||||
// _toApply already contains newly created entities, but these should never be "entering PVS"
|
||||
DebugTools.Assert(!_toApply.ContainsKey(uid.Value));
|
||||
|
||||
meta.Flags &= ~MetaDataFlags.Detached;
|
||||
enteringPvs++;
|
||||
}
|
||||
else if (meta.LastStateApplied >= es.EntityLastModified && meta.LastStateApplied != GameTick.Zero)
|
||||
{
|
||||
// _toApply already contains newly created entities, but for those this set should have no effect
|
||||
DebugTools.Assert(!_toApply.ContainsKey(uid.Value) || meta.LastStateApplied == curState.ToSequence);
|
||||
|
||||
meta.LastStateApplied = curState.ToSequence;
|
||||
continue;
|
||||
}
|
||||
|
||||
_toApply.Add(uid.Value, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
|
||||
// Any newly created entities already added to _toApply should've already been caught by the previous continue
|
||||
DebugTools.Assert(!_toApply.ContainsKey(uid.Value));
|
||||
|
||||
_toApply.Add(uid.Value, new(uid.Value, es.NetEntity, meta, false, isEnteringPvs, meta.LastStateApplied, es, null, null));
|
||||
meta.LastStateApplied = curState.ToSequence;
|
||||
}
|
||||
|
||||
// Detach entities to null space
|
||||
var containerSys = _entitySystemManager.GetEntitySystem<ContainerSystem>();
|
||||
var lookupSys = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
|
||||
var detached = ProcessPvsDeparture(curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
ProcessPvsDeparture(curState.ToSequence, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
|
||||
// Check next state (AFTER having created new entities introduced in curstate)
|
||||
if (nextState != null)
|
||||
{
|
||||
foreach (var es in nextState.EntityStates.Span)
|
||||
{
|
||||
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
if (!_entities.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
continue;
|
||||
|
||||
// Does the next state actually have any future information about this entity that could be used for interpolation?
|
||||
@@ -854,15 +870,14 @@ namespace Robust.Client.GameStates
|
||||
|
||||
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid.Value, out var exists);
|
||||
|
||||
if (exists)
|
||||
state = (es.NetEntity, meta, state.EnteringPvs, state.LastApplied, state.curState, es);
|
||||
else
|
||||
state = (es.NetEntity, meta, false, GameTick.Zero, null, es);
|
||||
state = exists
|
||||
? state with {NextState = es}
|
||||
: new(uid.Value, es.NetEntity, meta, false, false, GameTick.Zero, null, es, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Check pending states and see if we need to force any entities to re-run component states.
|
||||
foreach (var uid in _pendingReapplyNetStates.Keys)
|
||||
foreach (var (uid, pending) in _pendingReapplyNetStates)
|
||||
{
|
||||
// Original entity referencing the NetEntity may have been deleted.
|
||||
if (!metas.TryGetComponent(uid, out var meta))
|
||||
@@ -879,51 +894,30 @@ namespace Robust.Client.GameStates
|
||||
|
||||
DebugTools.Assert(!curState.EntityDeletions.Value.Contains(meta.NetEntity));
|
||||
|
||||
// State already being re-applied so don't bulldoze it.
|
||||
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid, out var exists);
|
||||
|
||||
if (exists)
|
||||
continue;
|
||||
|
||||
state = (meta.NetEntity, meta, false, GameTick.Zero, null, null);
|
||||
state = exists
|
||||
? state with {PendingReapply = pending}
|
||||
: new(uid, meta.NetEntity, meta, false, false, GameTick.Zero, null, null, pending);
|
||||
}
|
||||
|
||||
_queuedBroadphaseUpdates.Clear();
|
||||
|
||||
using (_prof.Group("Sort States"))
|
||||
{
|
||||
SortStates(_toApply);
|
||||
}
|
||||
|
||||
// Apply entity states.
|
||||
using (_prof.Group("Apply States"))
|
||||
{
|
||||
foreach (var (entity, data) in _toApply)
|
||||
var span = _toApplySorted.AsSpan(0, _toApply.Count);
|
||||
foreach (ref var data in span)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
|
||||
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(entity)}. Exception: {e}");
|
||||
_entityManager.DeleteEntity(entity);
|
||||
RequestFullState();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (!data.EnteringPvs)
|
||||
continue;
|
||||
|
||||
// Now that things like collision data, fixtures, and positions have been updated, we queue a
|
||||
// broadphase update. However, if this entity is parented to some other entity also re-entering PVS,
|
||||
// we only need to update it's parent (as it recursively updates children anyways).
|
||||
var xform = xforms.GetComponent(entity);
|
||||
DebugTools.Assert(xform.Broadphase == BroadphaseData.Invalid);
|
||||
xform.Broadphase = null;
|
||||
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
|
||||
_queuedBroadphaseUpdates.Add((entity, xform));
|
||||
ApplyEntState(data, curState.ToSequence);
|
||||
}
|
||||
|
||||
Array.Clear(_toApplySorted, 0, _toApply.Count);
|
||||
_prof.WriteValue("Count", ProfData.Int32(_toApply.Count));
|
||||
}
|
||||
|
||||
@@ -958,14 +952,168 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize and start the newly created entities.
|
||||
if (_toCreate.Count > 0)
|
||||
InitializeAndStart(_toCreate, metas, xforms);
|
||||
// Delete any entities that failed to properly initialize/start
|
||||
foreach (var entity in _brokenEnts)
|
||||
{
|
||||
_entities.DeleteEntity(entity);
|
||||
}
|
||||
|
||||
_brokenEnts.Clear();
|
||||
|
||||
_prof.WriteValue("State Size", ProfData.Int32(curSpan.Length));
|
||||
_prof.WriteValue("Entered PVS", ProfData.Int32(enteringPvs));
|
||||
}
|
||||
|
||||
return (_toCreate.Keys, detached);
|
||||
private void ApplyEntState(in StateData data, GameTick toTick)
|
||||
{
|
||||
try
|
||||
{
|
||||
HandleEntityState(data, _entities.EventBus, toTick);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(data.Uid)}. Exception: {e}");
|
||||
_brokenEnts.Add(data.Uid);
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (data.Created)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entities.InitializeEntity(data.Uid, data.Meta);
|
||||
_entities.StartEntity(data.Uid);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error(
|
||||
$"Caught exception while initializing or starting entity: {_entities.ToPrettyString(data.Uid)}. Exception: {e}");
|
||||
_brokenEnts.Add(data.Uid);
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.EnteringPvs)
|
||||
return;
|
||||
|
||||
// Now that things like collision data, fixtures, and positions have been updated, we queue a
|
||||
// broadphase update. However, if this entity is parented to some other entity also re-entering PVS,
|
||||
// we only need to update it's parent (as it recursively updates children anyways).
|
||||
var xform = _entities.TransformQuery.Comp(data.Uid);
|
||||
DebugTools.Assert(xform.Broadphase == BroadphaseData.Invalid);
|
||||
xform.Broadphase = null;
|
||||
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
|
||||
_queuedBroadphaseUpdates.Add((data.Uid, xform));
|
||||
}
|
||||
|
||||
private void CreateNewEntity(EntityState state, GameTick toTick)
|
||||
{
|
||||
// TODO GAME STATE
|
||||
// store MetaData & Transform information separately.
|
||||
var metaState =
|
||||
(MetaDataComponentState?) state.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId)
|
||||
.State;
|
||||
|
||||
if (metaState == null)
|
||||
throw new MissingMetadataException(state.NetEntity);
|
||||
|
||||
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
|
||||
_toApply.Add(uid, new(uid, state.NetEntity, newMeta, true, false, GameTick.Zero, state, null, null));
|
||||
_created.Add(state.NetEntity);
|
||||
|
||||
// Client creates a client-side net entity for the newly created entity.
|
||||
// We need to clear this mapping before assigning the real net id.
|
||||
// TODO NetEntity Jank: prevent the client from creating this in the first place.
|
||||
_entities.ClearNetEntity(newMeta.NetEntity);
|
||||
|
||||
_entities.SetNetEntity(uid, state.NetEntity, newMeta);
|
||||
newMeta.LastStateApplied = toTick;
|
||||
|
||||
// Check if there's any component states awaiting this entity.
|
||||
if (!_entities.PendingNetEntityStates.Remove(state.NetEntity, out var value))
|
||||
return;
|
||||
|
||||
foreach (var (type, owner) in value)
|
||||
{
|
||||
var pending = _pendingReapplyNetStates.GetOrNew(owner);
|
||||
pending.Add(type);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sort states to ensure that we always apply states, initialize, and start parent entities before any of their
|
||||
/// children.
|
||||
/// </summary>
|
||||
private void SortStates(Dictionary<EntityUid, StateData> toApply)
|
||||
{
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (_toApplySorted == null || _toApplySorted.Length < toApply.Count)
|
||||
Array.Resize(ref _toApplySorted, toApply.Count);
|
||||
|
||||
_sorted.Clear();
|
||||
|
||||
var i = 0;
|
||||
foreach (var (ent, data) in toApply)
|
||||
{
|
||||
AddToSorted(ent, data, ref i);
|
||||
}
|
||||
|
||||
DebugTools.AssertEqual(i, toApply.Count);
|
||||
}
|
||||
|
||||
private void AddToSorted(EntityUid ent, in StateData data, ref int i)
|
||||
{
|
||||
if (!_sorted.Add(ent))
|
||||
return;
|
||||
|
||||
EnsureParentsSorted(ent, data, ref i);
|
||||
_toApplySorted[i++] = data;
|
||||
}
|
||||
|
||||
private void EnsureParentsSorted(EntityUid ent, in StateData data, ref int i)
|
||||
{
|
||||
var parent = GetStateParent(ent, data);
|
||||
|
||||
while (parent != EntityUid.Invalid)
|
||||
{
|
||||
if (_toApply.TryGetValue(parent, out var parentData))
|
||||
{
|
||||
AddToSorted(parent, parentData, ref i);
|
||||
// The above method will handle the rest of the transform hierarchy, so we can just return early.
|
||||
return;
|
||||
}
|
||||
|
||||
parent = _entities.TransformQuery.GetComponent(parent).ParentUid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the entity's parent in the game state that is being applies. I.e., if the state contains a new
|
||||
/// transform state, get the parent from that. Otherwise, return the entity's current parent.
|
||||
/// </summary>
|
||||
private EntityUid GetStateParent(EntityUid uid, in StateData data)
|
||||
{
|
||||
// TODO GAME STATE
|
||||
// store MetaData & Transform information separately.
|
||||
if (data.CurState != null
|
||||
&& data.CurState.ComponentChanges.Value
|
||||
.TryFirstOrNull(c => c.NetID == _xformCompNetId, out var found))
|
||||
{
|
||||
var state = (TransformComponentState) found.Value.State!;
|
||||
return _entities.GetEntity(state.ParentID);
|
||||
}
|
||||
|
||||
return _entities.TransformQuery.GetComponent(uid).ParentUid;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1000,7 +1148,7 @@ namespace Robust.Client.GameStates
|
||||
_toDelete.Clear();
|
||||
|
||||
// Client side entities won't need the transform, but that should always be a tiny minority of entities
|
||||
var metaQuery = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
|
||||
var metaQuery = _entities.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
|
||||
|
||||
while (metaQuery.MoveNext(out var ent, out var metadata, out var xform))
|
||||
{
|
||||
@@ -1067,9 +1215,9 @@ namespace Robust.Client.GameStates
|
||||
foreach (var netEntity in delSpan)
|
||||
{
|
||||
// Don't worry about this for later.
|
||||
_entityManager.PendingNetEntityStates.Remove(netEntity);
|
||||
_entities.PendingNetEntityStates.Remove(netEntity);
|
||||
|
||||
if (!_entityManager.TryGetEntity(netEntity, out var id))
|
||||
if (!_entities.TryGetEntity(netEntity, out var id))
|
||||
continue;
|
||||
|
||||
if (!xforms.TryGetComponent(id, out var xform))
|
||||
@@ -1099,9 +1247,10 @@ namespace Robust.Client.GameStates
|
||||
var containerSys = _entitySystemManager.GetEntitySystem<ContainerSystem>();
|
||||
var lookupSys = _entitySystemManager.GetEntitySystem<EntityLookupSystem>();
|
||||
Detach(GameTick.MaxValue, null, entities, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
_detached.Clear();
|
||||
}
|
||||
|
||||
private List<NetEntity> ProcessPvsDeparture(
|
||||
private void ProcessPvsDeparture(
|
||||
GameTick toTick,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
@@ -1110,25 +1259,23 @@ namespace Robust.Client.GameStates
|
||||
EntityLookupSystem lookupSys)
|
||||
{
|
||||
var toDetach = _processor.GetEntitiesToDetach(toTick, _pvsDetachBudget);
|
||||
var detached = new List<NetEntity>();
|
||||
|
||||
if (toDetach.Count == 0)
|
||||
return detached;
|
||||
return;
|
||||
|
||||
// TODO optimize
|
||||
// If an entity is leaving PVS, so are all of its children. If we can preserve the hierarchy we can avoid
|
||||
// things like container insertion and ejection.
|
||||
|
||||
using var _ = _prof.Group("Leave PVS");
|
||||
detached.EnsureCapacity(toDetach.Count);
|
||||
|
||||
_detached.Clear();
|
||||
foreach (var (tick, ents) in toDetach)
|
||||
{
|
||||
Detach(tick, toTick, ents, metas, xforms, xformSys, containerSys, lookupSys, detached);
|
||||
Detach(tick, toTick, ents, metas, xforms, xformSys, containerSys, lookupSys);
|
||||
}
|
||||
|
||||
_prof.WriteValue("Count", ProfData.Int32(detached.Count));
|
||||
return detached;
|
||||
_prof.WriteValue("Count", ProfData.Int32(_detached.Count));
|
||||
}
|
||||
|
||||
private void Detach(GameTick maxTick,
|
||||
@@ -1138,12 +1285,11 @@ namespace Robust.Client.GameStates
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
SharedTransformSystem xformSys,
|
||||
ContainerSystem containerSys,
|
||||
EntityLookupSystem lookupSys,
|
||||
List<NetEntity>? detached = null)
|
||||
EntityLookupSystem lookupSys)
|
||||
{
|
||||
foreach (var netEntity in entities)
|
||||
{
|
||||
if (!_entityManager.TryGetEntityData(netEntity, out var ent, out var meta))
|
||||
if (!_entities.TryGetEntityData(netEntity, out var ent, out var meta))
|
||||
continue;
|
||||
|
||||
if (meta.LastStateApplied > maxTick)
|
||||
@@ -1184,159 +1330,75 @@ namespace Robust.Client.GameStates
|
||||
containerSys.AddExpectedEntity(netEntity, container);
|
||||
}
|
||||
|
||||
detached?.Add(netEntity);
|
||||
_detached.Add(netEntity);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeAndStart(
|
||||
Dictionary<NetEntity, EntityState> toCreate,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
_toStart.Clear();
|
||||
|
||||
using (_prof.Group("Initialize Entity"))
|
||||
{
|
||||
EntityUid entity = default;
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
{
|
||||
(entity, var meta) = _entityManager.GetEntityData(netEntity);
|
||||
InitializeRecursive(entity, meta, metas, xforms);
|
||||
}
|
||||
}
|
||||
|
||||
using (_prof.Group("Start Entity"))
|
||||
{
|
||||
foreach (var (entity, netEntity) in _toStart)
|
||||
{
|
||||
try
|
||||
{
|
||||
_entities.StartEntity(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var entity in _brokenEnts)
|
||||
{
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
_brokenEnts.Clear();
|
||||
}
|
||||
|
||||
private void InitializeRecursive(
|
||||
EntityUid entity,
|
||||
MetaDataComponent meta,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
EntityQuery<TransformComponent> xforms)
|
||||
{
|
||||
var xform = xforms.GetComponent(entity);
|
||||
if (xform.ParentUid is {Valid: true} parent)
|
||||
{
|
||||
var parentMeta = metas.GetComponent(parent);
|
||||
if (parentMeta.EntityLifeStage < EntityLifeStage.Initialized)
|
||||
InitializeRecursive(parent, parentMeta, metas, xforms);
|
||||
}
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Initialized)
|
||||
{
|
||||
// Was probably already initialized because one of its children appeared earlier in the list.
|
||||
DebugTools.AssertEqual(_toStart.Count(x => x.Item1 == entity), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_entities.InitializeEntity(entity, meta);
|
||||
_toStart.Add((entity, meta.NetEntity));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Init: nent={meta.NetEntity}, ent={_entities.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
_toCreate.Remove(meta.NetEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
|
||||
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
|
||||
private void HandleEntityState(in StateData data, IEventBus bus, GameTick toTick)
|
||||
{
|
||||
_compStateWork.Clear();
|
||||
|
||||
// First remove any deleted components
|
||||
if (curState?.NetComponents != null)
|
||||
if (data.CurState?.NetComponents is {} netComps)
|
||||
{
|
||||
_toRemove.Clear();
|
||||
|
||||
foreach (var (id, comp) in meta.NetComponents)
|
||||
foreach (var (id, comp) in data.Meta.NetComponents)
|
||||
{
|
||||
DebugTools.Assert(comp.NetSyncEnabled);
|
||||
|
||||
if (!curState.NetComponents.Contains(id))
|
||||
if (!netComps.Contains(id))
|
||||
_toRemove.Add(comp);
|
||||
}
|
||||
|
||||
foreach (var comp in _toRemove)
|
||||
{
|
||||
_entities.RemoveComponent(uid, comp, meta);
|
||||
_entities.RemoveComponent(data.Uid, comp, data.Meta);
|
||||
}
|
||||
}
|
||||
|
||||
if (enteringPvs)
|
||||
if (data.EnteringPvs)
|
||||
{
|
||||
// last-server state has already been updated with new information from curState
|
||||
// --> simply reset to the most recent server state.
|
||||
//
|
||||
// as to why we need to reset: because in the process of detaching to null-space, we will have dirtied
|
||||
// the entity. most notably, all entities will have been ejected from their containers.
|
||||
foreach (var (id, state) in _processor.GetLastServerStates(netEntity))
|
||||
foreach (var (id, state) in _processor.GetLastServerStates(data.NetEntity))
|
||||
{
|
||||
if (!meta.NetComponents.TryGetValue(id, out var comp))
|
||||
if (!data.Meta.NetComponents.TryGetValue(id, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(id);
|
||||
_entityManager.AddComponent(uid, comp, true, metadata: meta);
|
||||
_entities.AddComponent(data.Uid, comp, true, metadata: data.Meta);
|
||||
}
|
||||
|
||||
_compStateWork[id] = (comp, state, null);
|
||||
}
|
||||
}
|
||||
else if (curState != null)
|
||||
else if (data.CurState != null)
|
||||
{
|
||||
foreach (var compChange in curState.ComponentChanges.Span)
|
||||
foreach (var compChange in data.CurState.ComponentChanges.Span)
|
||||
{
|
||||
if (!meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
|
||||
if (!data.Meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(compChange.NetID);
|
||||
_entityManager.AddComponent(uid, comp, true, metadata:meta);
|
||||
_entities.AddComponent(data.Uid, comp, true, metadata: data.Meta);
|
||||
}
|
||||
else if (compChange.LastModifiedTick <= lastApplied && lastApplied != GameTick.Zero)
|
||||
else if (compChange.LastModifiedTick <= data.LastApplied && data.LastApplied != GameTick.Zero)
|
||||
continue;
|
||||
|
||||
_compStateWork[compChange.NetID] = (comp, compChange.State, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextState != null)
|
||||
if (data.NextState != null)
|
||||
{
|
||||
foreach (var compState in nextState.ComponentChanges.Span)
|
||||
foreach (var compState in data.NextState.ComponentChanges.Span)
|
||||
{
|
||||
if (compState.LastModifiedTick != toTick + 1)
|
||||
continue;
|
||||
|
||||
if (!meta.NetComponents.TryGetValue(compState.NetID, out var comp))
|
||||
if (!data.Meta.NetComponents.TryGetValue(compState.NetID, out var comp))
|
||||
{
|
||||
// The component can be null here due to interp, because the NEXT state will have a new
|
||||
// component, but the component does not yet exist.
|
||||
@@ -1354,9 +1416,10 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
// If we have a NetEntity we reference come in then apply their state.
|
||||
if (_pendingReapplyNetStates.TryGetValue(uid, out var reapplyTypes))
|
||||
DebugTools.Assert(_pendingReapplyNetStates.ContainsKey(data.Uid) == (data.PendingReapply != null));
|
||||
if (data.PendingReapply is {} reapplyTypes)
|
||||
{
|
||||
var lastState = _processor.GetLastServerStates(netEntity);
|
||||
var lastState = _processor.GetLastServerStates(data.NetEntity);
|
||||
|
||||
foreach (var type in reapplyTypes)
|
||||
{
|
||||
@@ -1366,7 +1429,7 @@ namespace Robust.Client.GameStates
|
||||
if (netId == null)
|
||||
continue;
|
||||
|
||||
if (!meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
|
||||
if (!data.Meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
|
||||
!lastState.TryGetValue(netId.Value, out var lastCompState))
|
||||
{
|
||||
continue;
|
||||
@@ -1388,7 +1451,7 @@ namespace Robust.Client.GameStates
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
bus.RaiseComponentEvent(data.Uid, comp, ref handleState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1510,7 +1573,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
using var _ = _timing.StartStateApplicationArea();
|
||||
|
||||
var query = _entityManager.AllEntityQueryEnumerator<MetaDataComponent>();
|
||||
var query = _entities.AllEntityQueryEnumerator<MetaDataComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var meta))
|
||||
{
|
||||
@@ -1536,14 +1599,14 @@ namespace Robust.Client.GameStates
|
||||
if (!meta.NetComponents.TryGetValue(id, out var comp))
|
||||
{
|
||||
comp = _compFactory.GetComponent(id);
|
||||
_entityManager.AddComponent(uid, comp, true, meta);
|
||||
_entities.AddComponent(uid, comp, true, meta);
|
||||
}
|
||||
|
||||
if (state == null)
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(state, null);
|
||||
_entityManager.EventBus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
_entities.EventBus.RaiseComponentEvent(uid, comp, ref handleState);
|
||||
}
|
||||
|
||||
// ensure we don't have any extra components
|
||||
|
||||
@@ -34,9 +34,6 @@ namespace Robust.Client.GameStates
|
||||
/// </summary>
|
||||
int GetApplicableStateCount();
|
||||
|
||||
[Obsolete("use GetApplicableStateCount()")]
|
||||
int CurrentBufferSize => GetApplicableStateCount();
|
||||
|
||||
/// <summary>
|
||||
/// Total number of game states currently in the state buffer.
|
||||
/// </summary>
|
||||
@@ -85,6 +82,8 @@ namespace Robust.Client.GameStates
|
||||
/// </summary>
|
||||
IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState);
|
||||
|
||||
void MergeImplicitData();
|
||||
|
||||
/// <summary>
|
||||
/// Resets any entities that have changed while predicting future ticks.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user