mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
314 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ef7ef139b | ||
|
|
facc16e931 | ||
|
|
11e4084652 | ||
|
|
f12eb3a36a | ||
|
|
2fa83181e2 | ||
|
|
36f02b4a18 | ||
|
|
e842142dd7 | ||
|
|
2eb740cea8 | ||
|
|
a044f04e3b | ||
|
|
a4723d1f62 | ||
|
|
627c1eb054 | ||
|
|
836aec0b87 | ||
|
|
9116e64291 | ||
|
|
a6bfb5f557 | ||
|
|
5c83678c78 | ||
|
|
eac94b1032 | ||
|
|
efd870d070 | ||
|
|
94f98073b0 | ||
|
|
5aa9378de0 | ||
|
|
850e9ab695 | ||
|
|
7319f3a241 | ||
|
|
b6252c9e4f | ||
|
|
fcd507d1f9 | ||
|
|
1eb874f4c3 | ||
|
|
a628d31c4b | ||
|
|
2b0ecd7166 | ||
|
|
bde650689b | ||
|
|
87d8d74d8c | ||
|
|
dddf13a19a | ||
|
|
75626a86a3 | ||
|
|
3e3cd0e257 | ||
|
|
a3a90154a4 | ||
|
|
9240c94e59 | ||
|
|
a95ba9f181 | ||
|
|
074a4faa92 | ||
|
|
6b4d74f46e | ||
|
|
f648218756 | ||
|
|
b497efb0c0 | ||
|
|
d8f2b917b4 | ||
|
|
6dd6b79db6 | ||
|
|
ff4548f108 | ||
|
|
8f41405b31 | ||
|
|
f285c62674 | ||
|
|
56c30edf04 | ||
|
|
783d529ec4 | ||
|
|
895bfb8ec0 | ||
|
|
6ef67cf513 | ||
|
|
15a2f6702c | ||
|
|
c5c2c2022a | ||
|
|
3c378640dd | ||
|
|
0a149fa91c | ||
|
|
fe8d1d9422 | ||
|
|
c89c529ba4 | ||
|
|
dd56de70b7 | ||
|
|
710408c613 | ||
|
|
2461cd94dd | ||
|
|
721408bb37 | ||
|
|
8b42c1dd46 | ||
|
|
688b0b0458 | ||
|
|
5fa49b5689 | ||
|
|
1f7a9bdf0a | ||
|
|
796abe1230 | ||
|
|
6c2cf26250 | ||
|
|
ce262d5ff8 | ||
|
|
c250010dad | ||
|
|
709c7bc808 | ||
|
|
37918da73c | ||
|
|
6cc2083b09 | ||
|
|
6a6bfe33ca | ||
|
|
9737a4249c | ||
|
|
a48a353939 | ||
|
|
f0b45d95cb | ||
|
|
d69c5500f2 | ||
|
|
cf133ca341 | ||
|
|
b0922b8e0e | ||
|
|
512ebd8422 | ||
|
|
85f74c3ba3 | ||
|
|
da7abc6580 | ||
|
|
b1329d30bf | ||
|
|
12808d073e | ||
|
|
ec794ce4e4 | ||
|
|
6b13475842 | ||
|
|
b48ee22800 | ||
|
|
0b95a4edeb | ||
|
|
ed359481b4 | ||
|
|
1189613908 | ||
|
|
30907d8415 | ||
|
|
7f2da4d4f3 | ||
|
|
e30e963623 | ||
|
|
b056caeed7 | ||
|
|
fbc8086335 | ||
|
|
799702b814 | ||
|
|
63df90f86f | ||
|
|
51f0c60bd3 | ||
|
|
a9ed53f47b | ||
|
|
41c40f1a94 | ||
|
|
6e61c35d35 | ||
|
|
aae0a8bc51 | ||
|
|
cb543240c6 | ||
|
|
1654ab06f5 | ||
|
|
211245215e | ||
|
|
10aaaa65c5 | ||
|
|
d2a2afe82e | ||
|
|
025d90d281 | ||
|
|
c229f2e312 | ||
|
|
fe051a3577 | ||
|
|
51a0ef1e60 | ||
|
|
702dfef5fc | ||
|
|
a0c1ad246f | ||
|
|
1153888bd1 | ||
|
|
ccbb6ddec7 | ||
|
|
970da5f717 | ||
|
|
4d528dd577 | ||
|
|
c83720b163 | ||
|
|
bd87a805d4 | ||
|
|
fff42fb2b4 | ||
|
|
4500669f65 | ||
|
|
7d19ea9338 | ||
|
|
2dc610907d | ||
|
|
beb1c4b1fb | ||
|
|
7e331eaa75 | ||
|
|
caf9e45ad9 | ||
|
|
7cb3aeccc2 | ||
|
|
ae83e606d6 | ||
|
|
d9d5ef7471 | ||
|
|
0f97f366a6 | ||
|
|
35ab0b8cc8 | ||
|
|
5a14e939bf | ||
|
|
ccba6b5d1c | ||
|
|
254a5987c7 | ||
|
|
8550056e68 | ||
|
|
25211e3781 | ||
|
|
3500abfd47 | ||
|
|
7d1915096a | ||
|
|
4504731588 | ||
|
|
701fa95a82 | ||
|
|
40a9048704 | ||
|
|
cee8d42776 | ||
|
|
3330d96177 | ||
|
|
4033d96327 | ||
|
|
6e0205d1a8 | ||
|
|
7cd95351c3 | ||
|
|
2a102f048f | ||
|
|
16bab1bc03 | ||
|
|
123d0ae6ac | ||
|
|
d72de032fa | ||
|
|
0fdba836ee | ||
|
|
eb63809999 | ||
|
|
4c3c74865c | ||
|
|
b624f5b70f | ||
|
|
6566a7658a | ||
|
|
9e3e1cc929 | ||
|
|
4e87d93009 | ||
|
|
1031ae4cc5 | ||
|
|
73da147b88 | ||
|
|
0ab59d70b1 | ||
|
|
8e8470ac7e | ||
|
|
15f94bd094 | ||
|
|
68888c4370 | ||
|
|
19f87dfbb3 | ||
|
|
68e5b6924d | ||
|
|
9f913cd2d9 | ||
|
|
ec37d1c137 | ||
|
|
ea58924495 | ||
|
|
c5aa735506 | ||
|
|
f5a6e52c7f | ||
|
|
d5c4981648 | ||
|
|
8c6170661d | ||
|
|
1901059755 | ||
|
|
8cdec92be6 | ||
|
|
0a00e7ec29 | ||
|
|
8c25a83066 | ||
|
|
0fadfc2d9b | ||
|
|
a6e7224672 | ||
|
|
37796f4806 | ||
|
|
a5494d1df2 | ||
|
|
e2525a2103 | ||
|
|
b50f68866f | ||
|
|
03a4d3e0a0 | ||
|
|
af8fb52a4f | ||
|
|
fd60dc2887 | ||
|
|
cd24fd46b6 | ||
|
|
44cc7127fa | ||
|
|
af36d24892 | ||
|
|
caa8ff0f2d | ||
|
|
cd67c67a5c | ||
|
|
57b328e8c2 | ||
|
|
4874b1db68 | ||
|
|
814ad08884 | ||
|
|
8c4deb2067 | ||
|
|
f6a5120e56 | ||
|
|
c1b8bf8e52 | ||
|
|
4b193bad26 | ||
|
|
8db3da4852 | ||
|
|
0c271fc2f8 | ||
|
|
ed406c06b7 | ||
|
|
b31940b489 | ||
|
|
84360c653d | ||
|
|
6764ed56b0 | ||
|
|
2b55d39e51 | ||
|
|
fdc1de2430 | ||
|
|
99c5b0ad08 | ||
|
|
9e2ab2a917 | ||
|
|
36eb857b55 | ||
|
|
92d7f2723a | ||
|
|
91d3f67a94 | ||
|
|
b28b5ed09b | ||
|
|
30eed7957f | ||
|
|
73c1449811 | ||
|
|
958b5dd06d | ||
|
|
4002cbddb9 | ||
|
|
c38a14e78f | ||
|
|
5164d99996 | ||
|
|
c79217ab66 | ||
|
|
9ef7f7cb37 | ||
|
|
02ac314b1a | ||
|
|
8607ba1f16 | ||
|
|
2a9de462d5 | ||
|
|
c59ef5ab2d | ||
|
|
eba1d866fb | ||
|
|
ab2bff8f40 | ||
|
|
536fca4115 | ||
|
|
7a0d02463c | ||
|
|
6df53d60ed | ||
|
|
ff38e9f12a | ||
|
|
00c58c76a8 | ||
|
|
0bf99e173c | ||
|
|
eee771c5f1 | ||
|
|
2946cd866c | ||
|
|
9a2a3d658d | ||
|
|
d933f03a54 | ||
|
|
25bbb21dc8 | ||
|
|
4460454563 | ||
|
|
a2d8fa7a9b | ||
|
|
71f0491f10 | ||
|
|
b4c1618338 | ||
|
|
df0945f3cd | ||
|
|
8d477716b0 | ||
|
|
5c1a5e9826 | ||
|
|
6daa3ad2fc | ||
|
|
033a617102 | ||
|
|
b9b565d53e | ||
|
|
b7ea4d0cca | ||
|
|
919ec01477 | ||
|
|
e484eac29c | ||
|
|
eedadb250f | ||
|
|
3097784cd7 | ||
|
|
0fb41e06c8 | ||
|
|
0a79382a62 | ||
|
|
1f2b38a6d1 | ||
|
|
f760929527 | ||
|
|
f5ade69f6d | ||
|
|
9bfe889c86 | ||
|
|
e3954494e7 | ||
|
|
6697e36e84 | ||
|
|
dae4041e61 | ||
|
|
390f399750 | ||
|
|
28cf7442ce | ||
|
|
d8b03be651 | ||
|
|
16c7c71ca6 | ||
|
|
0245c371ae | ||
|
|
c8cb13f832 | ||
|
|
86ecfaa56b | ||
|
|
43a32e7015 | ||
|
|
83371885fa | ||
|
|
e686e1b4cc | ||
|
|
b1f9d011ce | ||
|
|
a2d0504368 | ||
|
|
7aa951ca48 | ||
|
|
75a80b7a8a | ||
|
|
69706b0257 | ||
|
|
10b191dff8 | ||
|
|
92ab3fb64b | ||
|
|
92a0c14383 | ||
|
|
5aaf6d0994 | ||
|
|
15f4da5e4b | ||
|
|
a528e87f3d | ||
|
|
4af67b1394 | ||
|
|
e8de9b98d3 | ||
|
|
a0ffeff4e5 | ||
|
|
07654564f3 | ||
|
|
7fbf8d05eb | ||
|
|
c12971cb9b | ||
|
|
2b6381c332 | ||
|
|
8149a3aaad | ||
|
|
4b39bf1f2d | ||
|
|
53394fff44 | ||
|
|
4bed20e070 | ||
|
|
e4b6af09f1 | ||
|
|
1ef29ae781 | ||
|
|
5686950421 | ||
|
|
2b54aa8984 | ||
|
|
859f150404 | ||
|
|
558f4b5b16 | ||
|
|
108366152b | ||
|
|
c55327e1d1 | ||
|
|
370e0fa0d0 | ||
|
|
4f9f82c20c | ||
|
|
43670a8ddd | ||
|
|
250313e1ed | ||
|
|
18d511d4b6 | ||
|
|
da9e5fb370 | ||
|
|
e3bac382ce | ||
|
|
179c6790b6 | ||
|
|
a7db5634df | ||
|
|
2daa86ff59 | ||
|
|
d6803f5294 | ||
|
|
bdcc0f7b9d | ||
|
|
ce49aa47cf | ||
|
|
c7d48b2526 | ||
|
|
6bb7f5b4ef | ||
|
|
2974310450 | ||
|
|
2694dce076 | ||
|
|
8960d1d995 |
@@ -7,6 +7,18 @@ indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
max_line_length = 120
|
||||
|
||||
# ReSharper properties
|
||||
resharper_csharp_max_line_length = 120
|
||||
resharper_csharp_wrap_after_declaration_lpar = true
|
||||
resharper_csharp_wrap_arguments_style = chop_if_long
|
||||
resharper_csharp_wrap_parameters_style = chop_if_long
|
||||
resharper_keep_existing_attribute_arrangement = true
|
||||
resharper_place_field_attribute_on_same_line = if_owner_is_single_line
|
||||
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||
resharper_wrap_chained_method_calls = chop_if_long
|
||||
|
||||
[*.{csproj,xml,yml,dll.config,targets,props}]
|
||||
indent_size = 2
|
||||
|
||||
|
||||
7
.github/workflows/build-test.yml
vendored
7
.github/workflows/build-test.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest ] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -27,7 +27,8 @@ jobs:
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
|
||||
- name: Test Engine
|
||||
- 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
|
||||
|
||||
|
||||
6
.github/workflows/publish-client.yml
vendored
6
.github/workflows/publish-client.yml
vendored
@@ -33,10 +33,10 @@ jobs:
|
||||
mkdir "release/${{ steps.parse_version.outputs.version }}"
|
||||
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
|
||||
|
||||
- name: Upload files to centcomm
|
||||
- name: Upload files to Suns
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
source: "release/${{ steps.parse_version.outputs.version }}"
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Update manifest JSON
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
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 }}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
We actually set ManagePackageVersionsCentrally manually in another import file.
|
||||
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
|
||||
https://github.com/NuGet/NuGet.Client/pull/5572
|
||||
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
|
||||
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
|
||||
-->
|
||||
<ManagePackageVersionsCentrally />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<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="Linguini.Bundle" Version="0.1.3" />
|
||||
<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.Analyzer.Testing" Version="1.1.1" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.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" />
|
||||
@@ -45,7 +55,7 @@
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<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.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 45f89ca263...1d85b82e05
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -24,12 +24,16 @@
|
||||
<RobustInjectorsConfiguration>$(Configuration)</RobustInjectorsConfiguration>
|
||||
<RobustInjectorsConfiguration Condition="'$(Configuration)' == 'DebugOpt'">Debug</RobustInjectorsConfiguration>
|
||||
<RobustInjectorsConfiguration Condition="'$(Configuration)' == 'Tools'">Release</RobustInjectorsConfiguration>
|
||||
<RobustInjectorsConfiguration Condition="'$(UseArtifactsOutput)' == 'true' And '$(RuntimeIdentifier)' != ''">$(RobustInjectorsConfiguration)_$(RuntimeIdentifier)</RobustInjectorsConfiguration>
|
||||
<RobustInjectorsConfiguration Condition="'$(UseArtifactsOutput)' == 'true'">$(RobustInjectorsConfiguration.ToLower())</RobustInjectorsConfiguration>
|
||||
<CompileRobustXamlTaskAssemblyFile Condition="'$(UseArtifactsOutput)' != 'true'">$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(RobustInjectorsConfiguration)\netstandard2.0\Robust.Client.Injectors.dll</CompileRobustXamlTaskAssemblyFile>
|
||||
<CompileRobustXamlTaskAssemblyFile Condition="'$(UseArtifactsOutput)' == 'true'">$(MSBuildThisFileDirectory)\..\..\artifacts\bin\Robust.Client.Injectors\$(RobustInjectorsConfiguration)\Robust.Client.Injectors.dll</CompileRobustXamlTaskAssemblyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<UsingTask
|
||||
Condition="'$(_RobustUseExternalMSBuild)' != 'true' And $(DesignTimeBuild) != true"
|
||||
TaskName="CompileRobustXamlTask"
|
||||
AssemblyFile="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(RobustInjectorsConfiguration)\netstandard2.0\Robust.Client.Injectors.dll"/>
|
||||
AssemblyFile="$(CompileRobustXamlTaskAssemblyFile)"/>
|
||||
<Target
|
||||
Name="CompileRobustXaml"
|
||||
Condition="Exists('@(IntermediateAssembly)')"
|
||||
|
||||
742
RELEASE-NOTES.md
742
RELEASE-NOTES.md
@@ -1,4 +1,4 @@
|
||||
# Release notes for RobustToolbox.
|
||||
# Release notes for RobustToolbox.
|
||||
|
||||
<!--
|
||||
NOTE: automatically updated sometimes by version.py.
|
||||
@@ -54,6 +54,746 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 225.0.2
|
||||
|
||||
|
||||
## 225.0.1
|
||||
|
||||
|
||||
## 225.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `NetEntity.Parse` and `TryParse` will now fail to parse empty strings.
|
||||
* Try to prevent EventBus looping. This also caps the amount of directed component subscriptions for a particular component to 256.
|
||||
|
||||
### New features
|
||||
|
||||
* `IPrototypeManager.TryIndex` will now default to logging errors if passed an invalid prototype id struct (i,e., `EntProtoId` or `ProtoId<T>`). There is a new optional bool argument to disable logging errors.
|
||||
* `Eye` now allows its `Position` to be set directly. Please only do this with the `FixedEye` child type constructed manually.
|
||||
* Engine now respects the hub's `can_skip_build` parameter on info query, fixing an issue where the first hub advertisement fails due to ACZ taking too long.
|
||||
* Add GetSession & TryGetSession to ActorSystem.
|
||||
* Raise an event when an entity's name is changed.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* The `ent` toolshed command now takes `NetEntity` values, fixing parsing in practical uses.
|
||||
* Fix ComponentFactory test mocks.
|
||||
* Fix LookupFlags missing from a couple of EntityLookupSystem methods.
|
||||
|
||||
### Other
|
||||
|
||||
* Improved engine's Happy Eyeballs implementation, should result in more usage of IPv6 for HTTP APIs when available.
|
||||
* Remove CompIdx locks to improve performance inside Pvs at higher player counts.
|
||||
* Avoid a read lock in GetEntityQuery to also improve performance.
|
||||
* Mark `EntityManager.System<T>` as Pure.
|
||||
|
||||
|
||||
## 224.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed UserInterfaceSystem sometimes throwing a key-not-found exception when trying to close UIs.
|
||||
|
||||
|
||||
## 224.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `ServerIntegrationInstance` has new methods for adding dummy player sessions for tests that require multiple players.
|
||||
* Linguini has been updated to v0.8.1. Errors will now be logged when a duplicate localization key is found.
|
||||
* Added `UserInterfaceSystem.SetUi()` for modifying the `InterfaceData` associated with some BUI.
|
||||
* Added the `EntityPrototypeView` control for spawning & rendering an entity prototype.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `UserInterfaceSystem` spamming client side errors when entities with UIs open are deleted while outside of PVS range.
|
||||
* Fix Toolshed's EnumTypeParse not working enum values with upercase characters.
|
||||
* Fixed `incmd` command not working due to an invalid cast.
|
||||
|
||||
### Other
|
||||
|
||||
* There have been various performance improvements to replay loading & playback.
|
||||
|
||||
### Internal
|
||||
|
||||
* Added `DummySession` and `DummyChannel` classes for use in integration tests and benchmarks to fool the server into thinking that there are multiple players connected.
|
||||
* Added `ICommonSessionInternal` and updated `CommonSession` so that the internal setters now go through that interface.
|
||||
|
||||
## 224.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes PVS throwing exceptions when invalid entities are passed to `ExpandPvsEvent`. Now it just logs an error.
|
||||
* Fixes BUIs not properly closing, resulting in invalid entities in `UserInterfaceUserComponent.OpenInterfaces`
|
||||
* Fixes an unknown/invalid prototype exception sometimes being thrown when running ``IPrototypeManager.ResolveResults()`
|
||||
|
||||
|
||||
## 224.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `Matrix3` has been replaced with `System.Numerics.Matrix3x2`. Various Matrix related methods have been turned into extension methods in the `Matrix3Helpers` class.
|
||||
* Engine `EntityCategory` prototype IDs have been changed to use CamelCase. I.e., `hideSpawnMenu` -> `HideSpawnMenu`
|
||||
* Prototypes can now be implicitly cast `ProtoId<T>` or `EntProtoId` ID structs. The new implicit cast might cause previous function calls to be ambiguous.
|
||||
|
||||
### New features
|
||||
|
||||
* `Array.Clear(Array)` is now available in the sandbox.
|
||||
* BUIs now use `ExpandPvsEvent`. I.e., if a player has a UI open, then the entity associated with that UI will always get sent to the player by the PVS system.
|
||||
* Added `cvar_subs` command for listing all subscribers to cvar changes
|
||||
* Entity categories have been reworked
|
||||
* Each category now has a `HideSpawnMenu` field. The old `HideSpawnMenu` category is now just a normal category with that field set to true.
|
||||
* Reworked category inheritance. Inheritance can now be disabled per category using a `Inheritable` field.
|
||||
* Entity prototypes can now be automatically added to categories based on the components that they have, either by specifying components when defining the category in yml, or by adding the EntityCategoryAttribute to the component class.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed client-side BUI error log spam if an unknown entity has a UI open.
|
||||
* Fixed placement manager spawning entities with incorrect rotations.
|
||||
|
||||
### Other
|
||||
|
||||
* Added a try-catch block to BUI constructors, to avoid clients getting stuck in error loops while applying states.
|
||||
* Attempting to play sounds on terminating entities no longer logs an error.
|
||||
|
||||
|
||||
## 223.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Better exception logging for IRobustJob.
|
||||
* Add SetGridAudio helper for SharedAudioSystem.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix placement manager not setting entity rotation correctly.
|
||||
* Fix grid-based audio not playing correctly.
|
||||
|
||||
|
||||
## 223.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added several new `FormattedMessage` methods for better exception tolerance when parsing markup. Several existing methods have been marked as obsolete, with new renamed methods taking their place.
|
||||
|
||||
|
||||
## 223.1.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `MapGridComponent.LastTileModifiedTick` is now actually networked to clients.
|
||||
|
||||
|
||||
## 223.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an exception caused by enum cvars using integer type values instead of enum values
|
||||
|
||||
|
||||
## 223.1.0
|
||||
|
||||
### Other
|
||||
|
||||
* Various `ContainerSystem` methods have been obsoleted in favour of overrides that take in an `Entity` struct instead of `EntityUid`
|
||||
* Various `EntityCoordinates` methods have been obsoleted with replacements added to `SharedTransformSystem`
|
||||
|
||||
|
||||
## 223.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `ComponentState` class is now abstract. Networked components that don't have state information now just return a null state.
|
||||
* The way that delta component states work has changed. It now expects there to be two different state classes, only one of which should implement `IComponentDeltaState<TFullState>`
|
||||
|
||||
### New features
|
||||
|
||||
* A new `replay.checkpoint_min_interval` cvar has been added. It can be used to limit the frequency at which checkpoints are generated when loading a replay.
|
||||
* Added `IConfigurationManager.OnCVarValueChanged`. This is a c# event that gets invoked whenever any cvar value changes.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `IEyeManager.GetWorldViewbounds()` and `IEyeManager.GetWorldViewbounds()` should now return the correct bounds if the main viewport does not take up the whole screen.
|
||||
|
||||
### Other
|
||||
|
||||
* The default values of various replay related cvars have been changed to try and reduce memory usage.
|
||||
|
||||
|
||||
## 222.4.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added the following types from `System.Numerics` to the sandbox: `Complex`, `Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector3`, `Vector4`.
|
||||
|
||||
|
||||
## 222.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* `ITileDefinition.EditorHidden` allows hiding a tile from the tile spawn panel.
|
||||
* Ordered event subscriptions now take child types into account, so ordering based on a shared type will work.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Cross-map BUI range checks now work.
|
||||
* Paused entities update on prototype reload.
|
||||
|
||||
### Other
|
||||
|
||||
* Fixed build compatibility with .NET 8.0.300 SDK, due to changes in how Central Package Management behaves.
|
||||
* Physics component has delta states to reduce network usage.
|
||||
|
||||
|
||||
## 222.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `EntityQuery.Comp()` (abbreviation of `GetComponent()`)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `SerializationManager.TryGetVariableType` checking the wrong property.
|
||||
* Fixed GrammarSystem mispredicting a character's gender
|
||||
|
||||
### Other
|
||||
|
||||
* User interface system now performs range checks in parallel
|
||||
|
||||
|
||||
## 222.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed never setting BoundUserInterface.State.
|
||||
|
||||
### Other
|
||||
|
||||
* Add truncate for filesaving.
|
||||
* Add method for getting the type of a data field by name from ISerializationManager.
|
||||
|
||||
|
||||
## 222.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `BoundKeyEventArgs.IsRepeat`.
|
||||
* Added `net.lidgren_log_warning` and `net.lidgren_log_error` CVars.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix assert trip when holding repeatable keybinds.
|
||||
|
||||
### Other
|
||||
|
||||
* Updated Lidgren to v0.3.1. This should provide performance improvements if warning/error logs are disabled.
|
||||
|
||||
|
||||
## 222.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Mark IComponentFactory argument in EntityPrototype as mandatory.
|
||||
|
||||
### New features
|
||||
|
||||
* Add `EntProtoId<T>` to check for components on the attached entity as well.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix PVS iterating duplicate chunks for multiple viewsubscriptions.
|
||||
|
||||
### Other
|
||||
|
||||
* Defer clientside BUI opens if it's the first state that comes in.
|
||||
|
||||
|
||||
## 221.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add SetMapAudio helper to SharedAudioSystem to setup map-wide audio entities.
|
||||
* Add SetWorldRotNoLerp method to SharedTransformSystem to avoid client lerping.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `SpriteComponent.CopyFrom` now copies `CopyToShaderParameters` configuration.
|
||||
|
||||
|
||||
## 221.1.0
|
||||
|
||||
|
||||
## 221.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `EntParentChangedMessage.OldMapId` is now an `EntityUid` instead of `MapId`
|
||||
* `TransformSystem.DetachParentToNull()` is being renamed to `DetachEntity`
|
||||
* The order in which `MoveEvent` handlers are invoked has been changed to prioritise engine subscriptions
|
||||
|
||||
### New features
|
||||
|
||||
* Added `UpdateHovered()` and `SetHovered()` to `IUserInterfaceManager`, for updating or modifying the currently hovered control.
|
||||
* Add SwapPositions to TransformSystem to swap two entity's transforms.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Improve client gamestate exception tolerance.
|
||||
|
||||
### Other
|
||||
|
||||
* If the currently hovered control is disposed, `UserInterfaceManager` will now look for a new control, rather than just setting the hovered control to null.
|
||||
|
||||
### Internal
|
||||
|
||||
* Use more `EntityQuery<T>` internally in EntityManager and PhysicsSystem.
|
||||
|
||||
|
||||
## 220.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* RSIs can now specify load parameters, mimicking the ones from `.png.yml`. Currently only disabling sRGB is supported.
|
||||
* Added a second UV channel to Clyde's vertex format. On regular batched sprite draws, this goes 0 -> 1 across the sprite quad.
|
||||
* Added a new `CopyToShaderParameters` system for `SpriteComponent` layers.
|
||||
|
||||
|
||||
## 220.1.0
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix client-side replay exceptions due to dropped states when recording.
|
||||
|
||||
### Other
|
||||
|
||||
* Remove IP + HWId from ViewVariables.
|
||||
* Close BUIs upon disconnect.
|
||||
|
||||
|
||||
## 220.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Refactor UserInterfaceSystem.
|
||||
- The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate.
|
||||
- Interface data is now stored via key rather than as a flat list which is a breaking change for YAML.
|
||||
- BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before.
|
||||
- BoundUserInterfaces now properly close in many more situations, additionally they are now attached to the entity so reconnecting can re-open them and they can be serialized properly.
|
||||
|
||||
|
||||
## 219.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add SetMapCoordinates to TransformSystem.
|
||||
* Improve YAML Linter and validation of static fields.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix DebugCoordsPanel freezing when hovering a control.
|
||||
|
||||
### Other
|
||||
|
||||
* Optimise physics networking to not dirty every tick of movement.
|
||||
|
||||
|
||||
## 219.1.3
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix map-loader not pausing pre-init maps when not actively overwriting an existing map.
|
||||
|
||||
|
||||
## 219.1.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix map-loader not map-initialising grids when loading into a post-init map.
|
||||
|
||||
|
||||
## 219.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix map-loader not map-initialising maps when overwriting a post-init map.
|
||||
|
||||
|
||||
## 219.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added a new optional arguments to various entity spawning methods, including a new argument to set the entity's rotation.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixes map initialisation not always initialising all entities on a map.
|
||||
|
||||
### Other
|
||||
|
||||
* The default value of the `auth.mode` cvar has changed
|
||||
|
||||
|
||||
## 219.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Move most IMapManager functionality to SharedMapSystem.
|
||||
|
||||
|
||||
## 218.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Control layout properties such as `Margin` can now be set via style sheets.
|
||||
* Expose worldposition in SpriteComponent.Render
|
||||
* Network audio entity Play/Pause/Stop states and playback position.
|
||||
* Add `Disabled` functionality to `Slider` control.
|
||||
|
||||
|
||||
## 218.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add IEquatable.Equals to the sandbox.
|
||||
* Enable roslyn extensions tests in CI.
|
||||
* Add a VerticalTabContainer control to match the horizontal one.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix divison remainder issue for Colors, fixing purples.
|
||||
|
||||
### Other
|
||||
|
||||
* Default hub address (`hub.hub_urls`) has been changed to `https://hub.spacestation14.com/`.
|
||||
|
||||
|
||||
## 218.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `Robust.Shared.Configuration.EnvironmentVariables` is now internal and no longer usable by content.
|
||||
|
||||
### New features
|
||||
|
||||
* Add TryGetRandom to EntityManager to get a random entity with the specified component and TryGetRandom to IPrototypeManager to return a random prototype of the specified type.
|
||||
* Add CopyData to AppearanceSystem.
|
||||
* Update UI themes on prototype reloads.
|
||||
* Allow scaling the line height of a RichTextLabel.
|
||||
* You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".".
|
||||
* Added non-generic variant of `GetCVar` to `IConfigurationManager`.
|
||||
* Add type tracking to FieldNotFoundErrorNode for serialization.
|
||||
* Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`.
|
||||
* UI theme prototypes are now updated when reloaded.
|
||||
* New `RA0025` analyzer diagnostic warns for manual assignment to `[Dependency]` fields.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Request headers in `IStatusHandlerContext` are now case-insensitive.
|
||||
* SetWorldPosition rotation now more closely aligns with prior behavior.
|
||||
* Fix exception when inspecting elements in some cases.
|
||||
* Fix HTTP errors on watchdog ping not being reported.
|
||||
|
||||
### Other
|
||||
|
||||
* Add an analyzer for redundantly assigning to dependency fields.
|
||||
|
||||
### Internal
|
||||
|
||||
* Remove redundant Exists checks in ContainerSystem.
|
||||
* Improve logging on watchdog pings.
|
||||
|
||||
|
||||
## 217.2.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix LineEdit tests on engine.
|
||||
|
||||
### Internal
|
||||
|
||||
* Make various ValueList enumerators access the span directly for performance.
|
||||
|
||||
|
||||
## 217.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes.
|
||||
* Add double-clicking to LineEdit.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Properly ignore non-hard fixtures for IntersectRayWithPredicate.
|
||||
* Fix nullable TimeSpan addition on some platforms.
|
||||
|
||||
|
||||
## 217.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections.
|
||||
* Added `SharedPhysicsSystem.EffectiveCurTime`. This is effectively a variation of `IGameTiming.CurTime` that takes into account the current physics sub-step.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `MapComponent.LightingEnabled` not leaving FOV rendering in a broken state.
|
||||
|
||||
### Internal
|
||||
|
||||
* `Shuffle<T>(Span<T>, System.Random)` has been removed, just use the builtin method.
|
||||
|
||||
|
||||
## 217.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* TransformSystem.SetWorldPosition and SetWorldPositionRotation will now also perform parent updates as necessary. Previously it would just set the entity's LocalPosition which may break if they were inside of a container. Now they will be removed from their container and TryFindGridAt will run to correctly parent them to the new position. If the old functionality is desired then you can use GetInvWorldMatrix to update the LocalPosition (bearing in mind containers may prevent this).
|
||||
|
||||
### New features
|
||||
|
||||
* Implement VV for AudioParams on SoundSpecifiers.
|
||||
* Add AddUi to the shared UI system.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix the first measure of ScrollContainer bars.
|
||||
|
||||
|
||||
## 216.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `net.low_lod_distance` cvar has been replaced with a new `net.pvs_priority_range`. Instead of limiting the range at which all entities are sent to a player, it now extends the range at which high priorities can be sent. The default value of this new cvar is 32.5, which is larger than the default `net.pvs_range` value of 25.
|
||||
|
||||
### New features
|
||||
|
||||
* You can now specify a component to not be saved to map files with `[UnsavedComponent]`.
|
||||
* Added `ITileDefinitionManager.TryGetDefinition`.
|
||||
* The map loader now tries to preserve the `tilemap` contents of map files, which should reduce diffs when re-saving a map after the game's internal tile IDs have changed.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix buffered audio sources not being disposed.
|
||||
|
||||
|
||||
## 215.3.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert zstd update.
|
||||
|
||||
|
||||
## 215.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* `EntityQuery<T>` now has `HasComp` and `TryComp` methods that are shorter than its existing ones.
|
||||
* Added `PlacementInformation.UseEditorContext`.
|
||||
* Added `Vector2Helpers` functions for comparing ranges between vectors.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `Texture.GetPixel()`: fixed off-by-one with Y coordinate.
|
||||
* `Texture.GetPixel()`: fix stack overflow when reading large images.
|
||||
* `Texture.GetPixel()`: use more widely compatible OpenGL calls.
|
||||
|
||||
### Other
|
||||
|
||||
* Disabled `net.mtu_expand` again by default, as it was causing issues.
|
||||
* Updated `SharpZstd` dependency.
|
||||
|
||||
|
||||
## 215.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Implement basic VV for SoundSpecifiers.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix QueueDel during EndCollideEvents from throwing while removing contacts.
|
||||
|
||||
|
||||
## 215.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add a CompletionHelper for audio filepaths that handles server packaging.
|
||||
* Add Random.NextAngle(min, max) method and Pick for `ValueList<T>`.
|
||||
* Added an `ICommonSession` parser for toolshed commands.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
|
||||
## 215.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Update Lidgren to 0.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Made a new `IMetricsManager` interface with an `UpdateMetrics` event that can be used to update Prometheus metrics whenever they are scraped.
|
||||
* Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly.
|
||||
* IoC now contains an `IMeterFactory` implementation that you can use to instantiate metric meters.
|
||||
* `net.mtu_ipv6` CVar allows specifying a different MTU value for IPv6.
|
||||
* Allows `player:entity` to take a parameter representing the player name.
|
||||
* Add collection parsing to the dev window for UI.
|
||||
* Add a debug assert to Dirty(uid, comp) to catch mismatches being passed in.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Support transform states with unknown parents.
|
||||
* Fix serialization error logging.
|
||||
* Fix naming of ResizableMemoryRegion metrics.
|
||||
* Fix uncaught overflow exception when parsing NetEntities.
|
||||
|
||||
### Other
|
||||
|
||||
* The replay system now allows loading a replay with a mismatching serializer type hash. This means replays should be more robust against future version updates (engine security patches or .NET updates).
|
||||
* `CheckBox`'s interior texture is now vertically centered.
|
||||
* Lidgren.Network has been updated to [`v0.3.0`](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.3.0/RELEASE-NOTES.md).
|
||||
* Lowered default IPv4 MTU to 900 (from 1000).
|
||||
* Automatic MTU expansion (`net.mtu_expand`) is now enabled by default.
|
||||
|
||||
### Internal
|
||||
|
||||
* Cleanup some Dirty component calls internally.
|
||||
|
||||
|
||||
## 214.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added a `Undetachable` entity metadata flag, which stops the client from moving an entity to nullspace when it moves out of PVS range.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix tooltips not clamping to the left side of the viewport.
|
||||
* Fix global audio property not being properly set.
|
||||
|
||||
### Internal
|
||||
|
||||
* The server game state / PVS code has been rewritten. It should be somewhat faster now, albeit at the cost of using more memory. The current engine version may be unstable.
|
||||
|
||||
|
||||
## 214.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed connection denial always causing redial.
|
||||
|
||||
|
||||
## 214.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added the `pvs_override_info` command for debugging PVS overrides.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix VV for prototype structs.
|
||||
* Fix audio limits for clientside audio.
|
||||
|
||||
|
||||
## 214.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `NetStructuredDisconnectMessages` has received a complete overhaul and has been moved to `NetDisconnectMessage`. The API is no longer designed such that consumers must pass around JSON nodes, as they are not in sandbox (and clunky).
|
||||
|
||||
### New features
|
||||
|
||||
* Add a basic default concurrent audio limit of 16 for a single filepath to avoid overflowing audio sources.
|
||||
* `NetConnectingArgs.Deny()` can now pass along structured data that will be received by the client.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed cursor position bugs when an empty `TextEdit` has a multi-line place holder.
|
||||
* Fixed empty `TextEdit` throwing exception if cursor is moved left.
|
||||
|
||||
|
||||
## 213.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove obsoleted BaseContainer methods.
|
||||
|
||||
### New features
|
||||
|
||||
* Add EntityManager.RaiseSharedEvent where the event won't go to the attached client but will be predicted locally on their end.
|
||||
* Add GetEntitiesInRange override that takes in EntityCoordinates and an EntityUid hashset.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Check if a sprite entity is deleted before drawing in SpriteView.
|
||||
|
||||
|
||||
## 212.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add IsHardCollidable to SharedPhysicsSystem to determine if 2 entities would collide.
|
||||
|
||||
### Other
|
||||
|
||||
* Double the default maximum replay size.
|
||||
|
||||
|
||||
## 212.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add nullable methods for TryIndex / HasIndex on IPrototypeManager.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix TextureRect alignment where the strech mode is KeepCentered.
|
||||
|
||||
|
||||
## 212.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix passing array by `this` instead of by `ref`.
|
||||
|
||||
|
||||
## 212.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Change Collapsible controls default orientations to Vertical.
|
||||
|
||||
### New features
|
||||
|
||||
* Expose the Label control for Collapsible controls.
|
||||
* Add GetGridPosition that considers physics center-of-mass.
|
||||
* Add TileToVector methods to get the LocalPosition of tile-coords (taking into account tile size).
|
||||
* Add some more helper methods to PVS filters around EntityUids.
|
||||
* Add support for Dictionary AutoNetworkedFields.
|
||||
* Add EnsureLength method for arrays.
|
||||
* Add PushMarkup to FormattedMessage.
|
||||
* Add DrawPrimitives overload for `List<Vector2>`
|
||||
* Add more ValueList ctors that are faster.
|
||||
* Add ToMapCoordinates method for NetCoordinates.
|
||||
|
||||
### Other
|
||||
|
||||
* Remove ISerializationHooks obsoletion as they are useful in some rare cases.
|
||||
|
||||
### Internal
|
||||
|
||||
* Bump max pool size for robust jobs.
|
||||
|
||||
|
||||
## 211.0.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix TextureRect scaling not handling UIScale correctly.
|
||||
|
||||
|
||||
## 211.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
id: Audio
|
||||
name: Audio
|
||||
description: Audio entity used by engine
|
||||
save: false
|
||||
save: false
|
||||
components:
|
||||
- type: Transform
|
||||
gridTraversal: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- type: entity
|
||||
id: debugRotation
|
||||
abstract: true
|
||||
categories: [ debug ]
|
||||
categories: [ Debug ]
|
||||
components:
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
# debug related entities
|
||||
- type: entityCategory
|
||||
id: debug
|
||||
id: Debug
|
||||
name: entity-category-name-debug
|
||||
description: entity-category-desc-debug
|
||||
suffix: entity-category-suffix-debug
|
||||
|
||||
# entities that spawn other entities
|
||||
- type: entityCategory
|
||||
id: spawner
|
||||
id: Spawner
|
||||
name: entity-category-name-spawner
|
||||
description: entity-category-desc-spawner
|
||||
|
||||
# entities that should be hidden from the spawn menu
|
||||
# simple category that just exists to hide prototypes in spawn menus
|
||||
- type: entityCategory
|
||||
id: hideSpawnMenu
|
||||
id: HideSpawnMenu
|
||||
name: entity-category-name-hide
|
||||
description: entity-category-desc-hide
|
||||
hideSpawnMenu: true
|
||||
inheritable: false
|
||||
|
||||
@@ -9,8 +9,10 @@ cmd-parse-failure-float = {$arg} is not a valid float.
|
||||
cmd-parse-failure-bool = {$arg} is not a valid bool.
|
||||
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-entity-exist = UID {$arg} does not correspond to an existing entity.
|
||||
cmd-parse-failure-session = There is no session with username: {$username}
|
||||
|
||||
cmd-error-file-not-found = Could not find file: {$file}.
|
||||
cmd-error-dir-not-found = Could not find directory: {$dir}.
|
||||
@@ -42,6 +44,13 @@ cmd-cvar-compl-list = List available CVars
|
||||
cmd-cvar-arg-name = <name | ?>
|
||||
cmd-cvar-value-hidden = <value hidden>
|
||||
|
||||
## 'cvar_subs' command
|
||||
cmd-cvar_subs-desc = Lists the OnValueChanged subscriptions for a CVar.
|
||||
cmd-cvar_subs-help = Usage: cvar_subs <name>
|
||||
|
||||
cmd-cvar_subs-invalid-args = Must provide exactly one argument.
|
||||
cmd-cvar_subs-arg-name = <name>
|
||||
|
||||
## 'list' command
|
||||
cmd-list-desc = Lists available commands, with optional search filter
|
||||
cmd-list-help = Usage: list [filter]
|
||||
@@ -244,9 +253,6 @@ cmd-bind-arg-command = <InputCommand>
|
||||
cmd-net-draw-interp-desc = Toggles the debug drawing of the network interpolation.
|
||||
cmd-net-draw-interp-help = Usage: net_draw_interp
|
||||
|
||||
cmd-net-draw-interp-desc = Toggles the debug drawing of the network interpolation.
|
||||
cmd-net-draw-interp-help = Usage: net_draw_interp
|
||||
|
||||
cmd-net-watch-ent-desc = Dumps all network updates for an EntityId to the console.
|
||||
cmd-net-watch-ent-help = Usage: net_watchent <0|EntityUid>
|
||||
|
||||
@@ -298,16 +304,9 @@ cmd-savegrid-help = savegrid <gridID> <Path>
|
||||
cmd-testbed-desc = Loads a physics testbed on the specified map.
|
||||
cmd-testbed-help = testbed <mapid> <test>
|
||||
|
||||
cmd-saveconfig-desc = Saves the client configuration to the config file.
|
||||
cmd-saveconfig-help = saveconfig
|
||||
|
||||
## 'flushcookies' command
|
||||
# Note: the flushcookies command is from Robust.Client.WebView, it's not in the main engine code.
|
||||
|
||||
cmd-flushcookies-desc = Flush CEF cookie storage to disk
|
||||
cmd-flushcookies-help = This ensure cookies are properly saved to disk in the event of unclean shutdowns.
|
||||
Note that the actual operation is asynchronous.
|
||||
|
||||
## 'addcomp' command
|
||||
cmd-addcomp-desc = Adds a component to an entity.
|
||||
cmd-addcomp-help = addcomp <uid> <componentName>
|
||||
@@ -383,9 +382,9 @@ cmd-tp-desc = Teleports a player to any location in the round.
|
||||
cmd-tp-help = tp <x> <y> [<mapID>]
|
||||
|
||||
cmd-tpto-desc = Teleports the current player or the specified players/entities to the location of the first player/entity.
|
||||
cmd-tpto-help = tpto <username|uid> [username|uid]...
|
||||
cmd-tpto-destination-hint = destination (uid or username)
|
||||
cmd-tpto-victim-hint = entity to teleport (uid or username)
|
||||
cmd-tpto-help = tpto <username|uid> [username|NetEntity]...
|
||||
cmd-tpto-destination-hint = destination (NetEntity or username)
|
||||
cmd-tpto-victim-hint = entity to teleport (NetEntity or username)
|
||||
cmd-tpto-parse-error = Cant resolve entity or player: {$str}
|
||||
|
||||
cmd-listplayers-desc = Lists all players currently connected.
|
||||
@@ -445,9 +444,6 @@ cmd-showanchored-help = Usage: showanchored
|
||||
cmd-dmetamem-desc = Dumps a type's members in a format suitable for the sandbox configuration file.
|
||||
cmd-dmetamem-help = Usage: dmetamem <type>
|
||||
|
||||
cmd-dmetamem-desc = Displays chunk bounds for the purposes of rendering.
|
||||
cmd-dmetamem-help = Usage: showchunkbb <type>
|
||||
|
||||
cmd-launchauth-desc = Load authentication tokens from launcher data to aid in testing of live servers.
|
||||
cmd-launchauth-help = Usage: launchauth <account name>
|
||||
|
||||
@@ -490,7 +486,7 @@ cmd-net_entityreport-help = Usage: net_entityreport
|
||||
cmd-net_refresh-desc = Requests a full server state.
|
||||
cmd-net_refresh-help = Usage: net_refresh
|
||||
|
||||
cmd-net_graph-desc = Toggles the net statistics pannel.
|
||||
cmd-net_graph-desc = Toggles the net statistics panel.
|
||||
cmd-net_graph-help = Usage: net_graph
|
||||
|
||||
cmd-net_watchent-desc = Dumps all network updates for an EntityId to the console.
|
||||
@@ -514,9 +510,6 @@ cmd-profsnap-help = Usage: profsnap
|
||||
cmd-devwindow-desc = Dev Window
|
||||
cmd-devwindow-help = Usage: devwindow
|
||||
|
||||
cmd-devwindow-desc = Open file
|
||||
cmd-devwindow-help = Usage: testopenfile
|
||||
|
||||
cmd-scene-desc = Immediately changes the UI scene/state.
|
||||
cmd-scene-help = Usage: scene <className>
|
||||
|
||||
@@ -527,14 +520,11 @@ cmd-hwid-desc = Returns the current HWID (HardWare ID).
|
||||
cmd-hwid-help = Usage: hwid
|
||||
|
||||
cmd-vvread-desc = Retrieve a path's value using VV (View Variables).
|
||||
cmd-vvread-desc = Usage: vvread <path>
|
||||
cmd-vvread-help = Usage: vvread <path>
|
||||
|
||||
cmd-vvwrite-desc = Modify a path's value using VV (View Variables).
|
||||
cmd-vvwrite-help = Usage: vvwrite <path>
|
||||
|
||||
cmd-vv-desc = Opens View Variables (VV).
|
||||
cmd-vv-help = Usage: vv <path|entity ID|guihover>
|
||||
|
||||
cmd-vvinvoke-desc = Invoke/Call a path with arguments using VV.
|
||||
cmd-vvinvoke-help = Usage: vvinvoke <path> [arguments...]
|
||||
|
||||
@@ -566,3 +556,9 @@ cmd-reloadtiletextures-help = Usage: reloadtiletextures
|
||||
cmd-audio_length-desc = Shows the length of an audio file
|
||||
cmd-audio_length-help = Usage: audio_length { cmd-audio_length-arg-file-name }
|
||||
cmd-audio_length-arg-file-name = <file name>
|
||||
|
||||
## PVS
|
||||
cmd-pvs-override-info-desc = Prints information about any PVS overrides associated with an entity.
|
||||
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}.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
entity-category-name-debug = Debug
|
||||
entity-category-desc-debug = Entity prototypes intended for debugging & testing.
|
||||
entity-category-suffix-debug = Debug
|
||||
|
||||
entity-category-name-spawner = Spawner
|
||||
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 the spawn menu
|
||||
entity-category-desc-hide = Entity prototypes that should be hidden from entity spawn menus
|
||||
|
||||
@@ -70,9 +70,9 @@ command-description-ls-in =
|
||||
command-description-methods-get =
|
||||
Returns all methods associated with the input type.
|
||||
command-description-methods-overrides =
|
||||
Returns all methods overriden on the input type.
|
||||
Returns all methods overridden on the input type.
|
||||
command-description-methods-overridesfrom =
|
||||
Returns all methods overriden from the given type on the input type.
|
||||
Returns all methods overridden from the given type on the input type.
|
||||
command-description-cmd-moo =
|
||||
Asks the important questions.
|
||||
command-description-cmd-descloc =
|
||||
@@ -418,6 +418,6 @@ command-description-tee =
|
||||
This essentially lets you have a branch in your code to do multiple operations on one value.
|
||||
command-description-cmd-info =
|
||||
Returns a CommandSpec for the given command.
|
||||
On it's own, this means it'll print the comamnd's help message.
|
||||
On its own, this means it'll print the command's help message.
|
||||
command-description-comp-rm =
|
||||
Removes the given component from the entity.
|
||||
|
||||
@@ -10,3 +10,18 @@ view-variable-instance-entity-client-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-server-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-add-window-server-components = Add Component [S]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
|
||||
|
||||
## SoundSpecifier
|
||||
vv-sound-none = None
|
||||
vv-sound-path = Path
|
||||
vv-sound-collection = Collection
|
||||
|
||||
vv-sound-volume = volume
|
||||
vv-sound-pitch = Pitch
|
||||
vv-sound-max-distance = Max Distance
|
||||
vv-sound-rolloff-factor = Rolloff Factor
|
||||
vv-sound-reference-distance = Reference Distance
|
||||
vv-sound-loop = Loop
|
||||
vv-sound-play-offset = Play Offset (s)
|
||||
vv-sound-variation = Pitch variation
|
||||
|
||||
@@ -20,11 +20,16 @@ public sealed class AccessAnalyzer_Test
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
AdditionalReferences = { typeof(AccessAnalyzer).Assembly },
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Analyzers.AccessAttribute.cs",
|
||||
"Robust.Shared.Analyzers.AccessPermissions.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
|
||||
58
Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs
Normal file
58
Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
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>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class DependencyAssignAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<DependencyAssignAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
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;
|
||||
|
||||
public Foo()
|
||||
{
|
||||
Field = "A";
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,9): warning RA0025: Tried to assign to [Dependency] field 'Field'. Remove [Dependency] or inject it via field injection instead.
|
||||
VerifyCS.Diagnostic().WithSpan(10, 9, 10, 20).WithArguments("Field"));
|
||||
}
|
||||
}
|
||||
57
Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs
Normal file
57
Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
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>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class NoUncachedRegexAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
// 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 System.Text.RegularExpressions;
|
||||
|
||||
public static class Foo
|
||||
{
|
||||
public static void Bad()
|
||||
{
|
||||
Regex.Replace("foo", "bar", "baz");
|
||||
}
|
||||
|
||||
public static void Good()
|
||||
{
|
||||
var r = new Regex("bar");
|
||||
r.Replace("foo", "baz");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(7,9): warning RA0026: Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.
|
||||
VerifyCS.Diagnostic().WithSpan(7, 9, 7, 43)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,13 @@
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets"/>
|
||||
<Import Project="..\MSBuild\Robust.Engine.props"/>
|
||||
|
||||
<!-- Engine source files needed to make the tests work -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LogicalName="Robust.Shared.Analyzers.AccessAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LogicalName="Robust.Shared.Analyzers.AccessPermissions.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
22
Robust.Analyzers.Tests/TestHelper.cs
Normal file
22
Robust.Analyzers.Tests/TestHelper.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
public static class TestHelper
|
||||
{
|
||||
public static void AddEmbeddedSources(SolutionState state, params string[] embeddedFiles)
|
||||
{
|
||||
AddEmbeddedSources(state, (IEnumerable<string>) embeddedFiles);
|
||||
}
|
||||
|
||||
public static void AddEmbeddedSources(SolutionState state, IEnumerable<string> embeddedFiles)
|
||||
{
|
||||
foreach (var fileName in embeddedFiles)
|
||||
{
|
||||
using var stream = typeof(AccessAnalyzer_Test).Assembly.GetManifestResourceStream(fileName)!;
|
||||
state.Sources.Add((fileName, SourceText.From(stream)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
using Robust.Shared.Serialization.Manager.Definition;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
@@ -56,8 +57,18 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to add a setter."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
|
||||
Diagnostics.IdDataFieldRedundantTag,
|
||||
"Data field has redundant tag specified",
|
||||
"Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Info,
|
||||
true,
|
||||
"Make sure to remove the tag string from the data field attribute."
|
||||
);
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
|
||||
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule
|
||||
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule,
|
||||
DataFieldRedundantTagRule
|
||||
);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
@@ -125,6 +136,11 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(fieldSymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +165,11 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(propertySymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
|
||||
@@ -248,6 +269,29 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasRedundantTag(ISymbol symbol)
|
||||
{
|
||||
if (!IsDataField(symbol, out var _, out var attribute))
|
||||
return false;
|
||||
|
||||
// No args, no problem
|
||||
if (attribute.ConstructorArguments.Length == 0)
|
||||
return false;
|
||||
|
||||
// If a tag is explicitly specified, it will be the first argument...
|
||||
var tagArgument = attribute.ConstructorArguments[0];
|
||||
// ...but the first arg could also something else, since tag is optional
|
||||
// so we make sure that it's a string
|
||||
if (tagArgument.Value is not string explicitName)
|
||||
return false;
|
||||
|
||||
// Get the name that sourcegen would provide
|
||||
var automaticName = DataDefinitionUtility.AutoGenerateTag(symbol.Name);
|
||||
|
||||
// If the explicit name matches the sourcegen name, we have a redundancy
|
||||
return explicitName == automaticName;
|
||||
}
|
||||
|
||||
private static bool IsImplicitDataDefinition(ITypeSymbol type)
|
||||
{
|
||||
if (HasAttribute(type, ImplicitDataDefinitionNamespace))
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
@@ -16,8 +13,11 @@ namespace Robust.Analyzers;
|
||||
[ExportCodeFixProvider(LanguageNames.CSharp)]
|
||||
public sealed class DefinitionFixer : CodeFixProvider
|
||||
{
|
||||
private const string DataFieldAttributeName = "DataField";
|
||||
|
||||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
|
||||
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable
|
||||
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable,
|
||||
IdDataFieldRedundantTag
|
||||
);
|
||||
|
||||
public override Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||
@@ -34,6 +34,8 @@ public sealed class DefinitionFixer : CodeFixProvider
|
||||
return RegisterDataFieldFix(context, diagnostic);
|
||||
case IdDataFieldPropertyWritable:
|
||||
return RegisterDataFieldPropertyFix(context, diagnostic);
|
||||
case IdDataFieldRedundantTag:
|
||||
return RegisterRedundantTagFix(context, diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +74,68 @@ public sealed class DefinitionFixer : CodeFixProvider
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private static async Task RegisterRedundantTagFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
var span = diagnostic.Location.SourceSpan;
|
||||
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<MemberDeclarationSyntax>().First();
|
||||
|
||||
if (token == null)
|
||||
return;
|
||||
|
||||
// Find the DataField attribute
|
||||
AttributeSyntax? dataFieldAttribute = null;
|
||||
foreach (var attributeList in token.AttributeLists)
|
||||
{
|
||||
foreach (var attribute in attributeList.Attributes)
|
||||
{
|
||||
if (attribute.Name.ToString() == DataFieldAttributeName)
|
||||
{
|
||||
dataFieldAttribute = attribute;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dataFieldAttribute != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (dataFieldAttribute == null)
|
||||
return;
|
||||
|
||||
context.RegisterCodeFix(CodeAction.Create(
|
||||
"Remove explicitly set tag",
|
||||
c => RemoveRedundantTag(context.Document, dataFieldAttribute, c),
|
||||
"Remove explicitly set tag"
|
||||
), diagnostic);
|
||||
}
|
||||
|
||||
private static async Task<Document> RemoveRedundantTag(Document document, AttributeSyntax syntax, CancellationToken cancellation)
|
||||
{
|
||||
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
|
||||
|
||||
if (syntax.ArgumentList == null)
|
||||
return document;
|
||||
|
||||
AttributeSyntax? newSyntax;
|
||||
if (syntax.ArgumentList.Arguments.Count == 1)
|
||||
{
|
||||
// If this is the only argument, delete the ArgumentList so we don't leave empty parentheses
|
||||
newSyntax = syntax.RemoveNode(syntax.ArgumentList, SyntaxRemoveOptions.KeepNoTrivia);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove the first argument, which is the tag
|
||||
var newArgs = syntax.ArgumentList.Arguments.RemoveAt(0);
|
||||
var newArgList = syntax.ArgumentList.WithArguments(newArgs);
|
||||
// Construct a new attribute with the tag removed
|
||||
newSyntax = syntax.WithArgumentList(newArgList);
|
||||
}
|
||||
|
||||
root = root!.ReplaceNode(syntax, newSyntax!);
|
||||
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
|
||||
61
Robust.Analyzers/DependencyAssignAnalyzer.cs
Normal file
61
Robust.Analyzers/DependencyAssignAnalyzer.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class DependencyAssignAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new (
|
||||
Diagnostics.IdDependencyFieldAssigned,
|
||||
"Assignment to dependency field",
|
||||
"Tried to assign to [Dependency] field '{0}'. Remove [Dependency] or inject it via field injection instead.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.RegisterOperationAction(CheckAssignment, OperationKind.SimpleAssignment);
|
||||
}
|
||||
|
||||
private static void CheckAssignment(OperationAnalysisContext context)
|
||||
{
|
||||
if (context.Operation is not ISimpleAssignmentOperation assignment)
|
||||
return;
|
||||
|
||||
if (assignment.Target is not IFieldReferenceOperation fieldRef)
|
||||
return;
|
||||
|
||||
var field = fieldRef.Field;
|
||||
var attributes = field.GetAttributes();
|
||||
if (attributes.Length == 0)
|
||||
return;
|
||||
|
||||
var depAttribute = context.Compilation.GetTypeByMetadataName(DependencyAttributeType);
|
||||
if (!HasAttribute(attributes, depAttribute))
|
||||
return;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(Rule, assignment.Syntax.GetLocation(), field.Name));
|
||||
}
|
||||
|
||||
private static bool HasAttribute(ImmutableArray<AttributeData> attributes, ISymbol symbol)
|
||||
{
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, symbol))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
66
Robust.Analyzers/NoUncachedRegexAnalyzer.cs
Normal file
66
Robust.Analyzers/NoUncachedRegexAnalyzer.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class NoUncachedRegexAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string RegexTypeName = "Regex";
|
||||
private const string RegexType = $"System.Text.RegularExpressions.{RegexTypeName}";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new (
|
||||
Diagnostics.IdUncachedRegex,
|
||||
"Use of uncached static Regex function",
|
||||
"Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public static readonly HashSet<string> BadFunctions =
|
||||
[
|
||||
"Count",
|
||||
"EnumerateMatches",
|
||||
"IsMatch",
|
||||
"Match",
|
||||
"Matches",
|
||||
"Replace",
|
||||
"Split"
|
||||
];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.RegisterOperationAction(CheckInvocation, OperationKind.Invocation);
|
||||
}
|
||||
|
||||
private static void CheckInvocation(OperationAnalysisContext context)
|
||||
{
|
||||
if (context.Operation is not IInvocationOperation invocation)
|
||||
return;
|
||||
|
||||
// All Regex functions we care about are static.
|
||||
var targetMethod = invocation.TargetMethod;
|
||||
if (!targetMethod.IsStatic)
|
||||
return;
|
||||
|
||||
// Bail early.
|
||||
if (targetMethod.ContainingType.Name != "Regex")
|
||||
return;
|
||||
|
||||
var regexType = context.Compilation.GetTypeByMetadataName(RegexType);
|
||||
if (!SymbolEqualityComparer.Default.Equals(regexType, targetMethod.ContainingType))
|
||||
return;
|
||||
|
||||
if (!BadFunctions.Contains(targetMethod.Name))
|
||||
return;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.Syntax.GetLocation()));
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,34 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for NotNullableFlagAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for FriendAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LinkBase="Implementations" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for PreferGenericVariantAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for DataDefinitionAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Serialization\Manager\Definition\DataDefinitionUtility.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>disable</Nullable>
|
||||
<!--
|
||||
Rider seems to get really confused with hot reload if we directly compile in the above-linked classes.
|
||||
As such, they have an #if to change their namespace in this project.
|
||||
-->
|
||||
<DefineConstants>$(DefineConstants);ROBUST_ANALYZERS_IMPL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Collections;
|
||||
|
||||
namespace Robust.Benchmarks.Collections;
|
||||
|
||||
[Virtual]
|
||||
public class ValueListEnumerationBenchmarks
|
||||
{
|
||||
[Params(4, 16, 64)]
|
||||
public int N { get; set; }
|
||||
|
||||
private sealed class Data(int i)
|
||||
{
|
||||
public readonly int I = i;
|
||||
}
|
||||
|
||||
private ValueList<Data> _valueList;
|
||||
private Data[] _array = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var list = new List<Data>(N);
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
list.Add(new(i));
|
||||
}
|
||||
|
||||
_array = list.ToArray();
|
||||
_valueList = new(list.ToArray());
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int ValueList()
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var ev in _valueList)
|
||||
{
|
||||
total += ev.I;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int ValueListSpan()
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var ev in _valueList.Span)
|
||||
{
|
||||
total += ev.I;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int Array()
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var ev in _array)
|
||||
{
|
||||
total += ev.I;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int Span()
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var ev in _array.AsSpan())
|
||||
{
|
||||
total += ev.I;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
@@ -26,9 +26,8 @@ public partial class AddRemoveComponentBenchmark
|
||||
.InitializeInstance();
|
||||
|
||||
_entityManager = _simulation.Resolve<IEntityManager>();
|
||||
|
||||
var coords = new MapCoordinates(0, 0, new MapId(1));
|
||||
_simulation.AddMap(coords.MapId);
|
||||
var map = _simulation.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
|
||||
@@ -29,8 +29,8 @@ public partial class ComponentIteratorBenchmark
|
||||
|
||||
Comps = new A[N+2];
|
||||
|
||||
var coords = new MapCoordinates(0, 0, new MapId(1));
|
||||
_simulation.AddMap(coords.MapId);
|
||||
var map = _simulation.CreateMap().MapId;
|
||||
var coords = new MapCoordinates(default, map);
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
|
||||
@@ -31,8 +31,8 @@ public partial class GetComponentBenchmark
|
||||
|
||||
Comps = new A[N+2];
|
||||
|
||||
var coords = new MapCoordinates(0, 0, new MapId(1));
|
||||
_simulation.AddMap(coords.MapId);
|
||||
var map = _simulation.CreateMap().Uid;
|
||||
var coords = new EntityCoordinates(map, default);
|
||||
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
|
||||
@@ -29,10 +29,9 @@ public partial class SpawnDeleteEntityBenchmark
|
||||
.InitializeInstance();
|
||||
|
||||
_entityManager = _simulation.Resolve<IEntityManager>();
|
||||
|
||||
_mapCoords = new MapCoordinates(0, 0, new MapId(1));
|
||||
var uid = _simulation.AddMap(_mapCoords.MapId);
|
||||
_entCoords = new EntityCoordinates(uid, 0, 0);
|
||||
var (map, mapId) = _simulation.CreateMap();
|
||||
_mapCoords = new MapCoordinates(default, mapId);
|
||||
_entCoords = new EntityCoordinates(map, 0, 0);
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
|
||||
@@ -91,8 +91,7 @@ public class RecursiveMoveBenchmark : RobustIntegrationTest
|
||||
// Set up map and spawn player
|
||||
server.WaitPost(() =>
|
||||
{
|
||||
var mapId = mapMan.CreateMap();
|
||||
var map = mapMan.GetMapEntityId(mapId);
|
||||
var map = server.ResolveDependency<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var gridComp = mapMan.CreateGridEntity(mapId);
|
||||
var grid = gridComp.Owner;
|
||||
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
|
||||
|
||||
@@ -239,6 +239,7 @@ namespace Robust.Build.Tasks
|
||||
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
|
||||
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
|
||||
}
|
||||
res.Remove();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -74,11 +74,13 @@ public sealed class AudioOverlay : Overlay
|
||||
output.Clear();
|
||||
output.AppendLine("Audio Source");
|
||||
output.AppendLine("Runtime:");
|
||||
output.AppendLine($"- Distance: {_audio.GetAudioDistance(distance.Length()):0.00}");
|
||||
output.AppendLine($"- Occlusion: {posOcclusion:0.0000}");
|
||||
output.AppendLine("Params:");
|
||||
output.AppendLine($"- RolloffFactor: {comp.RolloffFactor:0.0000}");
|
||||
output.AppendLine($"- Volume: {comp.Volume:0.0000}");
|
||||
output.AppendLine($"- Reference distance: {comp.ReferenceDistance}");
|
||||
output.AppendLine($"- Max distance: {comp.MaxDistance}");
|
||||
output.AppendLine($"- Reference distance: {comp.ReferenceDistance:0.00}");
|
||||
output.AppendLine($"- Max distance: {comp.MaxDistance:0.00}");
|
||||
var outputText = output.ToString().Trim();
|
||||
var dimensions = screenHandle.GetDimensions(_font, outputText, 1f);
|
||||
var buffer = new Vector2(3f, 3f);
|
||||
|
||||
57
Robust.Client/Audio/AudioSystem.Limits.cs
Normal file
57
Robust.Client/Audio/AudioSystem.Limits.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
public sealed partial class AudioSystem
|
||||
{
|
||||
/*
|
||||
* Handles limiting concurrent sounds for audio to avoid blowing the source budget on one sound getting spammed.
|
||||
*/
|
||||
|
||||
private readonly Dictionary<string, int> _playingCount = new();
|
||||
|
||||
private int _maxConcurrent;
|
||||
|
||||
private void InitializeLimit()
|
||||
{
|
||||
Subs.CVar(CfgManager, CVars.AudioDefaultConcurrent, SetConcurrentLimit, true);
|
||||
}
|
||||
|
||||
private void SetConcurrentLimit(int obj)
|
||||
{
|
||||
_maxConcurrent = obj;
|
||||
}
|
||||
|
||||
private bool TryAudioLimit(string sound)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sound))
|
||||
return true;
|
||||
|
||||
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(_playingCount, sound, out _);
|
||||
|
||||
if (count >= _maxConcurrent)
|
||||
return false;
|
||||
|
||||
count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RemoveAudioLimit(string sound)
|
||||
{
|
||||
if (!_playingCount.TryGetValue(sound, out var count))
|
||||
return;
|
||||
|
||||
count--;
|
||||
|
||||
if (count <= 0)
|
||||
{
|
||||
_playingCount.Remove(sound);
|
||||
return;
|
||||
}
|
||||
|
||||
_playingCount[sound] = count;
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
[Dependency] private readonly IParallelManager _parMan = default!;
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
[Dependency] private readonly IAudioInternal _audio = default!;
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xformSys = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
|
||||
@@ -51,6 +52,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private EntityUid? _listenerGrid;
|
||||
private UpdateAudioJob _updateAudioJob;
|
||||
|
||||
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
|
||||
private float _maxRayLength;
|
||||
@@ -108,6 +110,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
InitializeLimit();
|
||||
}
|
||||
|
||||
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
@@ -123,6 +126,33 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
component.Source.SetAuxiliary(null);
|
||||
}
|
||||
|
||||
switch (component.State)
|
||||
{
|
||||
case AudioState.Playing:
|
||||
component.StartPlaying();
|
||||
break;
|
||||
case AudioState.Paused:
|
||||
component.Pause();
|
||||
break;
|
||||
case AudioState.Stopped:
|
||||
component.StopPlaying();
|
||||
component.PlaybackPosition = 0f;
|
||||
break;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
if (diff > 0.1f)
|
||||
{
|
||||
component.PlaybackPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -163,26 +193,32 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
SetupSource(component, audioResource);
|
||||
SetupSource((uid, component), audioResource);
|
||||
component.Loaded = true;
|
||||
}
|
||||
|
||||
private void SetupSource(AudioComponent component, AudioResource audioResource, TimeSpan? length = null)
|
||||
private void SetupSource(Entity<AudioComponent> entity, AudioResource audioResource, TimeSpan? length = null)
|
||||
{
|
||||
var source = _audio.CreateAudioSource(audioResource);
|
||||
var component = entity.Comp;
|
||||
|
||||
if (source == null)
|
||||
if (TryAudioLimit(component.FileName))
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}");
|
||||
DebugTools.Assert(false);
|
||||
source = component.Source;
|
||||
}
|
||||
var newSource = _audio.CreateAudioSource(audioResource);
|
||||
|
||||
component.Source = source;
|
||||
if (newSource == null)
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}");
|
||||
DebugTools.Assert(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
component.Source = newSource;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to set all initial data for first frame.
|
||||
ApplyAudioParams(component.Params, component);
|
||||
source.Global = component.Global;
|
||||
component.Source.Global = component.Global;
|
||||
|
||||
// Don't play until first frame so occlusion etc. are correct.
|
||||
component.Gain = 0f;
|
||||
@@ -202,6 +238,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
// Breaks with prediction?
|
||||
component.Source.Dispose();
|
||||
|
||||
RemoveAudioLimit(component.FileName);
|
||||
}
|
||||
|
||||
private void OnAudioAttenuation(int obj)
|
||||
@@ -293,59 +331,26 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
Vector2 worldPos;
|
||||
var gridUid = xform.ParentUid;
|
||||
component.Volume = component.Params.Volume;
|
||||
Vector2 delta;
|
||||
|
||||
// Handle grid audio differently by using nearest-edge instead of entity centre.
|
||||
// Handle grid audio differently by using grid position.
|
||||
if ((component.Flags & AudioFlags.GridAudio) != 0x0)
|
||||
{
|
||||
// It's our grid so max volume.
|
||||
if (_listenerGrid == gridUid)
|
||||
{
|
||||
component.Volume = component.Params.Volume;
|
||||
component.Occlusion = 0f;
|
||||
component.Position = listener.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Need a grid-optimised version because this is gonna be expensive.
|
||||
// Just to avoid clipping on and off grid or nearestPoint changing we'll
|
||||
// always set the sound to listener's pos, we'll just manually do gain ourselves.
|
||||
if (_physics.TryGetNearest(gridUid, listener, out _, out var gridDistance))
|
||||
{
|
||||
// Out of range
|
||||
if (gridDistance > component.MaxDistance)
|
||||
{
|
||||
component.Gain = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
var paramsGain = VolumeToGain(component.Params.Volume);
|
||||
|
||||
// Thought I'd never have to manually calculate gain again but this is the least
|
||||
// unpleasant audio I could get at the moment.
|
||||
component.Gain = paramsGain * _audio.GetAttenuationGain(
|
||||
gridDistance,
|
||||
component.Params.RolloffFactor,
|
||||
component.Params.ReferenceDistance,
|
||||
component.Params.MaxDistance);
|
||||
component.Position = listener.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
// Can't get nearest point so don't play anymore.
|
||||
component.Gain = 0f;
|
||||
return;
|
||||
var parentUid = xform.ParentUid;
|
||||
worldPos = _maps.GetGridPosition(parentUid);
|
||||
}
|
||||
else
|
||||
{
|
||||
worldPos = _xformSys.GetWorldPosition(entity);
|
||||
}
|
||||
|
||||
worldPos = _xformSys.GetWorldPosition(entity);
|
||||
component.Volume = component.Params.Volume;
|
||||
|
||||
// Max distance check
|
||||
var delta = worldPos - listener.Position;
|
||||
delta = worldPos - listener.Position;
|
||||
var distance = delta.Length();
|
||||
|
||||
// Out of range so just clip it for us.
|
||||
if (distance > component.MaxDistance)
|
||||
if (GetAudioDistance(distance) > component.MaxDistance)
|
||||
{
|
||||
// Still keeps the source playing, just with no volume.
|
||||
component.Gain = 0f;
|
||||
@@ -360,8 +365,15 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
// Update audio occlusion
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
component.Occlusion = occlusion;
|
||||
if ((component.Flags & AudioFlags.NoOcclusion) == AudioFlags.NoOcclusion)
|
||||
{
|
||||
component.Occlusion = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
component.Occlusion = occlusion;
|
||||
}
|
||||
|
||||
// Update audio positions.
|
||||
component.Position = worldPos;
|
||||
@@ -411,13 +423,13 @@ 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(string? filename, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, 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(string? filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, Filter.Local(), uid, true, audioParams);
|
||||
}
|
||||
@@ -444,8 +456,11 @@ 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(string? filename, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage
|
||||
@@ -477,8 +492,11 @@ 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(string? filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
|
||||
@@ -518,8 +536,11 @@ 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(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
|
||||
@@ -553,65 +574,65 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, 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(string? filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, 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(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
}
|
||||
|
||||
public override void LoadStream<T>(AudioComponent component, T stream)
|
||||
public override void LoadStream<T>(Entity<AudioComponent> entity, T stream)
|
||||
{
|
||||
if (stream is AudioStream audioStream)
|
||||
{
|
||||
TryGetAudio(audioStream, out var audio);
|
||||
SetupSource(component, audio!, audioStream.Length);
|
||||
component.Loaded = true;
|
||||
SetupSource(entity, audio!, audioStream.Length);
|
||||
entity.Comp.Loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, 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(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, 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(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, 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(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, 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(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
}
|
||||
@@ -619,10 +640,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream)
|
||||
{
|
||||
var audioP = audioParams ?? AudioParams.Default;
|
||||
var entity = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
|
||||
var comp = SetupAudio(entity, null, audioP, stream.Length);
|
||||
LoadStream(comp, stream);
|
||||
var entity = SetupAudio(null, audioP, initialize: false, length: stream.Length);
|
||||
LoadStream(entity, stream);
|
||||
EntityManager.InitializeAndStartEntity(entity);
|
||||
var comp = entity.Comp;
|
||||
var source = comp.Source;
|
||||
|
||||
// TODO clamp the offset inside of SetPlaybackPosition() itself.
|
||||
|
||||
@@ -314,6 +314,8 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
|
||||
value = MathF.Max(value, 0f);
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
|
||||
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
|
||||
}
|
||||
|
||||
@@ -285,6 +285,7 @@ namespace Robust.Client
|
||||
/// <summary>
|
||||
/// Enumeration of the run levels of the BaseClient.
|
||||
/// </summary>
|
||||
/// <seealso cref="ClientRunLevelExt"/>
|
||||
public enum ClientRunLevel : byte
|
||||
{
|
||||
Error = 0,
|
||||
@@ -315,6 +316,21 @@ namespace Robust.Client
|
||||
SinglePlayerGame,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper functions for working with <see cref="ClientRunLevel"/>.
|
||||
/// </summary>
|
||||
public static class ClientRunLevelExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a <see cref="ClientRunLevel"/> is <see cref="ClientRunLevel.InGame"/>
|
||||
/// or <see cref="ClientRunLevel.SinglePlayerGame"/>.
|
||||
/// </summary>
|
||||
public static bool IsInGameLike(this ClientRunLevel runLevel)
|
||||
{
|
||||
return runLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for when something changed with the player.
|
||||
/// </summary>
|
||||
|
||||
@@ -294,7 +294,6 @@ namespace Robust.Client.Console.Commands
|
||||
internal sealed class SnapGridGetCell : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
|
||||
public override string Command => "sggcell";
|
||||
|
||||
@@ -320,7 +319,7 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid))
|
||||
if (_entManager.TryGetComponent<MapGridComponent>(_entManager.GetEntity(gridNet), out var grid))
|
||||
{
|
||||
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
|
||||
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
|
||||
@@ -429,7 +428,6 @@ namespace Robust.Client.Console.Commands
|
||||
internal sealed class GridTileCount : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
|
||||
public override string Command => "gridtc";
|
||||
|
||||
@@ -448,7 +446,7 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map.TryGetGrid(gridUid, out var grid))
|
||||
if (_entManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
|
||||
{
|
||||
shell.WriteLine(grid.GetAllTiles().Count().ToString());
|
||||
}
|
||||
@@ -555,7 +553,7 @@ namespace Robust.Client.Console.Commands
|
||||
if (type != typeof(Control))
|
||||
cname = $"Control > {cname}";
|
||||
|
||||
returnVal.GetOrNew(cname).Add((member.Name, member.GetValue(control)?.ToString() ?? "null"));
|
||||
returnVal.GetOrNew(cname).Add((member.Name, GetMemberValue(member, control, ", ")));
|
||||
}
|
||||
|
||||
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
|
||||
@@ -570,6 +568,28 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
internal static string PropertyValuesString(Control control, string key)
|
||||
{
|
||||
var member = GetAllMembers(control).Find(m => m.Name == key);
|
||||
return GetMemberValue(member, control, "\n", "\"{0}\"");
|
||||
}
|
||||
|
||||
private static string GetMemberValue(MemberInfo? member, Control control, string separator, string
|
||||
wrap = "{0}")
|
||||
{
|
||||
var value = member?.GetValue(control);
|
||||
var o = value switch
|
||||
{
|
||||
ICollection<Control> controls => string.Join(separator,
|
||||
controls.Select(ctrl => $"{ctrl.Name}({ctrl.GetType()})")),
|
||||
ICollection<string> list => string.Join(separator, list),
|
||||
null => null,
|
||||
_ => value.ToString()
|
||||
};
|
||||
// Convert to quote surrounded string or null with no quotes
|
||||
return o is not null ? string.Format(wrap, o) : "null";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SetClipboardCommand : LocalizedCommands
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var type = Type.GetType(args[0]);
|
||||
var type = GetType(args[0]);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
|
||||
shell.WriteLine(sig);
|
||||
}
|
||||
}
|
||||
|
||||
private Type? GetType(string name)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (assembly.GetType(name) is { } type)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
{
|
||||
var textEdit = new TextEdit
|
||||
{
|
||||
Placeholder = new Rope.Leaf("You deleted the lipsum OwO")
|
||||
Placeholder = new Rope.Leaf("You deleted the lipsum\nOwO")
|
||||
};
|
||||
TabContainer.SetTabTitle(textEdit, "TextEdit");
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
var viewBounds = args.WorldBounds;
|
||||
var viewAABB = args.WorldAABB;
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mapId = args.MapId;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
{
|
||||
@@ -368,12 +368,12 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
worldHandle.UseShader(null);
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
|
||||
{
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mapId = args.MapId;
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
|
||||
@@ -443,7 +443,7 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
screenHandle.UseShader(null);
|
||||
screenHandle.SetTransform(Matrix3.Identity);
|
||||
screenHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
@@ -520,11 +520,11 @@ namespace Robust.Client.Debugging
|
||||
var matrix1 = xform1.WorldMatrix;
|
||||
var matrix2 = xform2.WorldMatrix;
|
||||
|
||||
var xf1 = new Vector2(matrix1.R0C2, matrix1.R1C2);
|
||||
var xf2 = new Vector2(matrix2.R0C2, matrix2.R1C2);
|
||||
var xf1 = new Vector2(matrix1.M31, matrix1.M32);
|
||||
var xf2 = new Vector2(matrix2.M31, matrix2.M32);
|
||||
|
||||
var p1 = matrix1.Transform(joint.LocalAnchorA);
|
||||
var p2 = matrix2.Transform(joint.LocalAnchorB);
|
||||
var p1 = Vector2.Transform(joint.LocalAnchorA, matrix1);
|
||||
var p2 = Vector2.Transform(joint.LocalAnchorB, matrix2);
|
||||
|
||||
var xfa = new Transform(xf1, xform1.WorldRotation);
|
||||
var xfb = new Transform(xf2, xform2.WorldRotation);
|
||||
|
||||
@@ -612,6 +612,8 @@ namespace Robust.Client
|
||||
{
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
|
||||
}
|
||||
|
||||
_audio.FlushALDisposeQueues();
|
||||
}
|
||||
|
||||
internal static void SetupLogging(
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -134,6 +135,24 @@ namespace Robust.Client.GameObjects
|
||||
EventBus.RaiseEvent(EventSource.Local, new EntitySessionMessage<T>(eventArgs, msg));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RaiseSharedEvent<T>(T message, EntityUid? user = null)
|
||||
{
|
||||
if (user == null || user != _playerManager.LocalEntity || !_gameTiming.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
EventBus.RaiseEvent(EventSource.Local, ref message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RaiseSharedEvent<T>(T message, ICommonSession? user = null)
|
||||
{
|
||||
if (user == null || user != _playerManager.LocalSession || !_gameTiming.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
EventBus.RaiseEvent(EventSource.Local, ref message);
|
||||
}
|
||||
|
||||
#region IEntityNetworkManager impl
|
||||
|
||||
public override IEntityNetworkManager EntityNetManager => this;
|
||||
|
||||
@@ -6,13 +6,12 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.ComponentTrees;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -30,6 +29,7 @@ using static Robust.Client.ComponentTrees.SpriteTreeSystem;
|
||||
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;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -150,7 +150,7 @@ namespace Robust.Client.GameObjects
|
||||
[DataField("color")]
|
||||
private Color color = Color.White;
|
||||
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
public Matrix3x2 LocalMatrix = Matrix3x2.Identity;
|
||||
|
||||
[Animatable]
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -389,10 +389,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void UpdateLocalMatrix()
|
||||
{
|
||||
LocalMatrix = Matrix3.CreateTransform(in offset, in rotation, in scale);
|
||||
LocalMatrix = Matrix3Helpers.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrix()
|
||||
public Matrix3x2 GetLocalMatrix()
|
||||
{
|
||||
return LocalMatrix;
|
||||
}
|
||||
@@ -772,15 +772,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
foreach (var keyString in layerDatum.MapKeys)
|
||||
{
|
||||
object key;
|
||||
if (reflection.TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
var key = ParseKey(keyString);
|
||||
|
||||
if (LayerMap.TryGetValue(key, out var mappedIndex))
|
||||
{
|
||||
@@ -806,9 +798,30 @@ namespace Robust.Client.GameObjects
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = layerDatum.Visible ?? layer.Visible;
|
||||
|
||||
if (layerDatum.CopyToShaderParameters is { } copyParameters)
|
||||
{
|
||||
layer.CopyToShaderParameters = new CopyToShaderParameters(ParseKey(copyParameters.LayerKey))
|
||||
{
|
||||
ParameterTexture = copyParameters.ParameterTexture,
|
||||
ParameterUV = copyParameters.ParameterUV
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.CopyToShaderParameters = null;
|
||||
}
|
||||
|
||||
RebuildBounds();
|
||||
}
|
||||
|
||||
private object ParseKey(string keyString)
|
||||
{
|
||||
if (reflection.TryParseEnumReference(keyString, out var @enum))
|
||||
return @enum;
|
||||
|
||||
return keyString;
|
||||
}
|
||||
|
||||
public void LayerSetData(object layerKey, PrototypeLayerData data)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer, true))
|
||||
@@ -1237,9 +1250,9 @@ namespace Robust.Client.GameObjects
|
||||
public IEnumerable<ISpriteLayer> AllLayers => Layers;
|
||||
|
||||
// Lobby SpriteView rendering path
|
||||
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null)
|
||||
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default)
|
||||
{
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection);
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection);
|
||||
}
|
||||
|
||||
[DataField("noRot")] private bool _screenLock = false;
|
||||
@@ -1291,22 +1304,21 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero.
|
||||
// However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now:
|
||||
var entityMatrix = Matrix3.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation - cardinal);
|
||||
var entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation - cardinal);
|
||||
|
||||
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformSprite);
|
||||
var transformSprite = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
|
||||
|
||||
if (GranularLayersRendering)
|
||||
{
|
||||
//Default rendering
|
||||
entityMatrix = Matrix3.CreateTransform(worldPosition, worldRotation);
|
||||
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformDefault);
|
||||
entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation);
|
||||
var transformDefault = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
|
||||
//Snap to cardinals
|
||||
entityMatrix = Matrix3.CreateTransform(worldPosition, worldRotation - angle.GetCardinalDir().ToAngle());
|
||||
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformSnap);
|
||||
entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, worldRotation - angle.GetCardinalDir().ToAngle());
|
||||
var transformSnap = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
|
||||
//No rotation
|
||||
entityMatrix = Matrix3.CreateTransform(worldPosition, -eyeRotation);
|
||||
Matrix3.Multiply(in LocalMatrix, in entityMatrix, out var transformNoRot);
|
||||
|
||||
entityMatrix = Matrix3Helpers.CreateTransform(worldPosition, -eyeRotation);
|
||||
var transformNoRot = Matrix3x2.Multiply(LocalMatrix, entityMatrix);
|
||||
|
||||
foreach (var layer in Layers) {
|
||||
switch (layer.RenderingStrategy)
|
||||
@@ -1533,7 +1545,7 @@ namespace Robust.Client.GameObjects
|
||||
private RSI.State? _actualState;
|
||||
[ViewVariables] public RSI.State? ActualState => _actualState;
|
||||
|
||||
public Matrix3 LocalMatrix = Matrix3.Identity;
|
||||
public Matrix3x2 LocalMatrix = Matrix3x2.Identity;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Scale
|
||||
@@ -1637,6 +1649,9 @@ namespace Robust.Client.GameObjects
|
||||
[ViewVariables]
|
||||
public LayerRenderingStrategy RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public CopyToShaderParameters? CopyToShaderParameters;
|
||||
|
||||
public Layer(SpriteComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
@@ -1665,6 +1680,8 @@ namespace Robust.Client.GameObjects
|
||||
DirOffset = toClone.DirOffset;
|
||||
_autoAnimated = toClone._autoAnimated;
|
||||
RenderingStrategy = toClone.RenderingStrategy;
|
||||
if (toClone.CopyToShaderParameters is { } copyToShaderParameters)
|
||||
CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters);
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
@@ -1674,7 +1691,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void UpdateLocalMatrix()
|
||||
{
|
||||
LocalMatrix = Matrix3.CreateTransform(in _offset, in _rotation, in _scale);
|
||||
LocalMatrix = Matrix3Helpers.CreateTransform(in _offset, in _rotation, in _scale);
|
||||
}
|
||||
|
||||
RSI? ISpriteLayer.Rsi { get => RSI; set => SetRsi(value); }
|
||||
@@ -1946,27 +1963,27 @@ namespace Robust.Client.GameObjects
|
||||
/// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing &
|
||||
/// relevant RSI direction.
|
||||
/// </summary>
|
||||
public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3 layerDrawMatrix)
|
||||
public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3x2 layerDrawMatrix)
|
||||
{
|
||||
if (_parent.NoRotation || dir == RsiDirection.South)
|
||||
layerDrawMatrix = LocalMatrix;
|
||||
else
|
||||
{
|
||||
Matrix3.Multiply(in _rsiDirectionMatrices[(int)dir], in LocalMatrix, out layerDrawMatrix);
|
||||
layerDrawMatrix = Matrix3x2.Multiply(_rsiDirectionMatrices[(int)dir], LocalMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
private static Matrix3[] _rsiDirectionMatrices = new Matrix3[]
|
||||
private static Matrix3x2[] _rsiDirectionMatrices = new Matrix3x2[]
|
||||
{
|
||||
// array order chosen such that this array can be indexed by casing an RSI direction to an int
|
||||
Matrix3.Identity, // should probably just avoid matrix multiplication altogether if the direction is south.
|
||||
Matrix3.CreateRotation(-Direction.North.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.East.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.West.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.SouthEast.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.SouthWest.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.NorthEast.ToAngle()),
|
||||
Matrix3.CreateRotation(-Direction.NorthWest.ToAngle())
|
||||
Matrix3x2.Identity, // should probably just avoid matrix multiplication altogether if the direction is south.
|
||||
Matrix3Helpers.CreateRotation(-Direction.North.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.East.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.West.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.SouthEast.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.SouthWest.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.NorthEast.ToAngle()),
|
||||
Matrix3Helpers.CreateRotation(-Direction.NorthWest.ToAngle())
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -2000,7 +2017,7 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Render a layer. This assumes that the input angle is between 0 and 2pi.
|
||||
/// </summary>
|
||||
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, Angle angle, Direction? overrideDirection)
|
||||
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3x2 spriteMatrix, Angle angle, Direction? overrideDirection)
|
||||
{
|
||||
if (!Visible || Blank)
|
||||
return;
|
||||
@@ -2009,8 +2026,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// Set the drawing transform for this layer
|
||||
GetLayerDrawMatrix(dir, out var layerMatrix);
|
||||
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
// The direction used to draw the sprite can differ from the one that the angle would naively suggest,
|
||||
// due to direction overrides or offsets.
|
||||
@@ -2020,7 +2035,41 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// Get the correct directional texture from the state, and draw it!
|
||||
var texture = GetRenderTexture(_actualState, dir);
|
||||
RenderTexture(drawingHandle, texture);
|
||||
|
||||
if (CopyToShaderParameters == null)
|
||||
{
|
||||
// Set the drawing transform for this layer
|
||||
var transformMatrix = Matrix3x2.Multiply(layerMatrix, spriteMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
RenderTexture(drawingHandle, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multiple atrocities to god being committed right here.
|
||||
var otherLayerIdx = _parent.LayerMap[CopyToShaderParameters.LayerKey!];
|
||||
var otherLayer = _parent.Layers[otherLayerIdx];
|
||||
if (otherLayer.Shader is not { } shader)
|
||||
{
|
||||
// No shader set apparently..?
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shader.Mutable)
|
||||
otherLayer.Shader = shader = shader.Duplicate();
|
||||
|
||||
var clydeTexture = Clyde.RenderHandle.ExtractTexture(texture, null, out var csr);
|
||||
var sr = Clyde.RenderHandle.WorldTextureBoundsToUV(clydeTexture, csr);
|
||||
|
||||
if (CopyToShaderParameters.ParameterTexture is { } paramTexture)
|
||||
shader.SetParameter(paramTexture, clydeTexture);
|
||||
|
||||
if (CopyToShaderParameters.ParameterUV is { } paramUV)
|
||||
{
|
||||
var uv = new Vector4(sr.Left, sr.Bottom, sr.Right, sr.Top);
|
||||
shader.SetParameter(paramUV, uv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture)
|
||||
@@ -2098,6 +2147,23 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiated version of <see cref="PrototypeCopyToShaderParameters"/>.
|
||||
/// Has <see cref="LayerKey"/> actually resolved to a a real key.
|
||||
/// </summary>
|
||||
public sealed class CopyToShaderParameters(object layerKey)
|
||||
{
|
||||
public object LayerKey = layerKey;
|
||||
public string? ParameterTexture;
|
||||
public string? ParameterUV;
|
||||
|
||||
public CopyToShaderParameters(CopyToShaderParameters toClone) : this(toClone.LayerKey)
|
||||
{
|
||||
ParameterTexture = toClone.ParameterTexture;
|
||||
ParameterUV = toClone.ParameterUV;
|
||||
}
|
||||
}
|
||||
|
||||
void IAnimationProperties.SetAnimatableProperty(string name, object value)
|
||||
{
|
||||
if (!name.StartsWith("layer/"))
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace Robust.Client.GameObjects
|
||||
toDelete.Add(id);
|
||||
}
|
||||
|
||||
foreach (var dead in toDelete)
|
||||
foreach (var dead in toDelete.Span)
|
||||
{
|
||||
component.Containers.Remove(dead);
|
||||
}
|
||||
@@ -120,7 +120,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
|
||||
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
|
||||
InitContainer(container, (uid, component), id);
|
||||
container.Init(this, id, (uid, component));
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Robust.Client.GameObjects
|
||||
toRemove.Add(entity);
|
||||
}
|
||||
|
||||
foreach (var entity in toRemove)
|
||||
foreach (var entity in toRemove.Span)
|
||||
{
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
@@ -162,7 +162,7 @@ namespace Robust.Client.GameObjects
|
||||
removedExpected.Add(netEntity);
|
||||
}
|
||||
|
||||
foreach (var entityUid in removedExpected)
|
||||
foreach (var entityUid in removedExpected.Span)
|
||||
{
|
||||
RemoveExpectedEntity(entityUid, out _);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -118,7 +119,7 @@ public sealed class EntityLookupOverlay : Overlay
|
||||
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
|
||||
var (entPos, entRot) = _transform.GetWorldPositionRotation(ent);
|
||||
|
||||
var lookupPos = invMatrix.Transform(entPos);
|
||||
var lookupPos = Vector2.Transform(entPos, invMatrix);
|
||||
var lookupRot = entRot - rotation;
|
||||
|
||||
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, _xformQuery);
|
||||
@@ -127,6 +128,6 @@ public sealed class EntityLookupOverlay : Overlay
|
||||
}
|
||||
});
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var map = _eyeManager.CurrentMap;
|
||||
var map = args.MapId;
|
||||
if (map == MapId.Nullspace) return;
|
||||
|
||||
foreach (var (_, treeComp) in _trees.GetIntersectingTrees(map, args.WorldBounds))
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var currentMap = _eyeManager.CurrentMap;
|
||||
var currentMap = args.MapId;
|
||||
var viewport = args.WorldBounds;
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,14 @@ 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!;
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private ISawmill _sawmillInputContext = default!;
|
||||
|
||||
@@ -81,18 +83,35 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
// send it off to the server
|
||||
var clientMsg = (ClientFullInputCmdMessage)message;
|
||||
var fullMsg = new FullInputCmdMessage(
|
||||
clientMsg.Tick,
|
||||
clientMsg.SubTick,
|
||||
(int)clientMsg.InputSequence,
|
||||
clientMsg.InputFunctionId,
|
||||
clientMsg.State,
|
||||
GetNetCoordinates(clientMsg.Coordinates),
|
||||
clientMsg.ScreenCoordinates)
|
||||
var clientMsg = message switch
|
||||
{
|
||||
Uid = GetNetEntity(clientMsg.Uid)
|
||||
ClientFullInputCmdMessage clientInput => clientInput,
|
||||
FullInputCmdMessage fullInput => new ClientFullInputCmdMessage(
|
||||
fullInput.Tick,
|
||||
fullInput.SubTick,
|
||||
fullInput.InputFunctionId,
|
||||
GetCoordinates(fullInput.Coordinates),
|
||||
fullInput.ScreenCoordinates,
|
||||
fullInput.State,
|
||||
GetEntity(fullInput.Uid)),
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
var fullMsg = message switch
|
||||
{
|
||||
FullInputCmdMessage fullInput => fullInput,
|
||||
ClientFullInputCmdMessage client => new FullInputCmdMessage(
|
||||
client.Tick,
|
||||
client.SubTick,
|
||||
client.InputFunctionId,
|
||||
clientMsg.State,
|
||||
GetNetCoordinates(client.Coordinates),
|
||||
clientMsg.ScreenCoordinates,
|
||||
GetNetEntity(clientMsg.Uid)
|
||||
),
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
DispatchInputCommand(clientMsg, fullMsg);
|
||||
@@ -130,7 +149,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
_conHost.RegisterCommand("incmd",
|
||||
"Inserts an input command into the simulation",
|
||||
"incmd <KeyFunction> <d|u KeyState> <wxPos> <wyPos>",
|
||||
"incmd <KeyFunction> <KeyState> [wxPos] [wyPos]",
|
||||
GenerateInputCommand);
|
||||
}
|
||||
|
||||
@@ -146,17 +165,47 @@ namespace Robust.Client.GameObjects
|
||||
if (_playerManager.LocalEntity is not { } pent)
|
||||
return;
|
||||
|
||||
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
|
||||
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
|
||||
if (args.Length is not (2 or 4))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString($"cmd-invalid-arg-number-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
var pxform = Transform(pent);
|
||||
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent, new MapCoordinates(wPos, pxform.MapID));
|
||||
var keyFunction = new BoundKeyFunction(args[0]);
|
||||
if (!Enum.TryParse<BoundKeyState>(args[1], out var state))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-parse-failure-enum", ("arg", args[1]), ("enum", nameof(BoundKeyState))));
|
||||
return;
|
||||
}
|
||||
|
||||
var wOffset = Vector2.Zero;
|
||||
if (args.Length == 4)
|
||||
{
|
||||
if (!float.TryParse(args[2], out var wX))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[2])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!float.TryParse(args[3], out var wY))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-float", ("arg", args[3])));
|
||||
return;
|
||||
}
|
||||
|
||||
wOffset = new Vector2(wX, wY);
|
||||
}
|
||||
|
||||
var coords = EntityCoordinates.FromMap(pent, _transform.GetMapCoordinates(pent).Offset(wOffset), _transform, EntityManager);
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
|
||||
|
||||
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
|
||||
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
|
||||
var message = new ClientFullInputCmdMessage(_timing.CurTick,
|
||||
_timing.TickFraction,
|
||||
funcId,
|
||||
coords,
|
||||
new ScreenCoordinates(0, 0, default),
|
||||
state,
|
||||
EntityUid.Invalid);
|
||||
|
||||
HandleInputCommand(_playerManager.LocalSession, keyFunction, message);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
@@ -16,6 +13,17 @@ public sealed class MapSystem : SharedMapSystem
|
||||
[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))
|
||||
{
|
||||
id = new MapId(--LastMapId);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -27,9 +35,4 @@ public sealed class MapSystem : SharedMapSystem
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,8 @@
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using System;
|
||||
using UserInterfaceComponent = Robust.Shared.GameObjects.UserInterfaceComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
|
||||
}
|
||||
|
||||
private void MessageReceived(BoundUIWrapMessage ev)
|
||||
{
|
||||
var uid = GetEntity(ev.Entity);
|
||||
|
||||
if (!TryComp<UserInterfaceComponent>(uid, out var cmp))
|
||||
return;
|
||||
|
||||
var uiKey = ev.UiKey;
|
||||
var message = ev.Message;
|
||||
message.Session = _playerManager.LocalSession!;
|
||||
message.Entity = GetNetEntity(uid);
|
||||
message.UiKey = uiKey;
|
||||
|
||||
// Raise as object so the correct type is used.
|
||||
RaiseLocalEvent(uid, (object)message, true);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case OpenBoundInterfaceMessage _:
|
||||
TryOpenUi(uid, uiKey, cmp);
|
||||
break;
|
||||
|
||||
case CloseBoundInterfaceMessage _:
|
||||
TryCloseUi(message.Session, uid, uiKey, remoteCall: true, uiComp: cmp);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (cmp.OpenInterfaces.TryGetValue(uiKey, out var bui))
|
||||
bui.InternalReceiveMessage(message);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryOpenUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? uiComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref uiComp))
|
||||
return false;
|
||||
|
||||
if (uiComp.OpenInterfaces.ContainsKey(uiKey))
|
||||
return false;
|
||||
|
||||
var data = uiComp.MappedInterfaceData[uiKey];
|
||||
|
||||
// TODO: This type should be cached, but I'm too lazy.
|
||||
var type = _reflectionManager.LooseGetType(data.ClientType);
|
||||
var boundInterface =
|
||||
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new object[] {uid, uiKey});
|
||||
|
||||
boundInterface.Open();
|
||||
uiComp.OpenInterfaces[uiKey] = boundInterface;
|
||||
|
||||
if (_playerManager.LocalSession is { } playerSession)
|
||||
{
|
||||
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
|
||||
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,11 @@ namespace Robust.Client.GameStates
|
||||
private readonly HashSet<NetEntity> _stateEnts = new();
|
||||
private readonly List<EntityUid> _toDelete = new();
|
||||
private readonly List<IComponent> _toRemove = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _outputData = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _outputData = new();
|
||||
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
|
||||
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState>>(new DictPolicy<ushort, IComponentState>(), 256);
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState?>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState?>>(new DictPolicy<ushort, IComponentState?>(), 256);
|
||||
|
||||
private uint _metaCompNetId;
|
||||
|
||||
@@ -125,6 +125,8 @@ namespace Robust.Client.GameStates
|
||||
#endif
|
||||
|
||||
private bool _resettingPredictedEntities;
|
||||
private readonly List<EntityUid> _brokenEnts = new();
|
||||
private readonly List<(EntityUid, NetEntity)> _toStart = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
@@ -257,7 +259,7 @@ namespace Robust.Client.GameStates
|
||||
public void UpdateFullRep(GameState state, bool cloneDelta = false)
|
||||
=> _processor.UpdateFullRep(state, cloneDelta);
|
||||
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
|
||||
=> _processor.GetFullRep();
|
||||
|
||||
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
|
||||
@@ -598,8 +600,12 @@ namespace Robust.Client.GameStates
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
|
||||
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
if (compState != null)
|
||||
{
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
}
|
||||
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
}
|
||||
@@ -631,8 +637,12 @@ namespace Robust.Client.GameStates
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was removed: {comp.GetType()}");
|
||||
|
||||
var stateEv = new ComponentHandleState(state, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
|
||||
if (state != null)
|
||||
{
|
||||
var stateEv = new ComponentHandleState(state, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
|
||||
}
|
||||
|
||||
comp.ClearCreationTick(); // don't undo the re-adding.
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
@@ -667,7 +677,16 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var netEntity in createdEntities)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta))
|
||||
{
|
||||
_sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}");
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
var (_, meta) = _entityManager.GetEntityData(netEntity);
|
||||
#endif
|
||||
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
|
||||
@@ -676,7 +695,7 @@ namespace Robust.Client.GameStates
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
compData.Add(netId, state);
|
||||
}
|
||||
}
|
||||
@@ -700,7 +719,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
using var _ = _timing.StartStateApplicationArea();
|
||||
|
||||
// TODO repays optimize this.
|
||||
// TODO replays optimize this.
|
||||
// This currently just saves game states as they are applied.
|
||||
// However this is inefficient and may have redundant data.
|
||||
// E.g., we may record states: [10 to 15] [11 to 16] *error* [0 to 18] [18 to 19] [18 to 20] ...
|
||||
@@ -876,9 +895,22 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
foreach (var (entity, data) in _toApply)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
|
||||
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
|
||||
|
||||
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;
|
||||
|
||||
@@ -917,7 +949,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessDeletions(delSpan, xforms, xformSys);
|
||||
ProcessDeletions(delSpan, xforms, metas, xformSys);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -962,6 +994,7 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
var xforms = _entities.GetEntityQuery<TransformComponent>();
|
||||
var metas = _entities.GetEntityQuery<MetaDataComponent>();
|
||||
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
|
||||
|
||||
_toDelete.Clear();
|
||||
@@ -990,12 +1023,12 @@ namespace Robust.Client.GameStates
|
||||
|
||||
// This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to
|
||||
// null. First we will detach the parent in order to reduce the number of broadphase/lookup updates.
|
||||
xformSys.DetachParentToNull(ent, xform);
|
||||
xformSys.DetachEntity(ent, xform);
|
||||
|
||||
// Then detach all children.
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
|
||||
|
||||
if (deleteClientChildren
|
||||
&& !deleteClientEntities // don't add duplicates
|
||||
@@ -1014,9 +1047,9 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDeletions(
|
||||
ReadOnlySpan<NetEntity> delSpan,
|
||||
private void ProcessDeletions(ReadOnlySpan<NetEntity> delSpan,
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
SharedTransformSystem xformSys)
|
||||
{
|
||||
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
|
||||
@@ -1043,13 +1076,13 @@ namespace Robust.Client.GameStates
|
||||
continue; // Already deleted? or never sent to us?
|
||||
|
||||
// First, a single recursive map change
|
||||
xformSys.DetachParentToNull(id.Value, xform);
|
||||
xformSys.DetachEntity(id.Value, xform);
|
||||
|
||||
// Then detach all children.
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
|
||||
}
|
||||
|
||||
// Finally, delete the entity.
|
||||
@@ -1120,7 +1153,7 @@ namespace Robust.Client.GameStates
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((meta.Flags & MetaDataFlags.Detached) != 0)
|
||||
if ((meta.Flags & (MetaDataFlags.Detached | MetaDataFlags.Undetachable)) != 0)
|
||||
continue;
|
||||
|
||||
if (lastStateApplied.HasValue)
|
||||
@@ -1138,13 +1171,13 @@ namespace Robust.Client.GameStates
|
||||
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
|
||||
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
|
||||
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container))
|
||||
{
|
||||
containerSys.Remove((ent.Value, xform, meta), container, false, true);
|
||||
}
|
||||
|
||||
meta._flags |= MetaDataFlags.Detached;
|
||||
xformSys.DetachParentToNull(ent.Value, xform);
|
||||
xformSys.DetachEntity(ent.Value, xform);
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
|
||||
|
||||
if (container != null)
|
||||
@@ -1157,63 +1190,58 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
|
||||
{
|
||||
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
|
||||
_toStart.Clear();
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
var brokenEnts = new List<EntityUid>();
|
||||
#endif
|
||||
using (_prof.Group("Initialize Entity"))
|
||||
{
|
||||
EntityUid entity = default;
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
{
|
||||
var entity = _entityManager.GetEntity(netEntity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
_entities.InitializeEntity(entity, metaQuery.GetComponent(entity));
|
||||
#if EXCEPTION_TOLERANCE
|
||||
(entity, var meta) = _entityManager.GetEntityData(netEntity);
|
||||
_entities.InitializeEntity(entity, meta);
|
||||
_toStart.Add((entity, netEntity));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}");
|
||||
_sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
brokenEnts.Add(entity);
|
||||
toCreate.Remove(netEntity);
|
||||
}
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (_prof.Group("Start Entity"))
|
||||
{
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
foreach (var (entity, netEntity) in _toStart)
|
||||
{
|
||||
var entity = _entityManager.GetEntity(netEntity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
_entities.StartEntity(entity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
_entities.StartEntity(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}");
|
||||
_sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
brokenEnts.Add(entity);
|
||||
toCreate.Remove(netEntity);
|
||||
}
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
foreach (var entity in brokenEnts)
|
||||
foreach (var entity in _brokenEnts)
|
||||
{
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
#endif
|
||||
_brokenEnts.Clear();
|
||||
}
|
||||
|
||||
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
|
||||
@@ -1329,23 +1357,11 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (comp, cur, next) in _compStateWork.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(comp, ref handleState);
|
||||
}
|
||||
#pragma warning disable CS0168 // Variable is declared but never used
|
||||
catch (Exception e)
|
||||
#pragma warning restore CS0168 // Variable is declared but never used
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
|
||||
#else
|
||||
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
if (cur == null && next == null)
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(comp, ref handleState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1414,10 +1430,10 @@ namespace Robust.Client.GameStates
|
||||
_entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) &&
|
||||
(containerMeta.Flags & MetaDataFlags.Detached) == 0)
|
||||
{
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container, null, true);
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container);
|
||||
}
|
||||
|
||||
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
|
||||
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachEntity(uid, xform);
|
||||
|
||||
if (container != null)
|
||||
containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container);
|
||||
@@ -1496,6 +1512,9 @@ namespace Robust.Client.GameStates
|
||||
_entityManager.AddComponent(uid, comp, true, meta);
|
||||
}
|
||||
|
||||
if (state == null)
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(state, null);
|
||||
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Client.GameStates
|
||||
/// <summary>
|
||||
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
|
||||
/// </summary>
|
||||
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _lastStateFullRep
|
||||
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _lastStateFullRep
|
||||
= new();
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -212,7 +212,7 @@ Had full state: {LastFullState != null}"
|
||||
{
|
||||
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
|
||||
{
|
||||
compData = new Dictionary<ushort, IComponentState>();
|
||||
compData = new();
|
||||
_lastStateFullRep.Add(entityState.NetEntity, compData);
|
||||
}
|
||||
|
||||
@@ -221,21 +221,20 @@ Had full state: {LastFullState != null}"
|
||||
var compState = change.State;
|
||||
|
||||
if (compState is IComponentDeltaState delta
|
||||
&& !delta.FullState
|
||||
&& compData.TryGetValue(change.NetID, out var old)) // May fail if relying on implicit data
|
||||
{
|
||||
DebugTools.Assert(old is IComponentDeltaState oldDelta && oldDelta.FullState, "last state is not a full state");
|
||||
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
|
||||
|
||||
if (cloneDelta)
|
||||
{
|
||||
compState = delta.CreateNewFullState(old);
|
||||
compState = delta.CreateNewFullState(old!);
|
||||
}
|
||||
else
|
||||
{
|
||||
delta.ApplyToFullState(old);
|
||||
delta.ApplyToFullState(old!);
|
||||
compState = old;
|
||||
}
|
||||
DebugTools.Assert(compState is IComponentDeltaState newState && newState.FullState, "newly constructed state is not a full state");
|
||||
DebugTools.Assert(compState is not IComponentDeltaState, "newly constructed state is not a full state");
|
||||
}
|
||||
|
||||
compData[change.NetID] = compState;
|
||||
@@ -391,7 +390,7 @@ Had full state: {LastFullState != null}"
|
||||
LastFullStateRequested = null;
|
||||
}
|
||||
|
||||
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> implicitData)
|
||||
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> implicitData)
|
||||
{
|
||||
foreach (var (netEntity, implicitEntState) in implicitData)
|
||||
{
|
||||
@@ -399,6 +398,7 @@ Had full state: {LastFullState != null}"
|
||||
|
||||
foreach (var (netId, implicitCompState) in implicitEntState)
|
||||
{
|
||||
DebugTools.Assert(implicitCompState is not IComponentDeltaState);
|
||||
ref var serverState = ref CollectionsMarshal.GetValueRefOrAddDefault(fullRep, netId, out var exists);
|
||||
|
||||
if (!exists)
|
||||
@@ -407,36 +407,32 @@ Had full state: {LastFullState != null}"
|
||||
continue;
|
||||
}
|
||||
|
||||
if (serverState is not IComponentDeltaState serverDelta || serverDelta.FullState)
|
||||
if (serverState is not IComponentDeltaState serverDelta)
|
||||
continue;
|
||||
|
||||
DebugTools.AssertNotNull(implicitCompState);
|
||||
|
||||
// Server sent an initial delta state. This is fine as long as the client can infer an initial full
|
||||
// state from the entity prototype.
|
||||
if (implicitCompState is not IComponentDeltaState implicitDelta || !implicitDelta.FullState)
|
||||
{
|
||||
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {netEntity}");
|
||||
continue;
|
||||
}
|
||||
|
||||
serverDelta.ApplyToFullState(implicitCompState);
|
||||
serverDelta.ApplyToFullState(implicitCompState!);
|
||||
serverState = implicitCompState;
|
||||
DebugTools.Assert(implicitCompState is IComponentDeltaState d && d.FullState);
|
||||
DebugTools.Assert(serverState is not IComponentDeltaState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity netEntity)
|
||||
public Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity netEntity)
|
||||
{
|
||||
return _lastStateFullRep[netEntity];
|
||||
}
|
||||
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
|
||||
{
|
||||
return _lastStateFullRep;
|
||||
}
|
||||
|
||||
public bool TryGetLastServerStates(NetEntity entity,
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary)
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary)
|
||||
{
|
||||
return _lastStateFullRep.TryGetValue(entity, out dictionary);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace Robust.Client.GameStates
|
||||
/// <summary>
|
||||
/// Returns the full collection of cached game states that are used to reset predicted entities.
|
||||
/// </summary>
|
||||
Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep();
|
||||
Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep();
|
||||
|
||||
/// <summary>
|
||||
/// This will perform some setup in order to reset the game to an earlier state. To fully reset the state
|
||||
|
||||
@@ -83,13 +83,13 @@ namespace Robust.Client.GameStates
|
||||
/// The data to merge.
|
||||
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
|
||||
/// </param>
|
||||
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> data);
|
||||
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> data);
|
||||
|
||||
/// <summary>
|
||||
/// Get the last state data from the server for an entity.
|
||||
/// </summary>
|
||||
/// <returns>Dictionary (net ID -> ComponentState)</returns>
|
||||
Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity entity);
|
||||
Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the number of applicable states in the game state buffer from a given tick.
|
||||
@@ -99,6 +99,6 @@ namespace Robust.Client.GameStates
|
||||
int GetApplicableStateCount(GameTick? fromTick);
|
||||
|
||||
bool TryGetLastServerStates(NetEntity entity,
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary);
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Robust.Client.GameStates
|
||||
while (query.MoveNext(out var uid, out var transform))
|
||||
{
|
||||
// if not on the same map, continue
|
||||
if (transform.MapID != _eyeManager.CurrentMap || _container.IsEntityInContainer(uid))
|
||||
if (transform.MapID != args.MapId || _container.IsEntityInContainer(uid))
|
||||
continue;
|
||||
|
||||
if (transform.GridUid == uid)
|
||||
|
||||
@@ -64,29 +64,22 @@ namespace Robust.Client.Graphics
|
||||
/// <inheritdoc />
|
||||
public Box2 GetWorldViewport()
|
||||
{
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
|
||||
var topLeft = ScreenToMap(Vector2.Zero);
|
||||
var topRight = ScreenToMap(new Vector2(vpSize.X, 0));
|
||||
var bottomRight = ScreenToMap(vpSize);
|
||||
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y));
|
||||
|
||||
var left = MathHelper.Min(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
|
||||
var bottom = MathHelper.Min(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
|
||||
var right = MathHelper.Max(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
|
||||
var top = MathHelper.Max(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
|
||||
|
||||
return new Box2(left, bottom, right, top);
|
||||
return GetWorldViewbounds().CalcBoundingBox();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2Rotated GetWorldViewbounds()
|
||||
{
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
// This is an inefficient and roundabout way of geting the viewport.
|
||||
// But its a method that shouldn't get used much.
|
||||
|
||||
var vp = MainViewport as Control;
|
||||
var vpSize = vp?.PixelSize ?? _displayManager.ScreenSize;
|
||||
|
||||
var topRight = ScreenToMap(new Vector2(vpSize.X, 0)).Position;
|
||||
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y)).Position;
|
||||
|
||||
// This assumes the main viewports eye and the main eye are the same.
|
||||
var rotation = new Angle(CurrentEye.Rotation);
|
||||
var center = (bottomLeft + topRight) / 2;
|
||||
|
||||
@@ -108,18 +101,16 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void GetScreenProjectionMatrix(out Matrix3 projMatrix)
|
||||
public void GetScreenProjectionMatrix(out Matrix3x2 projMatrix)
|
||||
{
|
||||
Matrix3 result = default;
|
||||
Matrix3x2 result = default;
|
||||
|
||||
result.R0C0 = PixelsPerMeter;
|
||||
result.R1C1 = -PixelsPerMeter;
|
||||
result.M11 = PixelsPerMeter;
|
||||
result.M22 = -PixelsPerMeter;
|
||||
|
||||
var screenSize = _displayManager.ScreenSize;
|
||||
result.R0C2 = screenSize.X / 2f;
|
||||
result.R1C2 = screenSize.Y / 2f;
|
||||
|
||||
result.R2C2 = 1;
|
||||
result.M31 = screenSize.X / 2f;
|
||||
result.M32 = screenSize.Y / 2f;
|
||||
|
||||
/* column major
|
||||
Sx 0 Tx
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
@@ -13,26 +14,29 @@ namespace Robust.Client.Graphics
|
||||
public interface IEyeManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The current eye that is being used to render the game.
|
||||
/// The primary eye, which is usually the eye associated with the main viewport.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally, you should avoid using this whenever possible. E.g., when rendering overlays should use the
|
||||
/// eye & viewbounds that gets passed to the draw method.
|
||||
/// Setting this property to null will use the default eye.
|
||||
/// </remarks>
|
||||
IEye CurrentEye { get; set; }
|
||||
|
||||
IViewportControl MainViewport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the map on which the current eye is "placed".
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
MapId CurrentMap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A world-space box that is at LEAST the area covered by the viewport.
|
||||
/// A world-space box that is at LEAST the area covered by the main viewport.
|
||||
/// May be larger due to say rotation.
|
||||
/// </summary>
|
||||
Box2 GetWorldViewport();
|
||||
|
||||
/// <summary>
|
||||
/// A world-space box of the area visible in the main viewport.
|
||||
/// </summary>
|
||||
Box2Rotated GetWorldViewbounds();
|
||||
|
||||
/// <summary>
|
||||
@@ -40,10 +44,10 @@ namespace Robust.Client.Graphics
|
||||
/// to UI screen space.
|
||||
/// </summary>
|
||||
/// <param name="projMatrix"></param>
|
||||
void GetScreenProjectionMatrix(out Matrix3 projMatrix);
|
||||
void GetScreenProjectionMatrix(out Matrix3x2 projMatrix);
|
||||
|
||||
/// <summary>
|
||||
/// Projects a point from world space to UI screen space using the current camera.
|
||||
/// Projects a point from world space to UI screen space using the main viewport.
|
||||
/// </summary>
|
||||
/// <param name="point">Point in world to transform.</param>
|
||||
/// <returns>Corresponding point in UI screen space.</returns>
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
("aPos", 0),
|
||||
("tCoord", 1),
|
||||
("modulate", 2)
|
||||
("tCoord2", 2),
|
||||
("modulate", 3)
|
||||
};
|
||||
|
||||
private const int UniIModUV = 0;
|
||||
|
||||
@@ -124,7 +124,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
foreach (var (grid, chunks) in _mapChunkData)
|
||||
{
|
||||
var gridComp = _mapManager.GetGridComp(grid);
|
||||
var gridComp = _entityManager.GetComponent<MapGridComponent>(grid);
|
||||
foreach (var (index, chunk) in chunks)
|
||||
{
|
||||
if (!chunk.Dirty || gridComp.Chunks.ContainsKey(index))
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -250,10 +251,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
|
||||
{
|
||||
var mapId = eye.Position.MapId;
|
||||
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
|
||||
{
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
}
|
||||
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
|
||||
var worldOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceEntities);
|
||||
@@ -368,7 +367,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.UseShader(entry.Sprite.PostShader);
|
||||
CalcScreenMatrices(viewport.Size, out var proj, out var view);
|
||||
_renderHandle.SetProjView(proj, view);
|
||||
_renderHandle.SetModelTransform(Matrix3.Identity);
|
||||
_renderHandle.SetModelTransform(Matrix3x2.Identity);
|
||||
|
||||
var rounded = roundedPos - entityPostRenderTarget.Size / 2;
|
||||
|
||||
@@ -481,7 +480,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var worldBounds = CalcWorldBounds(viewport);
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
|
||||
if (_eyeManager.CurrentMap != MapId.Nullspace)
|
||||
if (eye.Position.MapId != MapId.Nullspace)
|
||||
{
|
||||
using (DebugGroup("Lights"))
|
||||
using (_prof.Group("Lights"))
|
||||
@@ -514,7 +513,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
|
||||
{
|
||||
ApplyFovToBuffer(viewport, eye);
|
||||
var mapUid = _mapManager.GetMapEntityId(eye.Position.MapId);
|
||||
if (_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
|
||||
ApplyFovToBuffer(viewport, eye);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,7 +530,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Because the math is wrong.
|
||||
// So there are distortions from incorrect projection.
|
||||
_renderHandle.UseShader(_fovDebugShaderInstance);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3x2.Identity);
|
||||
var pos = UIBox2.FromDimensions(viewport.Size / 2 - new Vector2(200, 200), new Vector2(400, 400));
|
||||
_renderHandle.DrawingHandleScreen.DrawTextureRect(FovTexture, pos);
|
||||
}
|
||||
@@ -537,15 +538,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (DebugLayers == ClydeDebugLayers.Light)
|
||||
{
|
||||
_renderHandle.UseShader(null);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3.Identity);
|
||||
_renderHandle.DrawingHandleScreen.SetTransform(Matrix3x2.Identity);
|
||||
_renderHandle.DrawingHandleScreen.DrawTextureRect(
|
||||
viewport.WallBleedIntermediateRenderTarget2.Texture,
|
||||
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));
|
||||
}
|
||||
|
||||
using (_prof.Group("Overlays WS"))
|
||||
if (eye.Position.MapId != MapId.Nullspace)
|
||||
{
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
|
||||
using (_prof.Group("Overlays WS"))
|
||||
{
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
|
||||
}
|
||||
}
|
||||
|
||||
_currentViewport = oldVp;
|
||||
|
||||
@@ -23,9 +23,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
// Colour Modulation.
|
||||
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
|
||||
// Texture Coords (2).
|
||||
GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(2);
|
||||
// Colour Modulation.
|
||||
GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 6 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(3);
|
||||
}
|
||||
|
||||
// NOTE: This is:
|
||||
@@ -37,6 +40,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
public readonly Vector2 Position;
|
||||
public readonly Vector2 TextureCoordinates;
|
||||
public readonly Vector2 TextureCoordinates2;
|
||||
// Note that this color is in linear space.
|
||||
public readonly Color Modulate;
|
||||
|
||||
@@ -48,6 +52,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Modulate = modulate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(Vector2 position, Vector2 textureCoordinates, Vector2 textureCoordinates2, Color modulate)
|
||||
{
|
||||
Position = position;
|
||||
TextureCoordinates = textureCoordinates;
|
||||
TextureCoordinates2 = textureCoordinates2;
|
||||
Modulate = modulate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(float x, float y, float u, float v, float r, float g, float b, float a)
|
||||
: this(new Vector2(x, y), new Vector2(u, v), new Color(r, g, b, a))
|
||||
@@ -85,28 +98,34 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[FieldOffset(20 * sizeof(float))] public Vector3 ViewMatrixC2;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ProjViewMatrices(in Matrix3 projMatrix, in Matrix3 viewMatrix)
|
||||
public ProjViewMatrices(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix)
|
||||
{
|
||||
ProjMatrixC0 = new Vector3(projMatrix.R0C0, projMatrix.R1C0, projMatrix.R2C0);
|
||||
ProjMatrixC1 = new Vector3(projMatrix.R0C1, projMatrix.R1C1, projMatrix.R2C1);
|
||||
ProjMatrixC2 = new Vector3(projMatrix.R0C2, projMatrix.R1C2, projMatrix.R2C2);
|
||||
// We put the rows of the input matrix into the columns of our GPU matrices
|
||||
// this transpose is required, as in C#, we premultiply vectors with matrices
|
||||
// (vM) while GL postmultiplies vectors with matrices (Mv); however, since
|
||||
// the Matrix3x2 data is stored row-major, and GL uses column-major, the
|
||||
// memory layout is the same (or would be, if Matrix3x2 didn't have an
|
||||
// implicit column)
|
||||
ProjMatrixC0 = new Vector3(projMatrix.M11, projMatrix.M12, 0);
|
||||
ProjMatrixC1 = new Vector3(projMatrix.M21, projMatrix.M22, 0);
|
||||
ProjMatrixC2 = new Vector3(projMatrix.M31, projMatrix.M32, 1);
|
||||
|
||||
ViewMatrixC0 = new Vector3(viewMatrix.R0C0, viewMatrix.R1C0, viewMatrix.R2C0);
|
||||
ViewMatrixC1 = new Vector3(viewMatrix.R0C1, viewMatrix.R1C1, viewMatrix.R2C1);
|
||||
ViewMatrixC2 = new Vector3(viewMatrix.R0C2, viewMatrix.R1C2, viewMatrix.R2C2);
|
||||
ViewMatrixC0 = new Vector3(viewMatrix.M11, viewMatrix.M12, 0);
|
||||
ViewMatrixC1 = new Vector3(viewMatrix.M21, viewMatrix.M22, 0);
|
||||
ViewMatrixC2 = new Vector3(viewMatrix.M31, viewMatrix.M32, 1);
|
||||
}
|
||||
|
||||
public void Apply(Clyde clyde, GLShaderProgram program)
|
||||
{
|
||||
program.SetUniformMaybe("projectionMatrix", new Matrix3(
|
||||
ProjMatrixC0.X, ProjMatrixC1.X, ProjMatrixC2.X,
|
||||
ProjMatrixC0.Y, ProjMatrixC1.Y, ProjMatrixC2.Y,
|
||||
ProjMatrixC0.Z, ProjMatrixC1.Z, ProjMatrixC2.Z
|
||||
program.SetUniformMaybe("projectionMatrix", new Matrix3x2(
|
||||
ProjMatrixC0.X, ProjMatrixC0.Y, // Implicit 0
|
||||
ProjMatrixC1.X, ProjMatrixC1.Y, // Implicit 0
|
||||
ProjMatrixC2.X, ProjMatrixC2.Y // Implicit 1
|
||||
));
|
||||
program.SetUniformMaybe("viewMatrix", new Matrix3(
|
||||
ViewMatrixC0.X, ViewMatrixC1.X, ViewMatrixC2.X,
|
||||
ViewMatrixC0.Y, ViewMatrixC1.Y, ViewMatrixC2.Y,
|
||||
ViewMatrixC0.Z, ViewMatrixC1.Z, ViewMatrixC2.Z
|
||||
program.SetUniformMaybe("viewMatrix", new Matrix3x2(
|
||||
ViewMatrixC0.X, ViewMatrixC0.Y, // Implicit 0
|
||||
ViewMatrixC1.X, ViewMatrixC1.Y, // Implicit 0
|
||||
ViewMatrixC2.X, ViewMatrixC2.Y // Implicit 1
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,18 +492,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var offset = new Vector2(component.Radius, component.Radius);
|
||||
|
||||
Matrix3 matrix;
|
||||
Matrix3x2 matrix;
|
||||
if (mask == null)
|
||||
{
|
||||
matrix = Matrix3.Identity;
|
||||
matrix = Matrix3x2.Identity;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only apply rotation if a mask is said, because else it doesn't matter.
|
||||
matrix = Matrix3.CreateRotation(rotation);
|
||||
matrix = Matrix3Helpers.CreateRotation(rotation);
|
||||
}
|
||||
|
||||
(matrix.R0C2, matrix.R1C2) = lightPos;
|
||||
(matrix.M31, matrix.M32) = lightPos;
|
||||
|
||||
_drawQuad(-offset, offset, matrix, lightShader);
|
||||
}
|
||||
@@ -692,7 +692,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitX);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightBlurTarget.Texture);
|
||||
|
||||
@@ -700,7 +700,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Blur vertically to _wallBleedIntermediateRenderTarget2.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitY);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.LightRenderTarget.Texture);
|
||||
}
|
||||
@@ -754,14 +754,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Blur horizontally to _wallBleedIntermediateRenderTarget1.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitX);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget1.Texture);
|
||||
BindRenderTargetFull(viewport.WallBleedIntermediateRenderTarget2);
|
||||
|
||||
// Blur vertically to _wallBleedIntermediateRenderTarget2.
|
||||
shader.SetUniformMaybe("direction", Vector2.UnitY);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3.Identity, shader);
|
||||
_drawQuad(Vector2.Zero, viewport.Size, Matrix3x2.Identity, shader);
|
||||
|
||||
SetTexture(TextureUnit.Texture0, viewport.WallBleedIntermediateRenderTarget2.Texture);
|
||||
}
|
||||
@@ -909,13 +909,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Second modification is that output must be fov-centred (difference-space)
|
||||
uZero -= fovCentre;
|
||||
|
||||
var clipToDiff = new Matrix3(in uX, in uY, in uZero);
|
||||
var clipToDiff = new Matrix3x2(uX.X, uX.Y, uY.X, uY.Y, uZero.X, uZero.Y);
|
||||
|
||||
fovShader.SetUniformMaybe("clipToDiff", clipToDiff);
|
||||
_drawQuad(Vector2.Zero, Vector2.One, Matrix3.Identity, fovShader);
|
||||
_drawQuad(Vector2.Zero, Vector2.One, Matrix3x2.Identity, fovShader);
|
||||
}
|
||||
|
||||
private void UpdateOcclusionGeometry(MapId map, Box2 expandedBounds, Matrix3 eyeTransform)
|
||||
private void UpdateOcclusionGeometry(MapId map, Box2 expandedBounds, Matrix3x2 eyeTransform)
|
||||
{
|
||||
using var _ = _prof.Group("UpdateOcclusionGeometry");
|
||||
using var _p = DebugGroup(nameof(UpdateOcclusionGeometry));
|
||||
@@ -968,9 +968,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var worldTransform = xformSystem.GetWorldMatrix(transform, xforms);
|
||||
var box = occluder.BoundingBox;
|
||||
|
||||
var tl = worldTransform.Transform(box.TopLeft);
|
||||
var tr = worldTransform.Transform(box.TopRight);
|
||||
var br = worldTransform.Transform(box.BottomRight);
|
||||
var tl = Vector2.Transform(box.TopLeft, worldTransform);
|
||||
var tr = Vector2.Transform(box.TopRight, worldTransform);
|
||||
var br = Vector2.Transform(box.BottomRight, worldTransform);
|
||||
var bl = tl + br - tr;
|
||||
|
||||
// Faces.
|
||||
@@ -1010,9 +1010,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
//
|
||||
|
||||
// Calculate delta positions from camera.
|
||||
var dTl = eyeTransform.Transform(tl);
|
||||
var dTr = eyeTransform.Transform(tr);
|
||||
var dBl = eyeTransform.Transform(bl);
|
||||
var dTl = Vector2.Transform(tl, eyeTransform);
|
||||
var dTr = Vector2.Transform(tr, eyeTransform);
|
||||
var dBl = Vector2.Transform(bl, eyeTransform);
|
||||
var dBr = dBl + dTr - dTl;
|
||||
|
||||
// Get which neighbors are occluding.
|
||||
@@ -1201,7 +1201,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void LightResolutionScaleChanged(float newValue)
|
||||
{
|
||||
_lightResolutionScale = newValue;
|
||||
_lightResolutionScale = newValue > 0.05f ? newValue : 0.05f;
|
||||
RegenAllLightRts();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
private RenderHandle _renderHandle = default!;
|
||||
|
||||
private sealed class RenderHandle : IRenderHandle
|
||||
internal sealed class RenderHandle : IRenderHandle
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
private readonly IEntityManager _entities;
|
||||
@@ -33,17 +33,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DrawingHandleWorld = new DrawingHandleWorldImpl(white, this);
|
||||
}
|
||||
|
||||
public void SetModelTransform(in Matrix3 matrix)
|
||||
public void SetModelTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_clyde.DrawSetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public Matrix3 GetModelTransform()
|
||||
public Matrix3x2 GetModelTransform()
|
||||
{
|
||||
return _clyde.DrawGetModelTransform();
|
||||
}
|
||||
|
||||
public void SetProjView(in Matrix3 proj, in Matrix3 view)
|
||||
public void SetProjView(in Matrix3x2 proj, in Matrix3x2 view)
|
||||
{
|
||||
_clyde.DrawSetProjViewTransform(proj, view);
|
||||
}
|
||||
@@ -88,16 +88,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var clydeTexture = ExtractTexture(texture, in subRegion, out var csr);
|
||||
|
||||
var (w, h) = clydeTexture.Size;
|
||||
var sr = new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
|
||||
var sr = WorldTextureBoundsToUV(clydeTexture, csr);
|
||||
|
||||
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
|
||||
}
|
||||
|
||||
internal static Box2 WorldTextureBoundsToUV(ClydeTexture texture, UIBox2 csr)
|
||||
{
|
||||
var (w, h) = texture.Size;
|
||||
return new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a subRegion (px) into texture coords (0-1) of a given texture (cells of the textureAtlas).
|
||||
/// </summary>
|
||||
private static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
|
||||
internal static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
|
||||
{
|
||||
if (texture is AtlasTexture atlas)
|
||||
{
|
||||
@@ -172,9 +177,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var oldModel = _clyde._currentMatrixModel;
|
||||
|
||||
var newModel = oldModel;
|
||||
position += new Vector2(oldModel.R0C2, oldModel.R1C2);
|
||||
newModel.R0C2 = 0;
|
||||
newModel.R1C2 = 0;
|
||||
position += new Vector2(oldModel.M31, oldModel.M32);
|
||||
newModel.M31 = 0;
|
||||
newModel.M32 = 0;
|
||||
SetModelTransform(newModel);
|
||||
|
||||
// Switch rendering to pseudo-world space.
|
||||
@@ -189,7 +194,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Maaaaybe this is meant to have a minus sign.
|
||||
var rot = -(float) eyeRot.Theta;
|
||||
|
||||
var view = Matrix3.CreateTransform(ofsX, ofsY, rot, scale.X, scale.Y);
|
||||
var view = Matrix3Helpers.CreateTransform(ofsX, ofsY, rot, scale.X, scale.Y);
|
||||
SetProjView(proj, view);
|
||||
}
|
||||
|
||||
@@ -292,12 +297,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle = renderHandle;
|
||||
}
|
||||
|
||||
public override void SetTransform(in Matrix3 matrix)
|
||||
public override void SetTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_renderHandle.SetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public override Matrix3 GetTransform()
|
||||
public override Matrix3x2 GetTransform()
|
||||
{
|
||||
return _renderHandle.GetModelTransform();
|
||||
}
|
||||
@@ -397,12 +402,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle = renderHandle;
|
||||
}
|
||||
|
||||
public override void SetTransform(in Matrix3 matrix)
|
||||
public override void SetTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_renderHandle.SetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public override Matrix3 GetTransform()
|
||||
public override Matrix3x2 GetTransform()
|
||||
{
|
||||
return _renderHandle.GetModelTransform();
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// This matrix is applied to most normal geometry coming in.
|
||||
// Some is applied while the batch is being created (e.g. simple texture draw calls).
|
||||
// For DrawPrimitives OTOH the model matrix is passed along with the render command so is applied in the shader.
|
||||
private Matrix3 _currentMatrixModel = Matrix3.Identity;
|
||||
private Matrix3x2 _currentMatrixModel = Matrix3x2.Identity;
|
||||
|
||||
// Buffers and data for the batching system. Written into during (queue) and processed during (submit).
|
||||
private readonly Vertex2D[] BatchVertexData = new Vertex2D[MaxBatchQuads * 4];
|
||||
@@ -84,8 +84,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Current projection & view matrices that are being used ot render.
|
||||
// This gets updated to keep track during (queue) and (misc), but not during (submit).
|
||||
private Matrix3 _currentMatrixProj;
|
||||
private Matrix3 _currentMatrixView;
|
||||
private Matrix3x2 _currentMatrixProj;
|
||||
private Matrix3x2 _currentMatrixView;
|
||||
|
||||
// (queue) and (misc), current state of the scissor test. Null if disabled.
|
||||
private UIBox2i? _currentScissorState;
|
||||
@@ -110,25 +110,25 @@ namespace Robust.Client.Graphics.Clyde
|
||||
UniformConstantsUBO.Reallocate(constants);
|
||||
}
|
||||
|
||||
private void CalcScreenMatrices(in Vector2i screenSize, out Matrix3 proj, out Matrix3 view)
|
||||
private void CalcScreenMatrices(in Vector2i screenSize, out Matrix3x2 proj, out Matrix3x2 view)
|
||||
{
|
||||
proj = Matrix3.Identity;
|
||||
proj.R0C0 = 2f / screenSize.X;
|
||||
proj.R1C1 = -2f / screenSize.Y;
|
||||
proj.R0C2 = -1;
|
||||
proj.R1C2 = 1;
|
||||
proj = Matrix3x2.Identity;
|
||||
proj.M11 = 2f / screenSize.X;
|
||||
proj.M22 = -2f / screenSize.Y;
|
||||
proj.M31 = -1;
|
||||
proj.M32 = 1;
|
||||
|
||||
if (_currentRenderTarget.FlipY)
|
||||
{
|
||||
proj.R1C1 *= -1;
|
||||
proj.R1C2 *= -1;
|
||||
proj.M22 *= -1;
|
||||
proj.M32 *= -1;
|
||||
}
|
||||
|
||||
view = Matrix3.Identity;
|
||||
view = Matrix3x2.Identity;
|
||||
}
|
||||
|
||||
private void CalcWorldMatrices(in Vector2i screenSize, in Vector2 renderScale, IEye eye,
|
||||
out Matrix3 proj, out Matrix3 view)
|
||||
out Matrix3x2 proj, out Matrix3x2 view)
|
||||
{
|
||||
eye.GetViewMatrix(out view, renderScale);
|
||||
|
||||
@@ -136,20 +136,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3 proj)
|
||||
private void CalcWorldProjMatrix(in Vector2i screenSize, out Matrix3x2 proj)
|
||||
{
|
||||
proj = Matrix3.Identity;
|
||||
proj.R0C0 = EyeManager.PixelsPerMeter * 2f / screenSize.X;
|
||||
proj.R1C1 = EyeManager.PixelsPerMeter * 2f / screenSize.Y;
|
||||
proj = Matrix3x2.Identity;
|
||||
proj.M11 = EyeManager.PixelsPerMeter * 2f / screenSize.X;
|
||||
proj.M22 = EyeManager.PixelsPerMeter * 2f / screenSize.Y;
|
||||
|
||||
if (_currentRenderTarget.FlipY)
|
||||
{
|
||||
proj.R1C1 *= -1;
|
||||
proj.R1C2 *= -1;
|
||||
proj.M22 *= -1;
|
||||
proj.M32 *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetProjViewBuffer(in Matrix3 proj, in Matrix3 view)
|
||||
private void SetProjViewBuffer(in Matrix3x2 proj, in Matrix3x2 view)
|
||||
{
|
||||
// TODO: Fix perf here.
|
||||
// This immediately causes a glBufferData() call every time this is changed.
|
||||
@@ -160,7 +160,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
ProjViewUBO.Reallocate(combined);
|
||||
}
|
||||
|
||||
private void SetProjViewFull(in Matrix3 proj, in Matrix3 view)
|
||||
private void SetProjViewFull(in Matrix3x2 proj, in Matrix3x2 view)
|
||||
{
|
||||
_currentMatrixProj = proj;
|
||||
_currentMatrixView = view;
|
||||
@@ -285,21 +285,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
};
|
||||
}
|
||||
|
||||
private void _drawQuad(Vector2 a, Vector2 b, in Matrix3 modelMatrix, GLShaderProgram program)
|
||||
private void _drawQuad(Vector2 a, Vector2 b, in Matrix3x2 modelMatrix, GLShaderProgram program)
|
||||
{
|
||||
DrawQuadWithVao(QuadVAO, a, b, modelMatrix, program);
|
||||
}
|
||||
|
||||
private void DrawQuadWithVao(GLHandle vao, Vector2 a, Vector2 b, in Matrix3 modelMatrix,
|
||||
private void DrawQuadWithVao(GLHandle vao, Vector2 a, Vector2 b, in Matrix3x2 modelMatrix,
|
||||
GLShaderProgram program)
|
||||
{
|
||||
BindVertexArray(vao.Handle);
|
||||
CheckGlError();
|
||||
|
||||
var rectTransform = Matrix3.Identity;
|
||||
(rectTransform.R0C0, rectTransform.R1C1) = b - a;
|
||||
(rectTransform.R0C2, rectTransform.R1C2) = a;
|
||||
rectTransform.Multiply(modelMatrix);
|
||||
var rectTransform = Matrix3x2.Identity;
|
||||
(rectTransform.M11, rectTransform.M22) = b - a;
|
||||
(rectTransform.M31, rectTransform.M32) = a;
|
||||
rectTransform = rectTransform * modelMatrix;
|
||||
program.SetUniformMaybe(UniIModelMatrix, rectTransform);
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
@@ -315,7 +315,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
FlushBatchQueue();
|
||||
|
||||
// Reset renderer state.
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
_currentMatrixModel = Matrix3x2.Identity;
|
||||
_queuedShaderInstance = _defaultShader;
|
||||
SetScissorFull(null);
|
||||
}
|
||||
@@ -496,7 +496,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case bool b:
|
||||
program.SetUniform(name, b ? 1 : 0);
|
||||
break;
|
||||
case Matrix3 matrix3:
|
||||
case Matrix3x2 matrix3:
|
||||
program.SetUniform(name, matrix3);
|
||||
break;
|
||||
case Matrix4 matrix4:
|
||||
@@ -532,17 +532,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return ref command;
|
||||
}
|
||||
|
||||
private void DrawSetModelTransform(in Matrix3 matrix)
|
||||
private void DrawSetModelTransform(in Matrix3x2 matrix)
|
||||
{
|
||||
_currentMatrixModel = matrix;
|
||||
}
|
||||
|
||||
private Matrix3 DrawGetModelTransform()
|
||||
private Matrix3x2 DrawGetModelTransform()
|
||||
{
|
||||
return _currentMatrixModel;
|
||||
}
|
||||
|
||||
private void DrawSetProjViewTransform(in Matrix3 proj, in Matrix3 view)
|
||||
private void DrawSetProjViewTransform(in Matrix3x2 proj, in Matrix3x2 view)
|
||||
{
|
||||
BreakBatch();
|
||||
|
||||
@@ -571,17 +571,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
EnsureBatchSpaceAvailable(4, GetQuadBatchIndexCount());
|
||||
EnsureBatchState(texture, true, GetQuadBatchPrimitiveType(), _queuedShader);
|
||||
|
||||
bl = _currentMatrixModel.Transform(bl);
|
||||
br = _currentMatrixModel.Transform(br);
|
||||
tr = _currentMatrixModel.Transform(tr);
|
||||
bl = Vector2.Transform(bl, _currentMatrixModel);
|
||||
br = Vector2.Transform(br, _currentMatrixModel);
|
||||
tr = Vector2.Transform(tr, _currentMatrixModel);
|
||||
tl = tr + bl - br;
|
||||
|
||||
// TODO: split batch if necessary.
|
||||
var vIdx = BatchVertexIndex;
|
||||
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, modulate);
|
||||
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, modulate);
|
||||
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, modulate);
|
||||
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, modulate);
|
||||
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, new Vector2(0, 0), modulate);
|
||||
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, new Vector2(1, 0), modulate);
|
||||
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, new Vector2(1, 1), modulate);
|
||||
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, new Vector2(0, 1), modulate);
|
||||
BatchVertexIndex += 4;
|
||||
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);
|
||||
|
||||
@@ -676,8 +676,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
EnsureBatchSpaceAvailable(2, 0);
|
||||
EnsureBatchState(_stockTextureWhite.TextureId, false, BatchPrimitiveType.LineList, _queuedShader);
|
||||
|
||||
a = _currentMatrixModel.Transform(a);
|
||||
b = _currentMatrixModel.Transform(b);
|
||||
a = Vector2.Transform(a, _currentMatrixModel);
|
||||
b = Vector2.Transform(b, _currentMatrixModel);
|
||||
|
||||
// TODO: split batch if necessary.
|
||||
var vIdx = BatchVertexIndex;
|
||||
@@ -807,7 +807,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
command.DrawBatch.ShaderInstance = metaData.ShaderInstance;
|
||||
|
||||
command.DrawBatch.Count = currentIndex - metaData.StartIndex;
|
||||
command.DrawBatch.ModelMatrix = Matrix3.Identity;
|
||||
command.DrawBatch.ModelMatrix = Matrix3x2.Identity;
|
||||
|
||||
_debugStats.LastBatches += 1;
|
||||
}
|
||||
@@ -882,7 +882,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_queuedRenderCommands.Clear();
|
||||
_currentViewport = null;
|
||||
_lightingReady = false;
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
_currentMatrixModel = Matrix3x2.Identity;
|
||||
SetScissorFull(null);
|
||||
BindRenderTargetFull(_mainWindow!.RenderTarget);
|
||||
_batchMetaData = null;
|
||||
@@ -961,13 +961,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public BatchPrimitiveType PrimitiveType;
|
||||
|
||||
// TODO: this makes the render commands so much more large please remove.
|
||||
public Matrix3 ModelMatrix;
|
||||
public Matrix3x2 ModelMatrix;
|
||||
}
|
||||
|
||||
private struct RenderCommandProjViewMatrix
|
||||
{
|
||||
public Matrix3 ProjMatrix;
|
||||
public Matrix3 ViewMatrix;
|
||||
public Matrix3x2 ProjMatrix;
|
||||
public Matrix3x2 ViewMatrix;
|
||||
}
|
||||
|
||||
private struct RenderCommandScissor
|
||||
@@ -1056,11 +1056,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private readonly struct FullStoredRendererState
|
||||
{
|
||||
public readonly Matrix3 ProjMatrix;
|
||||
public readonly Matrix3 ViewMatrix;
|
||||
public readonly Matrix3x2 ProjMatrix;
|
||||
public readonly Matrix3x2 ViewMatrix;
|
||||
public readonly LoadedRenderTarget RenderTarget;
|
||||
|
||||
public FullStoredRendererState(in Matrix3 projMatrix, in Matrix3 viewMatrix,
|
||||
public FullStoredRendererState(in Matrix3x2 projMatrix, in Matrix3x2 viewMatrix,
|
||||
LoadedRenderTarget renderTarget)
|
||||
{
|
||||
ProjMatrix = projMatrix;
|
||||
|
||||
@@ -506,7 +506,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix3 value)
|
||||
private protected override void SetParameterImpl(string name, in Matrix3x2 value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
data.ParametersDirty = true;
|
||||
|
||||
@@ -601,7 +601,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ClydeTexture : OwnedTexture
|
||||
internal sealed class ClydeTexture : OwnedTexture
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
public readonly bool IsSrgb;
|
||||
@@ -649,24 +649,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return $"ClydeTexture: ({TextureId})";
|
||||
}
|
||||
|
||||
public override Color GetPixel(int x, int y)
|
||||
public override unsafe Color GetPixel(int x, int y)
|
||||
{
|
||||
if (!_clyde._loadedTextures.TryGetValue(TextureId, out var loaded))
|
||||
{
|
||||
throw new DataException("Texture not found");
|
||||
}
|
||||
|
||||
Span<byte> rgba = stackalloc byte[4*this.Size.X*this.Size.Y];
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = rgba)
|
||||
{
|
||||
var curTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
var bufSize = 4 * loaded.Size.X * loaded.Size.Y;
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(bufSize);
|
||||
|
||||
GL.GetTextureImage(loaded.OpenGLObject.Handle, 0, PF.Rgba, PT.UnsignedByte, 4*this.Size.X*this.Size.Y, (IntPtr) p);
|
||||
}
|
||||
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
|
||||
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
GL.GetTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, (IntPtr) p);
|
||||
}
|
||||
int pixelPos = (this.Size.X*(this.Size.Y-y) + x)*4;
|
||||
return new Color(rgba[pixelPos+0], rgba[pixelPos+1], rgba[pixelPos+2], rgba[pixelPos+3]);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, curTexture2D);
|
||||
|
||||
var pixelPos = (loaded.Size.X * (loaded.Size.Y - y - 1) + x) * 4;
|
||||
var color = new Color(buffer[pixelPos+0], buffer[pixelPos+1], buffer[pixelPos+2], buffer[pixelPos+3]);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// view matrix
|
||||
vp.Eye.GetViewMatrixInv(out var viewMatrixInv, vp.RenderScale);
|
||||
point = viewMatrixInv * point;
|
||||
point = Vector2.Transform(point, viewMatrixInv);
|
||||
|
||||
return point;
|
||||
}
|
||||
@@ -136,7 +136,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// view matrix
|
||||
Eye.GetViewMatrixInv(out var viewMatrixInv, RenderScale);
|
||||
newPoint = viewMatrixInv * newPoint;
|
||||
newPoint = Vector2.Transform(newPoint, viewMatrixInv);
|
||||
|
||||
return new MapCoordinates(newPoint, Eye.Position.MapId);
|
||||
}
|
||||
@@ -150,7 +150,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var newPoint = point;
|
||||
|
||||
eye.GetViewMatrix(out var viewMatrix, RenderScale);
|
||||
newPoint = viewMatrix * newPoint;
|
||||
newPoint = Vector2.Transform(newPoint, viewMatrix);
|
||||
|
||||
// (inlined version of UiProjMatrix)
|
||||
newPoint *= new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
@@ -159,14 +159,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return newPoint;
|
||||
}
|
||||
|
||||
public Matrix3 GetWorldToLocalMatrix()
|
||||
public Matrix3x2 GetWorldToLocalMatrix()
|
||||
{
|
||||
if (Eye == null)
|
||||
return Matrix3.Identity;
|
||||
return Matrix3x2.Identity;
|
||||
|
||||
Eye.GetViewMatrix(out var viewMatrix, RenderScale * new Vector2(EyeManager.PixelsPerMeter, -EyeManager.PixelsPerMeter));
|
||||
viewMatrix.R0C2 += Size.X / 2f;
|
||||
viewMatrix.R1C2 += Size.Y / 2f;
|
||||
viewMatrix.M31 += Size.X / 2f;
|
||||
viewMatrix.M32 += Size.Y / 2f;
|
||||
return viewMatrix;
|
||||
}
|
||||
|
||||
|
||||
@@ -361,7 +361,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix3 value)
|
||||
private protected override void SetParameterImpl(string name, in Matrix3x2 value)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -482,7 +482,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return default;
|
||||
}
|
||||
|
||||
public Matrix3 GetWorldToLocalMatrix() => default;
|
||||
public Matrix3x2 GetWorldToLocalMatrix() => default;
|
||||
|
||||
public Vector2 WorldToLocal(Vector2 point)
|
||||
{
|
||||
|
||||
@@ -247,28 +247,33 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.Uniform1(uniformId, singles.Length, singles);
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Matrix3 matrix)
|
||||
public void SetUniform(string uniformName, in Matrix3x2 matrix)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, matrix);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, in Matrix3 matrix)
|
||||
public void SetUniform(int uniformName, in Matrix3x2 matrix)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, matrix);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void SetUniformDirect(int slot, in Matrix3 value, bool transpose=true)
|
||||
private unsafe void SetUniformDirect(int slot, in Matrix3x2 value)
|
||||
{
|
||||
Matrix3 tmpTranspose = value;
|
||||
if (transpose)
|
||||
{
|
||||
// transposition not supported on GLES2, & no access to _hasGLES
|
||||
tmpTranspose.Transpose();
|
||||
}
|
||||
GL.UniformMatrix3(slot, 1, false, (float*) &tmpTranspose);
|
||||
// We put the rows of the input matrix into the columns of our GPU matrices
|
||||
// this transpose is required, as in C#, we premultiply vectors with matrices
|
||||
// (vM) while GL postmultiplies vectors with matrices (Mv); however, since
|
||||
// the Matrix3x2 data is stored row-major, and GL uses column-major, the
|
||||
// memory layout is the same (or would be, if Matrix3x2 didn't have an
|
||||
// implicit column)
|
||||
var buf = stackalloc float[9]{
|
||||
value.M11, value.M12, 0,
|
||||
value.M21, value.M22, 0,
|
||||
value.M31, value.M32, 1
|
||||
};
|
||||
GL.UniformMatrix3(slot, 1, false, (float*)buf);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
@@ -474,7 +479,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Matrix3 value)
|
||||
public void SetUniformMaybe(string uniformName, in Matrix3x2 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
@@ -490,7 +495,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(int uniformName, in Matrix3 value)
|
||||
public void SetUniformMaybe(int uniformName, in Matrix3x2 value)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
varying highp vec2 UV;
|
||||
varying highp vec2 UV2;
|
||||
varying highp vec2 Pos;
|
||||
varying highp vec4 VtxModulate;
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
/*layout (location = 0)*/ attribute vec2 aPos;
|
||||
// Texture coordinates.
|
||||
/*layout (location = 1)*/ attribute vec2 tCoord;
|
||||
/*layout (location = 2)*/ attribute vec2 tCoord2;
|
||||
// Colour modulation.
|
||||
/*layout (location = 2)*/ attribute vec4 modulate;
|
||||
/*layout (location = 3)*/ attribute vec4 modulate;
|
||||
|
||||
varying vec2 UV;
|
||||
varying vec2 UV2;
|
||||
varying vec2 Pos;
|
||||
varying vec4 VtxModulate;
|
||||
|
||||
@@ -36,5 +38,6 @@ void main()
|
||||
gl_Position = vec4(VERTEX, 0.0, 1.0);
|
||||
Pos = (VERTEX + 1.0) / 2.0;
|
||||
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
|
||||
UV2 = tCoord2;
|
||||
VtxModulate = zFromSrgb(modulate);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
varying highp vec2 UV;
|
||||
varying highp vec2 UV2;
|
||||
|
||||
uniform sampler2D lightMap;
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
/*layout (location = 0)*/ attribute vec2 aPos;
|
||||
// Texture coordinates.
|
||||
/*layout (location = 1)*/ attribute vec2 tCoord;
|
||||
/*layout (location = 2)*/ attribute vec2 tCoord2;
|
||||
// Colour modulation.
|
||||
/*layout (location = 2)*/ attribute vec4 modulate;
|
||||
/*layout (location = 3)*/ attribute vec4 modulate;
|
||||
|
||||
varying vec2 UV;
|
||||
varying vec2 UV2;
|
||||
|
||||
// Maybe we should merge these CPU side.
|
||||
// idk yet.
|
||||
@@ -40,6 +42,7 @@ void main()
|
||||
vec2 VERTEX = aPos;
|
||||
|
||||
UV = tCoord;
|
||||
UV2 = tCoord2;
|
||||
|
||||
// [SHADER_CODE]
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -43,19 +44,19 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
var matrix = Matrix3.CreateTransform(in position, in rotation, in scale);
|
||||
var matrix = Matrix3Helpers.CreateTransform(in position, in rotation, in scale);
|
||||
SetTransform(in matrix);
|
||||
}
|
||||
|
||||
public void SetTransform(in Vector2 position, in Angle rotation)
|
||||
{
|
||||
var matrix = Matrix3.CreateTransform(in position, in rotation);
|
||||
var matrix = Matrix3Helpers.CreateTransform(in position, in rotation);
|
||||
SetTransform(in matrix);
|
||||
}
|
||||
|
||||
public abstract void SetTransform(in Matrix3 matrix);
|
||||
public abstract void SetTransform(in Matrix3x2 matrix);
|
||||
|
||||
public abstract Matrix3 GetTransform();
|
||||
public abstract Matrix3x2 GetTransform();
|
||||
|
||||
public abstract void UseShader(ShaderInstance? shader);
|
||||
|
||||
@@ -63,6 +64,19 @@ namespace Robust.Client.Graphics
|
||||
|
||||
// ---- DrawPrimitives: Vector2 API ----
|
||||
|
||||
/// <summary>
|
||||
/// Draws arbitrary geometry primitives with a flat color.
|
||||
/// </summary>
|
||||
/// <param name="primitiveTopology">The topology of the primitives to draw.</param>
|
||||
/// <param name="vertices">The list of vertices to render.</param>
|
||||
/// <param name="color">The color to draw with.</param>
|
||||
public void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, List<Vector2> vertices,
|
||||
Color color)
|
||||
{
|
||||
var span = CollectionsMarshal.AsSpan(vertices);
|
||||
DrawPrimitives(primitiveTopology, span, color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws arbitrary geometry primitives with a flat color.
|
||||
/// </summary>
|
||||
@@ -100,43 +114,12 @@ namespace Robust.Client.Graphics
|
||||
DrawPrimitives(primitiveTopology, White, indices, drawVertices);
|
||||
}
|
||||
|
||||
private static void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
private void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
{
|
||||
if (input.Length == 0)
|
||||
return;
|
||||
|
||||
if (input.Length != output.Length)
|
||||
Color colorLinear = Color.FromSrgb(color);
|
||||
for (var i = 0; i < output.Length; i++)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid lengths!");
|
||||
}
|
||||
|
||||
var colorLinear = Color.FromSrgb(color);
|
||||
var colorVec = Unsafe.As<Color, Vector128<float>>(ref colorLinear);
|
||||
var uvVec = Vector128.Create(0, 0, 0.5f, 0.5f);
|
||||
var maskVec = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0, 0).AsSingle();
|
||||
|
||||
var simdVectors = (nuint)(input.Length / 2);
|
||||
ref readonly var srcBase = ref Unsafe.As<Vector2, float>(ref Unsafe.AsRef(in input[0]));
|
||||
ref var dstBase = ref Unsafe.As<DrawVertexUV2DColor, float>(ref output[0]);
|
||||
|
||||
for (nuint i = 0; i < simdVectors; i++)
|
||||
{
|
||||
var positions = Vector128.LoadUnsafe(in srcBase, i * 4);
|
||||
|
||||
var posColorLower = (positions & maskVec) | uvVec;
|
||||
var posColorUpper = (Vector128.Shuffle(positions, Vector128.Create(2, 3, 0, 0)) & maskVec) | uvVec;
|
||||
|
||||
posColorLower.StoreUnsafe(ref dstBase, i * 16);
|
||||
colorVec.StoreUnsafe(ref dstBase, i * 16 + 4);
|
||||
posColorUpper.StoreUnsafe(ref dstBase, i * 16 + 8);
|
||||
colorVec.StoreUnsafe(ref dstBase, i * 16 + 12);
|
||||
}
|
||||
|
||||
var lastPos = (int)simdVectors * 2;
|
||||
if (lastPos != output.Length)
|
||||
{
|
||||
// Odd number of vertices. Handle the last manually.
|
||||
output[lastPos] = new DrawVertexUV2DColor(input[lastPos], new Vector2(0.5f, 0.5f), colorLinear);
|
||||
output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +237,8 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
public Vector2 Position;
|
||||
public Vector2 UV;
|
||||
public Vector2 UV2;
|
||||
|
||||
/// <summary>
|
||||
/// Modulation colour for this vertex.
|
||||
/// Note that this color is in linear space.
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Robust.Client.Graphics
|
||||
/// <summary>
|
||||
/// Matrix equivalent of <see cref="LocalToWorld(Vector2)"/>.
|
||||
/// </summary>
|
||||
Matrix3 GetWorldToLocalMatrix();
|
||||
Matrix3x2 GetWorldToLocalMatrix();
|
||||
|
||||
/// <summary>
|
||||
/// Converts a point in world-space to the viewport's screen coordinates.
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Robust.Client.Graphics
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, in Matrix3 value)
|
||||
public void SetParameter(string name, in Matrix3x2 value)
|
||||
{
|
||||
EnsureAlive();
|
||||
EnsureMutable();
|
||||
@@ -219,7 +219,7 @@ namespace Robust.Client.Graphics
|
||||
private protected abstract void SetParameterImpl(string name, int value);
|
||||
private protected abstract void SetParameterImpl(string name, Vector2i value);
|
||||
private protected abstract void SetParameterImpl(string name, bool value);
|
||||
private protected abstract void SetParameterImpl(string name, in Matrix3 value);
|
||||
private protected abstract void SetParameterImpl(string name, in Matrix3x2 value);
|
||||
private protected abstract void SetParameterImpl(string name, in Matrix4 value);
|
||||
private protected abstract void SetParameterImpl(string name, Texture value);
|
||||
private protected abstract void SetStencilImpl(StencilParameters value);
|
||||
|
||||
@@ -17,7 +17,7 @@ using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
[Prototype("shader")]
|
||||
public sealed class ShaderPrototype : IPrototype, ISerializationHooks
|
||||
public sealed partial class ShaderPrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
[ViewVariables]
|
||||
[IdDataField]
|
||||
@@ -176,7 +176,7 @@ namespace Robust.Client.Graphics
|
||||
return node.AsVector4();
|
||||
}
|
||||
case ShaderDataType.Mat3:
|
||||
return node.AsMatrix3();
|
||||
return node.AsMatrix3x2();
|
||||
case ShaderDataType.Mat4:
|
||||
return node.AsMatrix4();
|
||||
default:
|
||||
@@ -219,7 +219,7 @@ namespace Robust.Client.Graphics
|
||||
case bool i:
|
||||
instance.SetParameter(key, i);
|
||||
break;
|
||||
case Matrix3 i:
|
||||
case Matrix3x2 i:
|
||||
instance.SetParameter(key, i);
|
||||
break;
|
||||
case Matrix4 i:
|
||||
|
||||
@@ -346,7 +346,7 @@ namespace Robust.Client.Input
|
||||
{
|
||||
if (binding.CanRepeat)
|
||||
{
|
||||
return SetBindState(binding, BoundKeyState.Down, uiOnly);
|
||||
return SetBindState(binding, BoundKeyState.Down, uiOnly, isRepeat);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -375,7 +375,7 @@ namespace Robust.Client.Input
|
||||
SetBindState(binding, BoundKeyState.Up);
|
||||
}
|
||||
|
||||
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false)
|
||||
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false, bool isRepeat = false)
|
||||
{
|
||||
if (binding.BindingType == KeyBindingType.Command && state == BoundKeyState.Down)
|
||||
{
|
||||
@@ -387,6 +387,7 @@ namespace Robust.Client.Input
|
||||
// I honestly have no idea what the best solution here is.
|
||||
// note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches
|
||||
DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??");
|
||||
DebugTools.Assert(!isRepeat || binding.CanRepeat);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -399,7 +400,7 @@ namespace Robust.Client.Input
|
||||
binding.State = state;
|
||||
|
||||
var eventArgs = new BoundKeyEventArgs(binding.Function, binding.State,
|
||||
MouseScreenPosition, binding.CanFocus);
|
||||
MouseScreenPosition, binding.CanFocus, isRepeat);
|
||||
|
||||
// UI returns true here into blockPass if it wants to prevent us from giving input events
|
||||
// to the viewport, but doesn't want it hard-handled so we keep processing possible key actions.
|
||||
|
||||
@@ -120,6 +120,6 @@ public sealed class TileEdgeOverlay : GridOverlay
|
||||
}
|
||||
}
|
||||
|
||||
args.WorldHandle.SetTransform(Matrix3.Identity);
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Robust.Client.Physics
|
||||
return true;
|
||||
}, true);
|
||||
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
|
||||
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
|
||||
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager)).LengthSquared())
|
||||
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager, pManager.EntityManager.System<SharedTransformSystem>())).LengthSquared())
|
||||
.ToList();
|
||||
|
||||
if (snapToEntities.Count == 0)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
@@ -24,7 +25,7 @@ namespace Robust.Client.Placement.Modes
|
||||
SnapSize = 1f;
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
Grid = pManager.MapManager.GetGrid(gridId);
|
||||
Grid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
SnapSize = Grid.TileSize; //Find snap size for the grid.
|
||||
}
|
||||
else
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
public SnapgridCenter(PlacementManager pMan) : base(pMan) { }
|
||||
|
||||
public override void Render(DrawingHandleWorld handle)
|
||||
public override void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
if (Grid != null)
|
||||
{
|
||||
@@ -34,18 +34,18 @@ namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(a, 0));
|
||||
var to = ScreenToWorld(new Vector2(a, viewportSize.Y));
|
||||
handle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
}
|
||||
for (var a = gridstart.Y; a < viewportSize.Y; a += SnapSize * EyeManager.PixelsPerMeter)
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(0, a));
|
||||
var to = ScreenToWorld(new Vector2(viewportSize.X, a));
|
||||
handle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw grid BELOW the ghost thing.
|
||||
base.Render(handle);
|
||||
base.Render(args);
|
||||
}
|
||||
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
@@ -56,7 +56,7 @@ namespace Robust.Client.Placement.Modes
|
||||
SnapSize = 1f;
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
Grid = pManager.MapManager.GetGrid(gridId);
|
||||
Grid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
SnapSize = Grid.TileSize; //Find snap size for the grid.
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
@@ -19,7 +20,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
var gridId = MouseCoords.GetGridUid(pManager.EntityManager);
|
||||
|
||||
if (!pManager.MapManager.TryGetGrid(gridId, out var mapGrid))
|
||||
if (!pManager.EntityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
|
||||
return;
|
||||
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
var mapGrid = pManager.MapManager.GetGrid(gridId);
|
||||
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
@@ -22,7 +23,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
var mapGrid = pManager.MapManager.GetGrid(gridId);
|
||||
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace Robust.Client.Placement.Modes
|
||||
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
var mapGrid = pManager.MapManager.GetGrid(gridId);
|
||||
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Log;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Placement
|
||||
{
|
||||
@@ -79,6 +80,10 @@ namespace Robust.Client.Placement
|
||||
private set
|
||||
{
|
||||
_isActive = value;
|
||||
|
||||
if (CurrentPermission?.UseEditorContext is false)
|
||||
return;
|
||||
|
||||
SwitchEditorContext(value);
|
||||
}
|
||||
}
|
||||
@@ -332,7 +337,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
private void HandleTileChanged(ref TileChangedEvent args)
|
||||
{
|
||||
var coords = MapManager.GetGrid(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices);
|
||||
var coords = EntityManager.GetComponent<MapGridComponent>(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices);
|
||||
_pendingTileChanges.RemoveAll(c => c.Item1 == coords);
|
||||
}
|
||||
|
||||
@@ -623,20 +628,20 @@ namespace Robust.Client.Placement
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Render(DrawingHandleWorld handle)
|
||||
private void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
if (CurrentMode == null || !IsActive)
|
||||
{
|
||||
if (EraserRect.HasValue)
|
||||
{
|
||||
handle.UseShader(_drawingShader);
|
||||
handle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
|
||||
handle.UseShader(null);
|
||||
args.WorldHandle.UseShader(_drawingShader);
|
||||
args.WorldHandle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
|
||||
args.WorldHandle.UseShader(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentMode.Render(handle);
|
||||
CurrentMode.Render(args);
|
||||
|
||||
if (CurrentPermission is not {Range: > 0} ||
|
||||
!CurrentMode.RangeRequired ||
|
||||
@@ -645,7 +650,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
var worldPos = EntityManager.GetComponent<TransformComponent>(controlled).WorldPosition;
|
||||
|
||||
handle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
|
||||
args.WorldHandle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
|
||||
}
|
||||
|
||||
private void HandleStartPlacement(MsgPlacement msg)
|
||||
@@ -753,7 +758,7 @@ namespace Robust.Client.Placement
|
||||
// If we have actually placed something on a valid grid...
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
var grid = MapManager.GetGrid(gridId);
|
||||
var grid = EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
|
||||
// no point changing the tile to the same thing.
|
||||
if (grid.GetTileRef(coordinates).Tile.TypeId == CurrentPermission.TileType)
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -87,7 +88,7 @@ namespace Robust.Client.Placement
|
||||
/// <returns></returns>
|
||||
public abstract bool IsValidPosition(EntityCoordinates position);
|
||||
|
||||
public virtual void Render(DrawingHandleWorld handle)
|
||||
public virtual void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
var uid = pManager.CurrentPlacementOverlayEntity;
|
||||
if (!pManager.EntityManager.TryGetComponent(uid, out SpriteComponent? sprite) || !sprite.Visible)
|
||||
@@ -115,15 +116,17 @@ namespace Robust.Client.Placement
|
||||
|
||||
var dirAng = pManager.Direction.ToAngle();
|
||||
var spriteSys = pManager.EntityManager.System<SpriteSystem>();
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
foreach (var coordinate in locationcollection)
|
||||
{
|
||||
if (!coordinate.IsValid(pManager.EntityManager))
|
||||
return; // Just some paranoia just in case
|
||||
var worldPos = coordinate.ToMapPos(pManager.EntityManager);
|
||||
var worldPos = coordinate.ToMapPos(pManager.EntityManager, transformSys);
|
||||
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
|
||||
|
||||
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
|
||||
spriteSys.Render(uid.Value, sprite, handle, pManager.EyeManager.CurrentEye.Rotation, worldRot, worldPos);
|
||||
var rot = args.Viewport.Eye?.Rotation ?? default;
|
||||
spriteSys.Render(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,11 +139,12 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
var mouseScreen = pManager.InputManager.MouseScreenPosition;
|
||||
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
|
||||
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint;
|
||||
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
|
||||
float iterations;
|
||||
Vector2 distance;
|
||||
if (Math.Abs(x) > Math.Abs(y))
|
||||
@@ -167,11 +171,12 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
var mouseScreen = pManager.InputManager.MouseScreenPosition;
|
||||
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
|
||||
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint;
|
||||
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
|
||||
|
||||
var xSign = Math.Sign(placementdiff.X);
|
||||
var ySign = Math.Sign(placementdiff.Y);
|
||||
@@ -193,9 +198,9 @@ namespace Robust.Client.Placement
|
||||
public TileRef GetTileRef(EntityCoordinates coordinates)
|
||||
{
|
||||
var gridUidOpt = coordinates.GetGridUid(pManager.EntityManager);
|
||||
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.MapManager.GetGrid(gridUid).GetTileRef(MouseCoords)
|
||||
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.EntityManager.GetComponent<MapGridComponent>(gridUid).GetTileRef(MouseCoords)
|
||||
: new TileRef(gridUidOpt ?? EntityUid.Invalid,
|
||||
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager), Tile.Empty);
|
||||
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager, pManager.EntityManager.System<SharedTransformSystem>()), Tile.Empty);
|
||||
}
|
||||
|
||||
public TextureResource GetSprite(string key)
|
||||
@@ -223,7 +228,8 @@ namespace Robust.Client.Placement
|
||||
}
|
||||
|
||||
var range = pManager.CurrentPermission!.Range;
|
||||
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, coordinates, range))
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, transformSys, coordinates, range))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -231,7 +237,8 @@ namespace Robust.Client.Placement
|
||||
public bool IsColliding(EntityCoordinates coordinates)
|
||||
{
|
||||
var bounds = pManager.ColliderAABB;
|
||||
var mapCoords = coordinates.ToMap(pManager.EntityManager);
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
var mapCoords = coordinates.ToMap(pManager.EntityManager, transformSys);
|
||||
var (x, y) = mapCoords.Position;
|
||||
|
||||
var collisionBox = Box2.FromDimensions(
|
||||
@@ -261,7 +268,8 @@ namespace Robust.Client.Placement
|
||||
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
|
||||
}
|
||||
|
||||
return EntityCoordinates.FromMap(pManager.EntityManager, gridUid, mapCoords);
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
return EntityCoordinates.FromMap(gridUid, mapCoords, transformSys, pManager.EntityManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
_manager.Render(args.WorldHandle);
|
||||
_manager.Render(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,8 +261,8 @@ namespace Robust.Client.Player
|
||||
{
|
||||
// This is a new userid, so we create a new session.
|
||||
DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
|
||||
var newSession = (CommonSession) CreateAndAddSession(state.UserId, state.Name);
|
||||
newSession.Ping = state.Ping;
|
||||
var newSession = (ICommonSessionInternal)CreateAndAddSession(state.UserId, state.Name);
|
||||
newSession.SetPing(state.Ping);
|
||||
SetStatus(newSession, state.Status);
|
||||
SetAttachedEntity(newSession, controlled, out _, true);
|
||||
dirty = true;
|
||||
@@ -279,9 +279,9 @@ namespace Robust.Client.Player
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
var local = (CommonSession) session;
|
||||
local.Name = state.Name;
|
||||
local.Ping = state.Ping;
|
||||
var local = (ICommonSessionInternal)session;
|
||||
local.SetName(state.Name);
|
||||
local.SetPing(state.Ping);
|
||||
SetStatus(local, state.Status);
|
||||
SetAttachedEntity(local, controlled, out _, true);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,31 @@ namespace Robust.Client.Replays.Loading;
|
||||
// so that when jumping to tick 1001 the client only has to apply states for tick 1000 and 1001, instead of 0, 1, 2, ...
|
||||
public sealed partial class ReplayLoadManager
|
||||
{
|
||||
// Scratch data used by UpdateEntityStates.
|
||||
// Avoids copying changes for every change to an entity between checkpoints, instead copies once per checkpoint on
|
||||
// first change. We can also use this to avoid building a dictionary of ComponentChange inside the inner loop.
|
||||
private class UpdateScratchData
|
||||
{
|
||||
public Dictionary<ushort, ComponentChange> Changes;
|
||||
public EntityState lastChange;
|
||||
public HashSet<ushort>? netComps;
|
||||
|
||||
public UpdateScratchData(EntityState oldEntState)
|
||||
{
|
||||
Changes = oldEntState.ComponentChanges.Value.ToDictionary(x => x.NetID);
|
||||
lastChange = oldEntState;
|
||||
netComps = oldEntState.NetComponents;
|
||||
}
|
||||
|
||||
public EntityState BakeChanges()
|
||||
{
|
||||
return new EntityState(lastChange.NetEntity,
|
||||
Changes.Values.ToList(),
|
||||
lastChange.EntityLastModified,
|
||||
netComps);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(CheckpointState[], TimeSpan[])> GenerateCheckpointsAsync(
|
||||
ReplayMessage? initMessages,
|
||||
HashSet<string> initialCvars,
|
||||
@@ -88,7 +113,6 @@ public sealed partial class ReplayLoadManager
|
||||
if (initMessages != null)
|
||||
UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
|
||||
UpdateMessages(messages[0], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
|
||||
ProcessQueue(GameTick.MaxValue, detachQueue, detached);
|
||||
|
||||
var entSpan = state0.EntityStates.Value;
|
||||
Dictionary<NetEntity, EntityState> entStates = new(entSpan.Count);
|
||||
@@ -98,6 +122,8 @@ public sealed partial class ReplayLoadManager
|
||||
entStates.Add(entState.NetEntity, modifiedState);
|
||||
}
|
||||
|
||||
ProcessQueue(GameTick.MaxValue, detachQueue, detached, entStates);
|
||||
|
||||
await callback(0, states.Count, LoadingState.ProcessingFiles, true);
|
||||
var playerSpan = state0.PlayerStates.Value;
|
||||
Dictionary<NetUserId, SessionState> playerStates = new(playerSpan.Count);
|
||||
@@ -132,6 +158,12 @@ public sealed partial class ReplayLoadManager
|
||||
var spawnedTracker = 0;
|
||||
var stateTracker = 0;
|
||||
var curState = state0;
|
||||
|
||||
var stats_due_ticks = 0;
|
||||
var stats_due_spawned = 0;
|
||||
var stats_due_state = 0;
|
||||
|
||||
var modifiedEntities = new Dictionary<NetEntity, UpdateScratchData>();
|
||||
for (var i = 1; i < states.Count; i++)
|
||||
{
|
||||
if (i % 10 == 0)
|
||||
@@ -142,23 +174,42 @@ public sealed partial class ReplayLoadManager
|
||||
DebugTools.Assert(curState.FromSequence <= lastState.ToSequence);
|
||||
|
||||
UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
|
||||
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
|
||||
UpdateEntityStates(curState.EntityStates.Span, entStates, modifiedEntities, ref spawnedTracker, ref stateTracker, detached);
|
||||
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
|
||||
ProcessQueue(curState.ToSequence, detachQueue, detached);
|
||||
UpdateDeletions(curState.EntityDeletions, entStates, detached);
|
||||
ProcessQueue(curState.ToSequence, detachQueue, detached, entStates);
|
||||
UpdateDeletions(curState.EntityDeletions, entStates, detached, modifiedEntities);
|
||||
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
|
||||
ticksSinceLastCheckpoint++;
|
||||
|
||||
// Don't create checkpoints too frequently no matter the circumstance
|
||||
if (ticksSinceLastCheckpoint < _checkpointMinInterval)
|
||||
continue;
|
||||
|
||||
// Check if enough time, spawned entities or changed states have occurred.
|
||||
if (ticksSinceLastCheckpoint < _checkpointInterval
|
||||
&& spawnedTracker < _checkpointEntitySpawnThreshold
|
||||
&& stateTracker < _checkpointEntityStateThreshold)
|
||||
{
|
||||
continue;
|
||||
|
||||
// Track and update statistics about why checkpoints are getting created:
|
||||
if (ticksSinceLastCheckpoint >= _checkpointInterval)
|
||||
{
|
||||
stats_due_ticks += 1;
|
||||
}
|
||||
else if (spawnedTracker >= _checkpointEntitySpawnThreshold)
|
||||
{
|
||||
stats_due_spawned += 1;
|
||||
}
|
||||
else if (stateTracker >= _checkpointEntityStateThreshold)
|
||||
{
|
||||
stats_due_state += 1;
|
||||
}
|
||||
|
||||
ticksSinceLastCheckpoint = 0;
|
||||
spawnedTracker = 0;
|
||||
stateTracker = 0;
|
||||
ApplyModifiedEntities(entStates, modifiedEntities);
|
||||
|
||||
var newState = new GameState(GameTick.Zero,
|
||||
curState.ToSequence,
|
||||
default,
|
||||
@@ -168,7 +219,8 @@ public sealed partial class ReplayLoadManager
|
||||
checkPoints.Add(new CheckpointState(newState, timeBase, cvars, i, detached));
|
||||
}
|
||||
|
||||
_sawmill.Info($"Finished generating checkpoints. Elapsed time: {st.Elapsed}");
|
||||
_sawmill.Info($"Finished generating {checkPoints.Count} checkpoints. Elapsed time: {st.Elapsed}. Checkpoint every {(float)states.Count / checkPoints.Count} ticks on average");
|
||||
_sawmill.Info($"Checkpoint stats - Spawning: {stats_due_spawned} StateChanges: {stats_due_state} Ticks: {stats_due_ticks}. ");
|
||||
await callback(states.Count, states.Count, LoadingState.ProcessingFiles, false);
|
||||
return (checkPoints.ToArray(), serverTime);
|
||||
}
|
||||
@@ -176,14 +228,28 @@ public sealed partial class ReplayLoadManager
|
||||
private void ProcessQueue(
|
||||
GameTick curTick,
|
||||
Dictionary<GameTick, List<NetEntity>> detachQueue,
|
||||
HashSet<NetEntity> detached)
|
||||
HashSet<NetEntity> detached,
|
||||
Dictionary<NetEntity, EntityState> entStates)
|
||||
{
|
||||
foreach (var (tick, ents) in detachQueue)
|
||||
{
|
||||
if (tick > curTick)
|
||||
continue;
|
||||
detachQueue.Remove(tick);
|
||||
detached.UnionWith(ents);
|
||||
|
||||
foreach (var e in ents)
|
||||
{
|
||||
if (entStates.ContainsKey(e))
|
||||
detached.Add(e);
|
||||
else
|
||||
{
|
||||
// AFAIK this should only happen if the client skipped over some ticks, probably due to packet loss
|
||||
// I.e., entity was created on tick n, then leaves PVS range on the tick n+1
|
||||
// If the n-th tick gets dropped, the client only ever receives the pvs-leave message.
|
||||
// In that case we should just ignore it.
|
||||
_sawmill.Debug($"Received a PVS detach msg for entity {e} before it was received?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,16 +367,18 @@ public sealed partial class ReplayLoadManager
|
||||
}
|
||||
|
||||
private void UpdateDeletions(NetListAsArray<NetEntity> entityDeletions,
|
||||
Dictionary<NetEntity, EntityState> entStates, HashSet<NetEntity> detached)
|
||||
Dictionary<NetEntity, EntityState> entStates, HashSet<NetEntity> detached, Dictionary<NetEntity, UpdateScratchData> modifiedEntities)
|
||||
{
|
||||
foreach (var ent in entityDeletions.Span)
|
||||
{
|
||||
entStates.Remove(ent);
|
||||
detached.Remove(ent);
|
||||
modifiedEntities.Remove(ent);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateEntityStates(ReadOnlySpan<EntityState> span, Dictionary<NetEntity, EntityState> entStates,
|
||||
Dictionary<NetEntity, UpdateScratchData> modified,
|
||||
ref int spawnedTracker, ref int stateTracker, HashSet<NetEntity> detached)
|
||||
{
|
||||
foreach (var entState in span)
|
||||
@@ -325,25 +393,85 @@ public sealed partial class ReplayLoadManager
|
||||
#if DEBUG
|
||||
foreach (var state in modifiedState.ComponentChanges.Value)
|
||||
{
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta);
|
||||
}
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get scratch versions (with write access) for entities modified since last checkpoint
|
||||
UpdateScratchData? scratch;
|
||||
if (!modified.TryGetValue(entState.NetEntity, out scratch))
|
||||
{
|
||||
scratch = new UpdateScratchData(oldEntState);
|
||||
modified[entState.NetEntity] = scratch;
|
||||
}
|
||||
|
||||
stateTracker++;
|
||||
DebugTools.Assert(oldEntState.NetEntity == entState.NetEntity);
|
||||
entStates[entState.NetEntity] = MergeStates(entState, oldEntState.ComponentChanges.Value, oldEntState.NetComponents);
|
||||
// Note this does not change entStates, that change occurs later in ApplyModifiedEntities (to avoid early copies)
|
||||
UpdateScratch(entState, scratch.Changes);
|
||||
if (entState.NetComponents != null)
|
||||
scratch.netComps = entState.NetComponents;
|
||||
scratch.lastChange = entState;
|
||||
|
||||
|
||||
#if DEBUG
|
||||
foreach (var state in entStates[entState.NetEntity].ComponentChanges.Span)
|
||||
{
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyModifiedEntities(Dictionary<NetEntity, EntityState> entStates, Dictionary<NetEntity, UpdateScratchData> modifiedEntities)
|
||||
{
|
||||
foreach (var modified in modifiedEntities)
|
||||
{
|
||||
entStates[modified.Key] = modified.Value.BakeChanges();
|
||||
}
|
||||
|
||||
modifiedEntities.Clear();
|
||||
}
|
||||
|
||||
private void UpdateScratch(
|
||||
EntityState newState,
|
||||
Dictionary<ushort, ComponentChange> oldState)
|
||||
{
|
||||
// remove any deleted components
|
||||
if (newState.NetComponents != null)
|
||||
{
|
||||
foreach (var change in oldState.Values)
|
||||
{
|
||||
if (!newState.NetComponents.Contains(change.NetID))
|
||||
oldState.Remove(change.NetID);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var newCompState in newState.ComponentChanges.Value)
|
||||
{
|
||||
if (!oldState.TryGetValue(newCompState.NetID, out var existing))
|
||||
{
|
||||
// This is a new component
|
||||
// I'm not 100% sure about this, but I think delta states should always be full states here?
|
||||
DebugTools.Assert(newCompState.State is not IComponentDeltaState newDelta);
|
||||
oldState[newCompState.NetID] = newCompState;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Modify or replace existing component
|
||||
if (newCompState.State is not IComponentDeltaState delta)
|
||||
{
|
||||
oldState[newCompState.NetID] = newCompState;
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(existing.State != null && existing.State is not IComponentDeltaState);
|
||||
oldState[newCompState.NetID] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State!), newCompState.LastModifiedTick);
|
||||
}
|
||||
}
|
||||
|
||||
private EntityState MergeStates(
|
||||
EntityState newState,
|
||||
IReadOnlyCollection<ComponentChange> oldState,
|
||||
@@ -369,20 +497,20 @@ public sealed partial class ReplayLoadManager
|
||||
if (!newCompStates.Remove(existing.NetID, out var newCompState))
|
||||
continue;
|
||||
|
||||
if (newCompState.State is not IComponentDeltaState delta || delta.FullState)
|
||||
if (newCompState.State is not IComponentDeltaState delta)
|
||||
{
|
||||
combined[index] = newCompState;
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(existing.State is IComponentDeltaState fullDelta && fullDelta.FullState);
|
||||
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State), newCompState.LastModifiedTick);
|
||||
DebugTools.Assert(existing.State != null && existing.State is not IComponentDeltaState);
|
||||
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State!), newCompState.LastModifiedTick);
|
||||
}
|
||||
|
||||
foreach (var compChange in newCompStates.Values)
|
||||
{
|
||||
// I'm not 100% sure about this, but I think delta states should always be full states here?
|
||||
DebugTools.Assert(compChange.State is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(compChange.State is not IComponentDeltaState);
|
||||
combined.Add(compChange);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed partial class ReplayLoadManager
|
||||
continue;
|
||||
|
||||
var state = _entMan.GetComponentState(_entMan.EventBus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
list.Add(new ComponentChange(netId, state, GameTick.Zero));
|
||||
set.Add(netId);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public sealed partial class ReplayLoadManager
|
||||
{
|
||||
if (comp.NetID == _metaId)
|
||||
{
|
||||
var state = (MetaDataComponentState) comp.State;
|
||||
var state = (MetaDataComponentState) comp.State!;
|
||||
return state.PrototypeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public sealed partial class ReplayLoadManager
|
||||
return parsed.FirstOrDefault()?.Root as MappingDataNode;
|
||||
}
|
||||
|
||||
private (MappingDataNode YamlData, HashSet<string> CVars, TimeSpan Duration, TimeSpan StartTime, bool ClientSide)
|
||||
private (MappingDataNode YamlData, HashSet<string> CVars, TimeSpan? Duration, TimeSpan StartTime, bool ClientSide)
|
||||
LoadMetadata(IReplayFileReader fileReader)
|
||||
{
|
||||
_sawmill.Info($"Reading replay metadata");
|
||||
@@ -137,23 +137,16 @@ public sealed partial class ReplayLoadManager
|
||||
if (data == null)
|
||||
throw new Exception("Failed to load yaml metadata");
|
||||
|
||||
TimeSpan duration;
|
||||
var finalData = LoadYamlFinalMetadata(fileReader);
|
||||
TimeSpan? duration = finalData == null
|
||||
? null
|
||||
: TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
|
||||
|
||||
if (finalData == null)
|
||||
{
|
||||
var msg = "Failed to load final yaml metadata";
|
||||
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw new Exception(msg);
|
||||
_sawmill.Warning("Failed to load final yaml metadata. Partial/incomplete replay?");
|
||||
|
||||
_sawmill.Error(msg);
|
||||
duration = TimeSpan.FromDays(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
|
||||
}
|
||||
|
||||
var typeHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyTypeHash]).Value);
|
||||
var typeHashString = ((ValueDataNode) data[MetaKeyTypeHash]).Value;
|
||||
var typeHash = Convert.FromHexString(typeHashString);
|
||||
var stringHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyStringHash]).Value);
|
||||
var startTick = ((ValueDataNode) data[MetaKeyStartTick]).Value;
|
||||
var timeBaseTick = ((ValueDataNode) data[MetaKeyBaseTick]).Value;
|
||||
@@ -161,7 +154,12 @@ public sealed partial class ReplayLoadManager
|
||||
var clientSide = bool.Parse(((ValueDataNode) data[MetaKeyIsClientRecording]).Value);
|
||||
|
||||
if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash()))
|
||||
throw new Exception($"{nameof(IRobustSerializer)} hashes do not match. Loading replays using a bad replay-client version?");
|
||||
{
|
||||
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw new Exception($"RobustSerializer hash mismatch. do not match. Client hash: {_serializer.GetSerializableTypesHashString()}, replay hash: {typeHashString}.");
|
||||
|
||||
_sawmill.Warning($"RobustSerializer hash mismatch. Replay may fail to load!");
|
||||
}
|
||||
|
||||
using var stringFile = fileReader.Open(FileStrings);
|
||||
var stringData = new byte[stringFile.Length];
|
||||
|
||||
@@ -34,6 +34,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
|
||||
private ushort _metaId;
|
||||
private bool _initialized;
|
||||
private int _checkpointInterval;
|
||||
private int _checkpointMinInterval;
|
||||
private int _checkpointEntitySpawnThreshold;
|
||||
private int _checkpointEntityStateThreshold;
|
||||
private ISawmill _sawmill = default!;
|
||||
@@ -45,6 +46,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
|
||||
|
||||
_initialized = true;
|
||||
_confMan.OnValueChanged(CVars.CheckpointInterval, value => _checkpointInterval = value, true);
|
||||
_confMan.OnValueChanged(CVars.CheckpointMinInterval, value => _checkpointMinInterval = value, true);
|
||||
_confMan.OnValueChanged(CVars.CheckpointEntitySpawnThreshold, value => _checkpointEntitySpawnThreshold = value,
|
||||
true);
|
||||
_confMan.OnValueChanged(CVars.CheckpointEntityStateThreshold, value => _checkpointEntityStateThreshold = value,
|
||||
|
||||
@@ -17,7 +17,7 @@ internal sealed partial class ReplayPlaybackManager
|
||||
/// </summary>
|
||||
/// <param name="index">The target tick/index. The actual checkpoint will have an index less than or equal to this.</param>
|
||||
/// <param name="flushEntities">Whether to delete all entities</param>
|
||||
public void ResetToNearestCheckpoint(int index, bool flushEntities)
|
||||
public void ResetToNearestCheckpoint(int index, bool flushEntities, CheckpointState? checkpoint = null)
|
||||
{
|
||||
if (Replay == null)
|
||||
throw new Exception("Not currently playing a replay");
|
||||
@@ -25,7 +25,8 @@ internal sealed partial class ReplayPlaybackManager
|
||||
if (flushEntities)
|
||||
_entMan.FlushEntities();
|
||||
|
||||
var checkpoint = GetLastCheckpoint(Replay, index);
|
||||
// Look up the desired checkpoint, unless our caller kindly provided one to us.
|
||||
checkpoint ??= GetLastCheckpoint(Replay, index);
|
||||
|
||||
_sawmill.Info($"Resetting to checkpoint. From {Replay.CurrentIndex} to {checkpoint.Index}");
|
||||
var st = new Stopwatch();
|
||||
@@ -79,13 +80,14 @@ internal sealed partial class ReplayPlaybackManager
|
||||
if (checkpoint.DetachedStates == null)
|
||||
return;
|
||||
|
||||
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length); ;
|
||||
var metas = _entMan.GetEntityQuery<MetaDataComponent>();
|
||||
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length);
|
||||
foreach (var es in checkpoint.DetachedStates)
|
||||
{
|
||||
var uid = _entMan.GetEntity(es.NetEntity);
|
||||
if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted)
|
||||
if (_entMan.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
{
|
||||
DebugTools.Assert(!meta.EntityDeleted);
|
||||
continue;
|
||||
}
|
||||
|
||||
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?
|
||||
.FirstOrDefault(c => c.NetID == _metaId).State;
|
||||
@@ -93,18 +95,16 @@ internal sealed partial class ReplayPlaybackManager
|
||||
if (metaState == null)
|
||||
throw new MissingMetadataException(es.NetEntity);
|
||||
|
||||
_entMan.CreateEntityUninitialized(metaState.PrototypeId, uid);
|
||||
meta = metas.GetComponent(uid);
|
||||
uid = _entMan.CreateEntity(metaState.PrototypeId, out meta);
|
||||
|
||||
// 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.
|
||||
_entMan.ClearNetEntity(meta.NetEntity);
|
||||
_entMan.SetNetEntity(uid.Value, es.NetEntity, meta);
|
||||
|
||||
_entMan.SetNetEntity(uid, es.NetEntity, meta);
|
||||
|
||||
_entMan.InitializeEntity(uid, meta);
|
||||
_entMan.StartEntity(uid);
|
||||
_entMan.InitializeEntity(uid.Value, meta);
|
||||
_entMan.StartEntity(uid.Value);
|
||||
meta.LastStateApplied = checkpoint.Tick;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Replays.Playback;
|
||||
@@ -27,27 +28,35 @@ internal sealed partial class ReplayPlaybackManager
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Playing &= !pausePlayback;
|
||||
value = Math.Clamp(value, 0, Replay.States.Count - 1);
|
||||
if (value == Replay.CurrentIndex)
|
||||
{
|
||||
ScrubbingTarget = null;
|
||||
return;
|
||||
}
|
||||
|
||||
BeforeSetTick?.Invoke();
|
||||
|
||||
// Begin timing replay processing so we can abort when we run out of time (_replayMaxScrubTime)
|
||||
var st = RStopwatch.StartNew();
|
||||
|
||||
bool skipEffectEvents = value > Replay.CurrentIndex + _visualEventThreshold;
|
||||
if (value < Replay.CurrentIndex)
|
||||
{
|
||||
skipEffectEvents = true;
|
||||
ResetToNearestCheckpoint(value, false);
|
||||
}
|
||||
else if (value > Replay.CurrentIndex + _checkpointInterval)
|
||||
else if (value > Replay.CurrentIndex + _checkpointMinInterval)
|
||||
{
|
||||
// If we are skipping many ticks into the future, we try to skip directly to a checkpoint instead of
|
||||
// applying every tick.
|
||||
var nextCheckpoint = GetNextCheckpoint(Replay, Replay.CurrentIndex);
|
||||
if (nextCheckpoint.Index < value)
|
||||
ResetToNearestCheckpoint(value, false);
|
||||
|
||||
var nextCheckpoint = GetLastCheckpoint(Replay, value);
|
||||
// Sanity-Check that the checkpoint is actually BEFORE the desired position.
|
||||
// Also check that it gets us closer to goal position than we already are.
|
||||
if (nextCheckpoint.Index <= value && nextCheckpoint.Index > Replay.CurrentIndex)
|
||||
ResetToNearestCheckpoint(value, false, nextCheckpoint);
|
||||
}
|
||||
|
||||
_entMan.EntitySysManager.GetEntitySystem<ClientDirtySystem>().Reset();
|
||||
@@ -75,6 +84,23 @@ internal sealed partial class ReplayPlaybackManager
|
||||
DebugTools.Assert(Replay.LastApplied >= state.FromSequence);
|
||||
DebugTools.Assert(Replay.LastApplied + 1 <= state.ToSequence);
|
||||
Replay.LastApplied = state.ToSequence;
|
||||
|
||||
if (st.Elapsed.TotalMilliseconds > _replayMaxScrubTime)
|
||||
{
|
||||
// Out of time to advance replay this tick
|
||||
// Note: We check at end of loop so we always advance at least 1 tick.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Use ScrubbingTarget to force a later invocation to continue moving towards the target tick
|
||||
if (Replay.CurrentIndex < value)
|
||||
{
|
||||
ScrubbingTarget = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScrubbingTarget = null;
|
||||
}
|
||||
|
||||
AfterSetTick?.Invoke();
|
||||
|
||||
@@ -51,7 +51,8 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
|
||||
|
||||
public ReplayData? Replay { get; private set; }
|
||||
public NetUserId? Recorder => Replay?.Recorder;
|
||||
private int _checkpointInterval;
|
||||
private int _checkpointMinInterval;
|
||||
private int _replayMaxScrubTime;
|
||||
private int _visualEventThreshold;
|
||||
public uint? AutoPauseCountdown { get; set; }
|
||||
public int? ScrubbingTarget { get; set; }
|
||||
@@ -93,7 +94,8 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
|
||||
_initialized = true;
|
||||
_sawmill = _logMan.GetSawmill("replay");
|
||||
_metaId = _factory.GetRegistration(typeof(MetaDataComponent)).NetID!.Value;
|
||||
_confMan.OnValueChanged(CVars.CheckpointInterval, (value) => _checkpointInterval = value, true);
|
||||
_confMan.OnValueChanged(CVars.CheckpointMinInterval, (value) => _checkpointMinInterval = value, true);
|
||||
_confMan.OnValueChanged(CVars.ReplayMaxScrubTime, (value) => _replayMaxScrubTime = value, true);
|
||||
_confMan.OnValueChanged(CVars.ReplaySkipThreshold, (value) => _visualEventThreshold = value, true);
|
||||
_client.RunLevelChanged += OnRunLevelChanged;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user