mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
398 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fd15b0375 | ||
|
|
9293421f41 | ||
|
|
4f358b3a5e | ||
|
|
76a804d427 | ||
|
|
f8bf877b61 | ||
|
|
1f99f43755 | ||
|
|
457abf323b | ||
|
|
35d81974a8 | ||
|
|
c6a9113144 | ||
|
|
c76d1d8c5c | ||
|
|
0e289873f5 | ||
|
|
7e59febb47 | ||
|
|
fb7a231b78 | ||
|
|
85ded7a5a5 | ||
|
|
db996b9fb3 | ||
|
|
52b09cef71 | ||
|
|
26dcef4a11 | ||
|
|
9133879bd3 | ||
|
|
76b2909116 | ||
|
|
9dab8e77a7 | ||
|
|
0464b89cfe | ||
|
|
877e64f6be | ||
|
|
4df03f859b | ||
|
|
66435e6022 | ||
|
|
dc4f3b8f6f | ||
|
|
8377acb672 | ||
|
|
84c14a4657 | ||
|
|
1d5476e01b | ||
|
|
73d21da0dd | ||
|
|
81672fbe29 | ||
|
|
5655dea1c7 | ||
|
|
2616f3cedd | ||
|
|
511f272e96 | ||
|
|
3c7f87b8c3 | ||
|
|
7f89aabbb0 | ||
|
|
d6c9420a74 | ||
|
|
43e67c0db9 | ||
|
|
2e1aa16d38 | ||
|
|
849bcaeb74 | ||
|
|
dce320d0f5 | ||
|
|
d40804b41e | ||
|
|
90596c8979 | ||
|
|
36f582a712 | ||
|
|
163deff564 | ||
|
|
ca68041199 | ||
|
|
850d7ba032 | ||
|
|
4c5fefc3b2 | ||
|
|
84ee039f62 | ||
|
|
a84b481143 | ||
|
|
69fa2eed64 | ||
|
|
71f8275ae2 | ||
|
|
b89bf5739c | ||
|
|
e9ec2ee1d3 | ||
|
|
a44a25fb96 | ||
|
|
5356523305 | ||
|
|
2004ee9040 | ||
|
|
7b6d9bd719 | ||
|
|
287e57f717 | ||
|
|
add06753e9 | ||
|
|
8aa6fb478b | ||
|
|
5e7d617736 | ||
|
|
fa6c37778e | ||
|
|
4f92301d25 | ||
|
|
bd6be35b4d | ||
|
|
c39f6d09eb | ||
|
|
5eda1b326d | ||
|
|
5c3b058e81 | ||
|
|
7a06db60cf | ||
|
|
aafb15aff5 | ||
|
|
c437fadd25 | ||
|
|
bab37587c5 | ||
|
|
c4cd1fb6ee | ||
|
|
94e6886a85 | ||
|
|
e93ae73e50 | ||
|
|
9739eaa34a | ||
|
|
2aa5e8c07f | ||
|
|
f8b2412855 | ||
|
|
04782b83ab | ||
|
|
e2f2b3a26d | ||
|
|
b44a1cde88 | ||
|
|
af964b52ed | ||
|
|
ebca38b57a | ||
|
|
86d05dfa1e | ||
|
|
e10bd576ff | ||
|
|
20ff1e86c0 | ||
|
|
9d2a2ef4ba | ||
|
|
70d2457514 | ||
|
|
ba8deea700 | ||
|
|
f44d797057 | ||
|
|
e72d29a677 | ||
|
|
5730465c36 | ||
|
|
19f50d60c9 | ||
|
|
bbf5e92f32 | ||
|
|
419f4f36ab | ||
|
|
7769252109 | ||
|
|
3785777983 | ||
|
|
61aa73ffe3 | ||
|
|
ce11bf4564 | ||
|
|
0cf97780f0 | ||
|
|
fc2f6d4910 | ||
|
|
69158de99b | ||
|
|
7bec76d8d1 | ||
|
|
6e86f98406 | ||
|
|
f782e76671 | ||
|
|
5a8464b518 | ||
|
|
5f1feb9bb1 | ||
|
|
196e2bb427 | ||
|
|
f8bebee904 | ||
|
|
6515b08b41 | ||
|
|
6e2f18d0d8 | ||
|
|
a2ecd63e9d | ||
|
|
6da9176410 | ||
|
|
ae9b771c8c | ||
|
|
d1e206864c | ||
|
|
f812eb1e27 | ||
|
|
1dec0dd980 | ||
|
|
7316d9e950 | ||
|
|
f7c2305bce | ||
|
|
ebc0fc9c60 | ||
|
|
d157aab786 | ||
|
|
70a5d1bad6 | ||
|
|
2471bf8b4b | ||
|
|
e7c5706b04 | ||
|
|
7b6d8a1465 | ||
|
|
31b750bd5b | ||
|
|
6ebd0eb4ae | ||
|
|
37eac8c73f | ||
|
|
44649eea1c | ||
|
|
caa0212282 | ||
|
|
7f9d08c8f9 | ||
|
|
1c128e6b74 | ||
|
|
099e7c5c48 | ||
|
|
2f76908efb | ||
|
|
5adde7d588 | ||
|
|
7b1bb7df47 | ||
|
|
8e5eb6ebbb | ||
|
|
87ef010348 | ||
|
|
229d1c248b | ||
|
|
8db606c4e4 | ||
|
|
6b5181269b | ||
|
|
17b84c3520 | ||
|
|
42875fc101 | ||
|
|
72a7bc2ae7 | ||
|
|
bef4f75419 | ||
|
|
216509f89d | ||
|
|
b7991204f1 | ||
|
|
02987ac703 | ||
|
|
5cf8cb262f | ||
|
|
49dfca169c | ||
|
|
33008a2bce | ||
|
|
c2e90132c0 | ||
|
|
9859b5b090 | ||
|
|
20aec0a8f9 | ||
|
|
a9ee78e40d | ||
|
|
69f36aac6f | ||
|
|
173b41ab9e | ||
|
|
7796d7f065 | ||
|
|
175c111be9 | ||
|
|
279cc0f83f | ||
|
|
b334d927a5 | ||
|
|
bdb9b9af2b | ||
|
|
5aa634b5eb | ||
|
|
60d7430fe7 | ||
|
|
a9aeff9b78 | ||
|
|
e7a0409645 | ||
|
|
3e718575ff | ||
|
|
746ec9eab7 | ||
|
|
60e6ecb0cc | ||
|
|
3a00e0d497 | ||
|
|
24a5020b42 | ||
|
|
bbb9e94ce9 | ||
|
|
2bea9576f0 | ||
|
|
6e0be2b8c9 | ||
|
|
0a348dd1be | ||
|
|
a95283913b | ||
|
|
ebc73a71c2 | ||
|
|
05321f0381 | ||
|
|
6554144c42 | ||
|
|
28e6cdc92f | ||
|
|
2ec715b70e | ||
|
|
424e0768c8 | ||
|
|
e1b9327ec0 | ||
|
|
80308a799f | ||
|
|
e30f8f3e69 | ||
|
|
302b910cf3 | ||
|
|
5a6a16e7dc | ||
|
|
8d84c56a72 | ||
|
|
13f821be5f | ||
|
|
d647ab1c61 | ||
|
|
b902ef290a | ||
|
|
058d897529 | ||
|
|
8e173a7a18 | ||
|
|
5443f77526 | ||
|
|
b406526592 | ||
|
|
48697da450 | ||
|
|
06f20ea722 | ||
|
|
427378f94d | ||
|
|
6cf5021efa | ||
|
|
ad9bda2efe | ||
|
|
f0bf251acf | ||
|
|
21f5fed32f | ||
|
|
cc67a47c32 | ||
|
|
e78e7bacfe | ||
|
|
6301008ac3 | ||
|
|
dfd572a0aa | ||
|
|
97fab99971 | ||
|
|
25d6bd908b | ||
|
|
fd5a8bf207 | ||
|
|
772076826a | ||
|
|
52d669e032 | ||
|
|
75fc9089c3 | ||
|
|
0972601a43 | ||
|
|
603c252c48 | ||
|
|
d5b1c044b7 | ||
|
|
4600f0531d | ||
|
|
c88498eca9 | ||
|
|
f15f1eb345 | ||
|
|
5be3ced05a | ||
|
|
7f03e88e97 | ||
|
|
8e3fa3e52d | ||
|
|
f9ae3e1fc2 | ||
|
|
bf9e95fa8a | ||
|
|
030a7d265b | ||
|
|
df70e94743 | ||
|
|
d68cd4d7eb | ||
|
|
d098881bff | ||
|
|
b8fbe32c27 | ||
|
|
02d2bd31e7 | ||
|
|
bd0dba0df0 | ||
|
|
17164ead34 | ||
|
|
079e099a3b | ||
|
|
1c8ed1c5b2 | ||
|
|
d4cdb7ff3b | ||
|
|
31d301b91f | ||
|
|
07c65474cb | ||
|
|
8c3c8fceea | ||
|
|
ee894d8a8e | ||
|
|
ce169ed15e | ||
|
|
9b215098e4 | ||
|
|
78d01fd25d | ||
|
|
8e8bfbe0cc | ||
|
|
45b0a49ffb | ||
|
|
58638c9109 | ||
|
|
2f2a397ecf | ||
|
|
78c551d854 | ||
|
|
679e07d9ea | ||
|
|
fa5d0235ec | ||
|
|
04d029b9a2 | ||
|
|
6e84821233 | ||
|
|
881cfeb9a9 | ||
|
|
0187e85700 | ||
|
|
dee8a87acd | ||
|
|
d4a7e8f3e0 | ||
|
|
dae6424667 | ||
|
|
3770149cfc | ||
|
|
341c279265 | ||
|
|
37c20723ab | ||
|
|
c3e4a64ad7 | ||
|
|
225446920a | ||
|
|
9b2a50b1a8 | ||
|
|
b4ed513e8c | ||
|
|
c28f2d77c3 | ||
|
|
3e344d00a8 | ||
|
|
c321400347 | ||
|
|
a4ff5d65ec | ||
|
|
97c70124a5 | ||
|
|
cdcc5239ab | ||
|
|
ba2f464249 | ||
|
|
4210f30460 | ||
|
|
9fc95591d9 | ||
|
|
0a59079a4a | ||
|
|
89f168c04d | ||
|
|
f11ac39cd5 | ||
|
|
380de8c4c3 | ||
|
|
539c161ea3 | ||
|
|
b07187459b | ||
|
|
97fb54b6d7 | ||
|
|
fe30d974ca | ||
|
|
884fade25a | ||
|
|
a3ab745121 | ||
|
|
4e0ad23272 | ||
|
|
b9b9cd0711 | ||
|
|
b05b1a7c86 | ||
|
|
4ced901358 | ||
|
|
898d5a3ba5 | ||
|
|
068c05c355 | ||
|
|
cd693875e6 | ||
|
|
e2723a83b3 | ||
|
|
f6ac8fbe1f | ||
|
|
a1e0f18bd6 | ||
|
|
1e7a481911 | ||
|
|
331f14b31a | ||
|
|
88660e1958 | ||
|
|
d9af00c931 | ||
|
|
b8cb037151 | ||
|
|
32d8bd8600 | ||
|
|
f5a1e06f21 | ||
|
|
48a5cb75d6 | ||
|
|
3e6b5a7739 | ||
|
|
9ac0aa4cd6 | ||
|
|
50fb8ca9e5 | ||
|
|
7da89765ac | ||
|
|
ce58c6688f | ||
|
|
2f03640200 | ||
|
|
55fecc95a9 | ||
|
|
8b54ad79cf | ||
|
|
88dec23a03 | ||
|
|
079702347d | ||
|
|
d18bf3d5ac | ||
|
|
ed832748b1 | ||
|
|
6491e51f3d | ||
|
|
58e7fb3a17 | ||
|
|
a9c7926226 | ||
|
|
241dc0b752 | ||
|
|
566948f1c0 | ||
|
|
dbba440f7e | ||
|
|
5b1e9eec27 | ||
|
|
8ef95f0199 | ||
|
|
b71e8f140a | ||
|
|
f8397099de | ||
|
|
5bbf1703ac | ||
|
|
1357e38759 | ||
|
|
655ecbab45 | ||
|
|
a0ef63bd4a | ||
|
|
f34763f11e | ||
|
|
b1f6b4cbe0 | ||
|
|
94708881b3 | ||
|
|
0f6dbac51c | ||
|
|
e67de121ca | ||
|
|
17979db216 | ||
|
|
8d0070b5c3 | ||
|
|
034c392cbe | ||
|
|
160bbc3a72 | ||
|
|
3a4d228e94 | ||
|
|
57f57f9d9f | ||
|
|
0592444252 | ||
|
|
d8499f2e60 | ||
|
|
476b5182f8 | ||
|
|
6c7ab1bd82 | ||
|
|
526ed31b0d | ||
|
|
3ac5552276 | ||
|
|
7a51c22514 | ||
|
|
aa339eb504 | ||
|
|
ffb3800664 | ||
|
|
39eb1a7d75 | ||
|
|
c25ab2fcb1 | ||
|
|
22e0fbc6c1 | ||
|
|
72071826a4 | ||
|
|
8b9c1ab9a1 | ||
|
|
16d5da5414 | ||
|
|
39f82694bf | ||
|
|
79c3c09851 | ||
|
|
ad7370f0b3 | ||
|
|
75cd8a0a12 | ||
|
|
915a812832 | ||
|
|
c8e7fe9f1f | ||
|
|
9803ed9cad | ||
|
|
f5f8a59c86 | ||
|
|
f4e3dfa601 | ||
|
|
9305b261bb | ||
|
|
3a161af4a5 | ||
|
|
0447d8d3b9 | ||
|
|
c2eed5c007 | ||
|
|
d9464e2621 | ||
|
|
1a70813f3c | ||
|
|
a3a69f821f | ||
|
|
c72988b05b | ||
|
|
11bc9c0fe4 | ||
|
|
98593b7b33 | ||
|
|
4b30a94126 | ||
|
|
1c8958d312 | ||
|
|
ef2f81a77a | ||
|
|
3935c63a80 | ||
|
|
76ab68dc3d | ||
|
|
a986292aa2 | ||
|
|
082fac52cd | ||
|
|
a60993c60d | ||
|
|
97ffc9ecc2 | ||
|
|
ab4f2c91b4 | ||
|
|
5f2cc942cb | ||
|
|
5a343477a9 | ||
|
|
7bc847c9a4 | ||
|
|
cfc7f26100 | ||
|
|
6eb3989362 | ||
|
|
04fd324e4a | ||
|
|
c9e999d024 | ||
|
|
243f405bab | ||
|
|
2353f0ed6f | ||
|
|
8be4b39449 | ||
|
|
e2c7d95519 | ||
|
|
0e6c00573f | ||
|
|
58ea614c1f | ||
|
|
5cbad8b3d9 | ||
|
|
e7e08e5dd6 | ||
|
|
9ac8db37cc | ||
|
|
2d6eebfae2 | ||
|
|
5540d643ef | ||
|
|
76c1d9f97b |
@@ -1,52 +0,0 @@
|
||||
environment:
|
||||
sonarqubekey:
|
||||
secure: h3llq6OeVa94hJ71UOEQSQDq75vFt+doso7iFry0gvt/fFcyeonY9wY+ETOIVITK
|
||||
global:
|
||||
PYTHONUNBUFFERED: True
|
||||
HEADLESS: 1 # For the unit tests.
|
||||
|
||||
version: 0.1.0.{build}
|
||||
pull_requests:
|
||||
do_not_increment_build_number: true
|
||||
|
||||
image: Visual Studio 2019
|
||||
install:
|
||||
- ps: >
|
||||
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
|
||||
{
|
||||
cinst msbuild-sonarqube-runner;
|
||||
}
|
||||
|
||||
before_build:
|
||||
- cmd: py -3.5 -m pip install --user requests
|
||||
- cmd: git submodule update --init --recursive
|
||||
- ps: >
|
||||
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
|
||||
{
|
||||
SonarScanner.MSBuild.exe begin /k:"ss14" /d:"sonar.host.url=https://sonarcloud.io" /d:"sonar.login=$env:sonarqubekey" /o:"space-wizards" /d:sonar.cs.nunit.reportsPaths="$(Get-Location)\nunitTestResult.xml";
|
||||
}
|
||||
|
||||
platform: x64
|
||||
configuration: Debug
|
||||
|
||||
cache:
|
||||
- packages -> **\*.csproj
|
||||
- Dependencies
|
||||
|
||||
build:
|
||||
project: RobustToolbox.sln
|
||||
parallel: false
|
||||
verbosity: minimal
|
||||
|
||||
build_script:
|
||||
- ps: dotnet build RobustToolbox.sln /p:AppVeyor=yes
|
||||
|
||||
test_script:
|
||||
- ps: dotnet test Robust.UnitTesting/Robust.UnitTesting.csproj
|
||||
|
||||
after_test:
|
||||
- ps: >
|
||||
if (-Not $env:APPVEYOR_PULL_REQUEST_NUMBER -And $env:APPVEYOR_REPO_BRANCH -Eq "master")
|
||||
{
|
||||
SonarScanner.MSBuild.exe end /d:"sonar.login=$env:sonarqubekey";
|
||||
}
|
||||
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.100
|
||||
dotnet-version: 6.0.100
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.100
|
||||
dotnet-version: 6.0.100
|
||||
|
||||
- name: Build
|
||||
run: dotnet build
|
||||
|
||||
2
.github/workflows/publish-client.yml
vendored
2
.github/workflows/publish-client.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.100
|
||||
dotnet-version: 6.0.100
|
||||
|
||||
- name: Package client
|
||||
run: Tools/package_client_build.py -p windows mac linux
|
||||
|
||||
4
.github/workflows/test-content.yml
vendored
4
.github/workflows/test-content.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.100
|
||||
dotnet-version: 6.0.100
|
||||
- name: Disable submodule autoupdate
|
||||
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
|
||||
|
||||
@@ -38,4 +38,4 @@ jobs:
|
||||
- name: Content.Tests
|
||||
run: dotnet test --no-build Content.Tests/Content.Tests.csproj -v n
|
||||
- name: Content.IntegrationTests
|
||||
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
run: COMPlus_gcServer=1 dotnet test --no-build Content.IntegrationTests/Content.IntegrationTests.csproj -v n
|
||||
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -18,4 +18,4 @@
|
||||
url = https://github.com/space-wizards/Linguini
|
||||
[submodule "cefglue"]
|
||||
path = cefglue
|
||||
url = https://gitlab.com/xiliumhq/chromiumembedded/cefglue/
|
||||
url = https://github.com/space-wizards/cefglue.git
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 4b36b36f39...1dd5c1f333
@@ -3,10 +3,10 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<DefineConstants Condition="'$(TargetFramework)' == 'netstandard2.1' or '$(TargetFramework)' == 'netcoreapp3.1'">$(DefineConstants);HAS_FULL_SPAN</DefineConstants>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
<DefaultItemExcludes>Lidgren.Network/**/*</DefaultItemExcludes>
|
||||
<DefineConstants>$(DefineConstants);USE_RELEASE_STATISTICS</DefineConstants>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
4
MSBuild/Robust.Engine.Version.props
Normal file
4
MSBuild/Robust.Engine.Version.props
Normal file
@@ -0,0 +1,4 @@
|
||||
<Project>
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<PropertyGroup><Version>0.8.0</Version></PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,9 +1,10 @@
|
||||
<Project>
|
||||
<!-- Engine-specific properties. Content should not use this file. -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<Import Project="Robust.Engine.Version.props" />
|
||||
</Project>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<TargetOS Condition="'$(TargetOS)' == ''">$(ActualOS)</TargetOS>
|
||||
<Python>python3</Python>
|
||||
<Python Condition="'$(ActualOS)' == 'Windows'">py -3</Python>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<EnableClientScripting>True</EnableClientScripting>
|
||||
<!-- Client scripting is disabled on full release builds for security and size reasons. -->
|
||||
<EnableClientScripting Condition="'$(FullRelease)' == 'True'">False</EnableClientScripting>
|
||||
|
||||
122
MSBuild/Robust.Trimming.targets
Normal file
122
MSBuild/Robust.Trimming.targets
Normal file
@@ -0,0 +1,122 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
|
||||
<!--
|
||||
Stuff for using ILLink trimming without self-contained deployments.
|
||||
This is not something officially supported by the .NET SDK currently, but we can simply run ILLink ourselves.
|
||||
-->
|
||||
|
||||
<!--
|
||||
A lot of stuff taken from Microsoft.NET.ILLink.targets in the SDK files.
|
||||
-->
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<RobustLinkRoots>
|
||||
<Visible>false</Visible>
|
||||
</RobustLinkRoots>
|
||||
<RobustLinkAssemblies>
|
||||
<Visible>false</Visible>
|
||||
</RobustLinkAssemblies>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<Target Name="RobustILLink"
|
||||
BeforeTargets="ILLink"
|
||||
Condition="'$(PublishTrimmed)' != 'true' And
|
||||
'$(RobustILLink)' == 'true'"
|
||||
DependsOnTargets="_ComputeAssembliesToPostprocessOnPublish">
|
||||
|
||||
<ComputeManagedAssemblies Assemblies="@(ResolvedFileToPublish)">
|
||||
<Output TaskParameter="ManagedAssemblies" ItemName="_ResolvedFileToPublishFiltered" />
|
||||
</ComputeManagedAssemblies>
|
||||
|
||||
<JoinItems Left="@(_ResolvedFileToPublishFiltered)" LeftKey="FileName" LeftMetadata="*"
|
||||
Right="@(RobustLinkRoots)"
|
||||
ItemSpecToUse="Left">
|
||||
<Output TaskParameter="JoinResult" ItemName="_RobustLinkRootsJoined" />
|
||||
</JoinItems>
|
||||
|
||||
<JoinItems Left="@(_ResolvedFileToPublishFiltered)" LeftKey="FileName" LeftMetadata="*"
|
||||
Right="@(RobustLinkAssemblies)"
|
||||
ItemSpecToUse="Left">
|
||||
<Output TaskParameter="JoinResult" ItemName="_RobustLinkAssembliesJoined" />
|
||||
</JoinItems>
|
||||
|
||||
<PropertyGroup>
|
||||
<TrimMode Condition=" '$(TrimMode)' == '' ">link</TrimMode>
|
||||
<TrimmerDefaultAction Condition=" '$(TrimmerDefaultAction)' == '' ">copy</TrimmerDefaultAction>
|
||||
<_ExtraTrimmerArgs>--skip-unresolved true $(_ExtraTrimmerArgs)</_ExtraTrimmerArgs>
|
||||
<ILLinkTreatWarningsAsErrors Condition=" '$(ILLinkTreatWarningsAsErrors)' == '' ">$(TreatWarningsAsErrors)</ILLinkTreatWarningsAsErrors>
|
||||
<TrimmerSingleWarn Condition=" '$(TrimmerSingleWarn)' == '' ">true</TrimmerSingleWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<RobustAssemblyToLink Include="@(_RobustLinkRootsJoined)">
|
||||
<TrimMode>Copy</TrimMode>
|
||||
</RobustAssemblyToLink>
|
||||
<RobustAssemblyToLink Include="@(_RobustLinkAssembliesJoined)">
|
||||
<TrimMode>Link</TrimMode>
|
||||
</RobustAssemblyToLink>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- The linker implicitly picks up PDBs next to input assemblies. We will filter these out of the publish set. -->
|
||||
<__PDBToLink Include="@(ResolvedFileToPublish)" Exclude="@(RobustAssemblyToLink->'%(RelativeDir)%(Filename).pdb')" />
|
||||
<_PDBToLink Include="@(ResolvedFileToPublish)" Exclude="@(__PDBToLink)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_LinkedResolvedFileToPublishCandidate Include="@(RobustAssemblyToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')" />
|
||||
<_LinkedResolvedFileToPublishCandidate Include="@(_PDBToLink->'$(IntermediateLinkDir)%(Filename)%(Extension)')" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--<Message Text="@(ResolvedFileToPublish)" Importance="high" />-->
|
||||
|
||||
<ItemGroup>
|
||||
<_TrimmerFeatureSettings Include="@(RuntimeHostConfigurationOption)"
|
||||
Condition="'%(RuntimeHostConfigurationOption.Trim)' == 'true'" />
|
||||
</ItemGroup>
|
||||
|
||||
<Delete Files="@(_LinkedResolvedFileToPublishCandidate)" />
|
||||
<ILLink AssemblyPaths="@(RobustAssemblyToLink)"
|
||||
ReferenceAssemblyPaths="@(ReferencePath)"
|
||||
RootAssemblyNames="@(RobustLinkRoots)"
|
||||
TrimMode="Skip"
|
||||
DefaultAction="$(TrimmerDefaultAction)"
|
||||
RemoveSymbols="false"
|
||||
FeatureSettings="@(_TrimmerFeatureSettings)"
|
||||
CustomData="@(_TrimmerCustomData)"
|
||||
|
||||
BeforeFieldInit="$(_TrimmerBeforeFieldInit)"
|
||||
OverrideRemoval="$(_TrimmerOverrideRemoval)"
|
||||
UnreachableBodies="$(_TrimmerUnreachableBodies)"
|
||||
UnusedInterfaces="$(_TrimmerUnusedInterfaces)"
|
||||
IPConstProp="$(_TrimmerIPConstProp)"
|
||||
Sealer="$(_TrimmerSealer)"
|
||||
|
||||
Warn="$(ILLinkWarningLevel)"
|
||||
NoWarn="$(NoWarn)"
|
||||
TreatWarningsAsErrors="$(ILLinkTreatWarningsAsErrors)"
|
||||
WarningsAsErrors="$(WarningsAsErrors)"
|
||||
WarningsNotAsErrors="$(WarningsNotAsErrors)"
|
||||
SingleWarn="$(TrimmerSingleWarn)"
|
||||
|
||||
CustomSteps="@(_TrimmerCustomSteps)"
|
||||
RootDescriptorFiles="@(TrimmerRootDescriptor)"
|
||||
OutputDirectory="$(IntermediateLinkDir)"
|
||||
DumpDependencies="$(_TrimmerDumpDependencies)"
|
||||
ExtraArgs="$(_ExtraTrimmerArgs)"
|
||||
ToolExe="$(_DotNetHostFileName)"
|
||||
ToolPath="$(_DotNetHostDirectory)"
|
||||
ContinueOnError="ErrorAndContinue">
|
||||
<Output TaskParameter="ExitCode" PropertyName="_ILLinkExitCode" />
|
||||
</ILLink>
|
||||
|
||||
<Touch Files="$(_LinkSemaphore)" AlwaysCreate="true" Condition=" '$(_ILLinkExitCode)' == '0' " />
|
||||
|
||||
<ItemGroup>
|
||||
<_LinkedResolvedFileToPublish Include="@(_LinkedResolvedFileToPublishCandidate)" Condition="Exists('%(Identity)')" />
|
||||
<ResolvedFileToPublish Remove="@(RobustAssemblyToLink)" />
|
||||
<ResolvedFileToPublish Remove="@(_PDBToLink)" />
|
||||
<ResolvedFileToPublish Include="@(_LinkedResolvedFileToPublish)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
Submodule NetSerializer updated: ab1b651292...b75b301bea
@@ -18,20 +18,18 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
NativeLibrary.SetDllImportResolver(typeof(GLFWNative).Assembly, (name, assembly, path) =>
|
||||
{
|
||||
// Please keep in sync with what Robust.Shared/DllMapHelper.cs does.
|
||||
|
||||
if (name != "glfw3.dll")
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.so.3", assembly, path);
|
||||
}
|
||||
string rName = null;
|
||||
if (OperatingSystem.IsLinux()) rName = "libglfw.so.3";
|
||||
else if (OperatingSystem.IsMacOS()) rName = "libglfw.3.dylib";
|
||||
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return NativeLibrary.Load("libglfw.3.dylib", assembly, path);
|
||||
}
|
||||
if ((rName != null) && NativeLibrary.TryLoad(rName, assembly, path, out var handle))
|
||||
return handle;
|
||||
|
||||
return IntPtr.Zero;
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ Use the [content repo](https://github.com/space-wizards/space-station-14) for ac
|
||||
|
||||
## Documentation/Wiki
|
||||
|
||||
The [HackMD Wiki](https://hackmd.io/@ss14/docs/wiki) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
|
||||
The [wiki](https://docs.spacestation14.io/) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -7,3 +7,8 @@
|
||||
- type: shader
|
||||
id: shaded
|
||||
kind: canvas
|
||||
|
||||
- type: shader
|
||||
id: bgra
|
||||
kind: source
|
||||
path: "/Shaders/Internal/bgra.swsl"
|
||||
|
||||
6
Resources/Shaders/Internal/bgra.swsl
Normal file
6
Resources/Shaders/Internal/bgra.swsl
Normal file
@@ -0,0 +1,6 @@
|
||||
// Swaps B and R channel so you can render BGRA stuff without swizzling at upload time.
|
||||
// Currently used by CEF.
|
||||
|
||||
void fragment() {
|
||||
COLOR = zTexture(UV).bgra;
|
||||
}
|
||||
@@ -2,29 +2,15 @@ preset raw;
|
||||
|
||||
#include "/Shaders/Internal/shadow_cast_shared.swsl"
|
||||
|
||||
#include "/Shaders/Internal/fov_shared.swsl"
|
||||
|
||||
const highp float g_MinVariance = 0.0;
|
||||
|
||||
varying highp vec2 worldPosition;
|
||||
|
||||
// Center of the FOV, in world coordinates.
|
||||
uniform highp vec2 center;
|
||||
|
||||
void vertex()
|
||||
{
|
||||
highp vec3 transformed = modelMatrix * vec3(VERTEX, 1.0);
|
||||
worldPosition = transformed.xy;
|
||||
transformed = projectionMatrix * viewMatrix * transformed;
|
||||
|
||||
VERTEX = transformed.xy;
|
||||
}
|
||||
|
||||
void fragment()
|
||||
{
|
||||
highp vec2 diff = worldPosition - center;
|
||||
highp float ourDist = length(worldSpaceDiff);
|
||||
|
||||
highp float ourDist = length(diff);
|
||||
|
||||
highp vec2 occlDist = occludeDepth(diff, TEXTURE, 0.25);
|
||||
highp vec2 occlDist = occludeDepth(worldSpaceDiff, TEXTURE, 0.25);
|
||||
|
||||
highp float occlusion = ChebyshevUpperBound(occlDist, ourDist);
|
||||
|
||||
|
||||
@@ -2,32 +2,18 @@ preset raw;
|
||||
|
||||
#include "/Shaders/Internal/shadow_cast_shared.swsl"
|
||||
|
||||
#include "/Shaders/Internal/fov_shared.swsl"
|
||||
|
||||
const highp float g_MinVariance = 0.0;
|
||||
|
||||
varying highp vec2 worldPosition;
|
||||
|
||||
// Center of the FOV, in world coordinates.
|
||||
uniform highp vec2 center;
|
||||
|
||||
void vertex()
|
||||
{
|
||||
highp vec3 transformed = modelMatrix * vec3(VERTEX, 1.0);
|
||||
worldPosition = transformed.xy;
|
||||
transformed = projectionMatrix * viewMatrix * transformed;
|
||||
|
||||
VERTEX = transformed.xy;
|
||||
}
|
||||
|
||||
void fragment()
|
||||
{
|
||||
highp vec2 diff = worldPosition - center;
|
||||
highp float ourDist = length(worldSpaceDiff);
|
||||
|
||||
highp float ourDist = length(diff);
|
||||
|
||||
highp float occlDist = occludeDepth(diff, TEXTURE, 0.75).r;
|
||||
highp float occlDist = occludeDepth(worldSpaceDiff, TEXTURE, 0.75).r;
|
||||
|
||||
// *Very* simple biased shadow check for FOV.
|
||||
if (!doesOcclude(diff, TEXTURE, 0.75, -0.75/32.0))
|
||||
if (!doesOcclude(worldSpaceDiff, TEXTURE, 0.75, -0.75/32.0))
|
||||
{
|
||||
discard;
|
||||
}
|
||||
|
||||
16
Resources/Shaders/Internal/fov_shared.swsl
Normal file
16
Resources/Shaders/Internal/fov_shared.swsl
Normal file
@@ -0,0 +1,16 @@
|
||||
// Shared between fov-lighting.swsl and fov.swsl, which both use the Clyde quad,
|
||||
// manually transformed into clip-space to cover the entire viewport
|
||||
|
||||
// World-space position offset from centre to pixel.
|
||||
varying highp vec2 worldSpaceDiff;
|
||||
|
||||
// Inverted transformation matrix from clip coordinates to difference coordinates.
|
||||
uniform highp mat3 clipToDiff;
|
||||
|
||||
void vertex()
|
||||
{
|
||||
// Convert quad-space (0.0 to 1.0) to clip-space (-1.0 to 1.0)
|
||||
VERTEX = (VERTEX.xy - 0.5) * 2.0;
|
||||
worldSpaceDiff = (clipToDiff * vec3(VERTEX, 1.0)).xy;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ void fragment()
|
||||
highp vec2 diff = worldPosition - lightCenter;
|
||||
|
||||
// Totally not hacky PCF on top of VSM.
|
||||
highp float occlusion = createOcclusion(diff);
|
||||
highp float occlusion = lightIndex < 0.0 ? 1.0 : createOcclusion(diff);
|
||||
|
||||
if (occlusion == 0.0)
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -92,7 +93,7 @@ namespace Robust.Analyzers
|
||||
continue;
|
||||
|
||||
// If we find that the containing class is specified in the attribute, return! All is good.
|
||||
if (SymbolEqualityComparer.Default.Equals(containingType, t))
|
||||
if (InheritsFromOrEquals(containingType, t))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -103,5 +104,26 @@ namespace Robust.Analyzers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool InheritsFromOrEquals(INamedTypeSymbol type, INamedTypeSymbol baseType)
|
||||
{
|
||||
foreach (var otherType in GetBaseTypesAndThis(type))
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(otherType, baseType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<INamedTypeSymbol> GetBaseTypesAndThis(INamedTypeSymbol namedType)
|
||||
{
|
||||
var current = namedType;
|
||||
while (current != null)
|
||||
{
|
||||
yield return current;
|
||||
current = current.BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Running;
|
||||
using System;
|
||||
|
||||
namespace Robust.Benchmarks
|
||||
{
|
||||
@@ -8,7 +10,14 @@ namespace Robust.Benchmarks
|
||||
// --anyCategories=ctg1,ctg2
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
#if DEBUG
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("\nWARNING: YOU ARE RUNNING A DEBUG BUILD, USE A RELEASE BUILD FOR AN ACCURATE BENCHMARK");
|
||||
Console.WriteLine("THE DEBUG BUILD IS ONLY GOOD FOR FIXING A CRASHING BENCHMARK\n");
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new DebugInProcessConfig());
|
||||
#else
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,593 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Xilium.CefGlue;
|
||||
using static Robust.Client.CEF.CefKeyCodes;
|
||||
using static Robust.Client.CEF.CefKeyCodes.ChromiumKeyboardCode;
|
||||
using static Robust.Client.Input.Keyboard;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Funny browser control to integrate in UI.
|
||||
public class BrowserControl : Control, IBrowserControl, IRawInputControl
|
||||
{
|
||||
private const int ScrollSpeed = 50;
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IInputManager _inputMgr = default!;
|
||||
[Dependency] private readonly CefManager _cef = default!;
|
||||
|
||||
private RobustRequestHandler _requestHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
|
||||
private LiveData? _data;
|
||||
private string _startUrl = "about:blank";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
{
|
||||
get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url;
|
||||
set
|
||||
{
|
||||
if (_data == null)
|
||||
_startUrl = value;
|
||||
else
|
||||
_data.Browser.GetMainFrame().LoadUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] public bool IsLoading => _data?.Browser.IsLoading ?? false;
|
||||
|
||||
private readonly Dictionary<Key, ChromiumKeyboardCode> _keyMap = new()
|
||||
{
|
||||
[Key.A] = VKEY_A,
|
||||
[Key.B] = VKEY_B,
|
||||
[Key.C] = VKEY_C,
|
||||
[Key.D] = VKEY_D,
|
||||
[Key.E] = VKEY_E,
|
||||
[Key.F] = VKEY_F,
|
||||
[Key.G] = VKEY_G,
|
||||
[Key.H] = VKEY_H,
|
||||
[Key.I] = VKEY_I,
|
||||
[Key.J] = VKEY_J,
|
||||
[Key.K] = VKEY_K,
|
||||
[Key.L] = VKEY_L,
|
||||
[Key.M] = VKEY_M,
|
||||
[Key.N] = VKEY_N,
|
||||
[Key.O] = VKEY_O,
|
||||
[Key.P] = VKEY_P,
|
||||
[Key.Q] = VKEY_Q,
|
||||
[Key.R] = VKEY_R,
|
||||
[Key.S] = VKEY_S,
|
||||
[Key.T] = VKEY_T,
|
||||
[Key.U] = VKEY_U,
|
||||
[Key.V] = VKEY_V,
|
||||
[Key.W] = VKEY_W,
|
||||
[Key.X] = VKEY_X,
|
||||
[Key.Y] = VKEY_Y,
|
||||
[Key.Z] = VKEY_Z,
|
||||
[Key.Num0] = VKEY_0,
|
||||
[Key.Num1] = VKEY_1,
|
||||
[Key.Num2] = VKEY_2,
|
||||
[Key.Num3] = VKEY_3,
|
||||
[Key.Num4] = VKEY_4,
|
||||
[Key.Num5] = VKEY_5,
|
||||
[Key.Num6] = VKEY_6,
|
||||
[Key.Num7] = VKEY_7,
|
||||
[Key.Num8] = VKEY_8,
|
||||
[Key.Num9] = VKEY_9,
|
||||
[Key.NumpadNum0] = VKEY_NUMPAD0,
|
||||
[Key.NumpadNum1] = VKEY_NUMPAD1,
|
||||
[Key.NumpadNum2] = VKEY_NUMPAD2,
|
||||
[Key.NumpadNum3] = VKEY_NUMPAD3,
|
||||
[Key.NumpadNum4] = VKEY_NUMPAD4,
|
||||
[Key.NumpadNum5] = VKEY_NUMPAD5,
|
||||
[Key.NumpadNum6] = VKEY_NUMPAD6,
|
||||
[Key.NumpadNum7] = VKEY_NUMPAD7,
|
||||
[Key.NumpadNum8] = VKEY_NUMPAD8,
|
||||
[Key.NumpadNum9] = VKEY_NUMPAD9,
|
||||
[Key.Escape] = VKEY_ESCAPE,
|
||||
[Key.Control] = VKEY_CONTROL,
|
||||
[Key.Shift] = VKEY_SHIFT,
|
||||
[Key.Alt] = VKEY_MENU,
|
||||
[Key.LSystem] = VKEY_LWIN,
|
||||
[Key.RSystem] = VKEY_RWIN,
|
||||
[Key.LBracket] = VKEY_OEM_4,
|
||||
[Key.RBracket] = VKEY_OEM_6,
|
||||
[Key.SemiColon] = VKEY_OEM_1,
|
||||
[Key.Comma] = VKEY_OEM_COMMA,
|
||||
[Key.Period] = VKEY_OEM_PERIOD,
|
||||
[Key.Apostrophe] = VKEY_OEM_7,
|
||||
[Key.Slash] = VKEY_OEM_2,
|
||||
[Key.BackSlash] = VKEY_OEM_5,
|
||||
[Key.Tilde] = VKEY_OEM_3,
|
||||
[Key.Equal] = VKEY_OEM_PLUS,
|
||||
[Key.Space] = VKEY_SPACE,
|
||||
[Key.Return] = VKEY_RETURN,
|
||||
[Key.BackSpace] = VKEY_BACK,
|
||||
[Key.Tab] = VKEY_TAB,
|
||||
[Key.PageUp] = VKEY_PRIOR,
|
||||
[Key.PageDown] = VKEY_NEXT,
|
||||
[Key.End] = VKEY_END,
|
||||
[Key.Home] = VKEY_HOME,
|
||||
[Key.Insert] = VKEY_INSERT,
|
||||
[Key.Delete] = VKEY_DELETE,
|
||||
[Key.Minus] = VKEY_OEM_MINUS,
|
||||
[Key.NumpadAdd] = VKEY_ADD,
|
||||
[Key.NumpadSubtract] = VKEY_SUBTRACT,
|
||||
[Key.NumpadDivide] = VKEY_DIVIDE,
|
||||
[Key.NumpadMultiply] = VKEY_MULTIPLY,
|
||||
[Key.NumpadDecimal] = VKEY_DECIMAL,
|
||||
[Key.Left] = VKEY_LEFT,
|
||||
[Key.Right] = VKEY_RIGHT,
|
||||
[Key.Up] = VKEY_UP,
|
||||
[Key.Down] = VKEY_DOWN,
|
||||
[Key.F1] = VKEY_F1,
|
||||
[Key.F2] = VKEY_F2,
|
||||
[Key.F3] = VKEY_F3,
|
||||
[Key.F4] = VKEY_F4,
|
||||
[Key.F5] = VKEY_F5,
|
||||
[Key.F6] = VKEY_F6,
|
||||
[Key.F7] = VKEY_F7,
|
||||
[Key.F8] = VKEY_F8,
|
||||
[Key.F9] = VKEY_F9,
|
||||
[Key.F10] = VKEY_F10,
|
||||
[Key.F11] = VKEY_F11,
|
||||
[Key.F12] = VKEY_F12,
|
||||
[Key.F13] = VKEY_F13,
|
||||
[Key.F14] = VKEY_F14,
|
||||
[Key.F15] = VKEY_F15,
|
||||
[Key.Pause] = VKEY_PAUSE,
|
||||
};
|
||||
|
||||
public BrowserControl()
|
||||
{
|
||||
CanKeyboardFocus = true;
|
||||
KeyboardFocusOnClick = true;
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
_cef.CheckInitialized();
|
||||
|
||||
DebugTools.AssertNull(_data);
|
||||
|
||||
// A funny render handler that will allow us to render to the control.
|
||||
var renderer = new ControlRenderHandler(this);
|
||||
|
||||
// A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the
|
||||
// rendering would work in that case? TODO CEF: Investigate a way to share the web client?
|
||||
var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler());
|
||||
|
||||
var info = CefWindowInfo.Create();
|
||||
|
||||
// FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window
|
||||
// Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client.
|
||||
info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle?
|
||||
info.WindowlessRenderingEnabled = true;
|
||||
|
||||
var settings = new CefBrowserSettings()
|
||||
{
|
||||
WindowlessFrameRate = 60
|
||||
};
|
||||
|
||||
// Create the web browser! And by default, we go to about:blank.
|
||||
var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl);
|
||||
|
||||
var texture = _clyde.CreateBlankTexture<Bgra32>(Vector2i.One);
|
||||
|
||||
_data = new LiveData(texture, client, browser, renderer);
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
DebugTools.AssertNotNull(_data);
|
||||
|
||||
_data!.Texture.Dispose();
|
||||
_data.Browser.GetHost().CloseBrowser(true);
|
||||
_data = null;
|
||||
}
|
||||
|
||||
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
{
|
||||
base.MouseMove(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
// Logger.Debug();
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false);
|
||||
}
|
||||
|
||||
protected internal override void MouseExited()
|
||||
{
|
||||
base.MouseExited();
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true);
|
||||
}
|
||||
|
||||
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int) args.RelativePosition.X, (int) args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseWheelEvent(
|
||||
mouseEvent,
|
||||
(int) args.Delta.X * ScrollSpeed,
|
||||
(int) args.Delta.Y * ScrollSpeed);
|
||||
}
|
||||
|
||||
bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
|
||||
{
|
||||
if (_data == null)
|
||||
return false;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight)
|
||||
{
|
||||
var key = guiRawEvent.Key switch
|
||||
{
|
||||
Key.MouseLeft => CefMouseButtonType.Left,
|
||||
Key.MouseMiddle => CefMouseButtonType.Middle,
|
||||
Key.MouseRight => CefMouseButtonType.Right,
|
||||
_ => default // not possible
|
||||
};
|
||||
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y,
|
||||
CefEventFlags.None);
|
||||
|
||||
// Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}");
|
||||
|
||||
// TODO: double click support?
|
||||
host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Handle left/right modifier keys??
|
||||
if (!_keyMap.TryGetValue(guiRawEvent.Key, out var vkKey))
|
||||
vkKey = default;
|
||||
|
||||
// Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}");
|
||||
|
||||
var lParam = 0;
|
||||
lParam |= (guiRawEvent.ScanCode & 0xFF) << 16;
|
||||
if (guiRawEvent.Action != RawKeyAction.Down)
|
||||
lParam |= 1 << 30;
|
||||
|
||||
if (guiRawEvent.Action == RawKeyAction.Up)
|
||||
lParam |= 1 << 31;
|
||||
|
||||
var modifiers = CalcModifiers(guiRawEvent.Key);
|
||||
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
// Repeats are sent as key downs, I guess?
|
||||
EventType = guiRawEvent.Action == RawKeyAction.Up
|
||||
? CefKeyEventType.KeyUp
|
||||
: CefKeyEventType.RawKeyDown,
|
||||
NativeKeyCode = lParam,
|
||||
// NativeKeyCode = guiRawEvent.ScanCode,
|
||||
WindowsKeyCode = (int) vkKey,
|
||||
IsSystemKey = false, // TODO
|
||||
Modifiers = modifiers
|
||||
});
|
||||
|
||||
if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = '\r',
|
||||
NativeKeyCode = lParam,
|
||||
Modifiers = modifiers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcModifiers(Key key)
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcMouseModifiers()
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseLeft))
|
||||
modifiers |= CefEventFlags.LeftMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseMiddle))
|
||||
modifiers |= CefEventFlags.MiddleMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseRight))
|
||||
modifiers |= CefEventFlags.RightMouseButton;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
protected internal override void TextEntered(GUITextEventArgs args)
|
||||
{
|
||||
base.TextEntered(args);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
Span<char> buf = stackalloc char[2];
|
||||
var written = args.AsRune.EncodeToUtf16(buf);
|
||||
|
||||
for (var i = 0; i < written; i++)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = buf[i],
|
||||
Character = buf[i],
|
||||
UnmodifiedCharacter = buf[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Resized()
|
||||
{
|
||||
base.Resized();
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
_data.Browser.GetHost().NotifyMoveOrResizeStarted();
|
||||
_data.Browser.GetHost().WasResized();
|
||||
_data.Texture.Dispose();
|
||||
_data.Texture = _clyde.CreateBlankTexture<Bgra32>((PixelWidth, PixelHeight));
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var bufImg = _data.Renderer.Buffer.Buffer;
|
||||
|
||||
_data.Texture.SetSubImage(
|
||||
Vector2i.Zero,
|
||||
bufImg,
|
||||
new UIBox2i(
|
||||
0, 0,
|
||||
Math.Min(PixelWidth, bufImg.Width),
|
||||
Math.Min(PixelHeight, bufImg.Height)));
|
||||
|
||||
handle.DrawTexture(_data.Texture, Vector2.Zero);
|
||||
}
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.StopLoad();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.Reload();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoBack)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoForward)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
// TODO: this should not run until the browser is done loading seriously does this even work?
|
||||
_data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.AddBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
private sealed class LiveData
|
||||
{
|
||||
public OwnedTexture Texture;
|
||||
public readonly RobustCefClient Client;
|
||||
public readonly CefBrowser Browser;
|
||||
public readonly ControlRenderHandler Renderer;
|
||||
|
||||
public LiveData(
|
||||
OwnedTexture texture,
|
||||
RobustCefClient client,
|
||||
CefBrowser browser,
|
||||
ControlRenderHandler renderer)
|
||||
{
|
||||
Texture = texture;
|
||||
Client = client;
|
||||
Browser = browser;
|
||||
Renderer = renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ControlRenderHandler : CefRenderHandler
|
||||
{
|
||||
public ImageBuffer Buffer { get; }
|
||||
private Control _control;
|
||||
|
||||
internal ControlRenderHandler(Control control)
|
||||
{
|
||||
Buffer = new ImageBuffer(control);
|
||||
_control = control;
|
||||
}
|
||||
|
||||
protected override CefAccessibilityHandler? GetAccessibilityHandler() => null;
|
||||
|
||||
protected override void GetViewRect(CefBrowser browser, out CefRectangle rect)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
{
|
||||
rect = new CefRectangle();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO CEF: Do we need to pass real screen coords? Cause what we do already works...
|
||||
//var screenCoords = _control.ScreenCoordinates;
|
||||
//rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1));
|
||||
|
||||
// We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise.
|
||||
rect = new CefRectangle(0, 0, (int) Math.Max(_control.Size.X, 1), (int) Math.Max(_control.Size.Y, 1));
|
||||
}
|
||||
|
||||
protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return false;
|
||||
|
||||
// TODO CEF: Get actual scale factor?
|
||||
screenInfo.DeviceScaleFactor = 1.0f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnPopupSize(CefBrowser browser, CefRectangle rect)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects,
|
||||
IntPtr buffer, int width, int height)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
|
||||
foreach (var dirtyRect in dirtyRects)
|
||||
{
|
||||
Buffer.UpdateBuffer(width, height, buffer, dirtyRect);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
|
||||
CefRectangle[] dirtyRects, IntPtr sharedHandle)
|
||||
{
|
||||
// Unused, but we're forced to implement it so.. NOOP.
|
||||
}
|
||||
|
||||
protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange,
|
||||
CefRectangle[] characterBounds)
|
||||
{
|
||||
if (_control.Disposed)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
// The library we're using right now. TODO CEF: Do we want to use something else? We will need to ship it ourselves if so.
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
// Register this with IoC.
|
||||
// TODO CEF: think if making this inherit CefApp is a good idea...
|
||||
// TODO CEF: A way to handle external window browsers...
|
||||
[UsedImplicitly]
|
||||
public partial class CefManager
|
||||
{
|
||||
private CefApp _app = default!;
|
||||
private bool _initialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Call this to initialize CEF.
|
||||
/// </summary>
|
||||
public void Initialize()
|
||||
{
|
||||
DebugTools.Assert(!_initialized);
|
||||
|
||||
string subProcessName;
|
||||
if (OperatingSystem.IsWindows())
|
||||
subProcessName = "Robust.Client.CEF.exe";
|
||||
else if (OperatingSystem.IsLinux())
|
||||
subProcessName = "Robust.Client.CEF";
|
||||
else
|
||||
throw new NotSupportedException("Unsupported platform for CEF!");
|
||||
|
||||
var subProcessPath = PathHelpers.ExecutableRelativeFile(subProcessName);
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
|
||||
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
|
||||
NoSandbox = true, // Not disabling the sandbox crashes CEF.
|
||||
BrowserSubprocessPath = subProcessPath,
|
||||
LocalesDirPath = Path.Combine(PathHelpers.GetExecutableDirectory(), "locales"),
|
||||
ResourcesDirPath = PathHelpers.GetExecutableDirectory(),
|
||||
RemoteDebuggingPort = 9222
|
||||
};
|
||||
|
||||
Logger.Info($"CEF Version: {CefRuntime.ChromeVersion}");
|
||||
|
||||
// --------------------------- README --------------------------------------------------
|
||||
// By the way! You're gonna need the CEF binaries in your client's bin folder.
|
||||
// More specifically, version cef_binary_91.1.21+g9dd45fe+chromium-91.0.4472.114
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_windows64_minimal.tar.bz2
|
||||
// https://cef-builds.spotifycdn.com/cef_binary_91.1.21%2Bg9dd45fe%2Bchromium-91.0.4472.114_linux64_minimal.tar.bz2
|
||||
// Here's how to get it to work:
|
||||
// 1. Copy all the contents of "Release" to the bin folder.
|
||||
// 2. Copy all the contents of "Resources" to the bin folder.
|
||||
// Supposedly, you should just need libcef.so in Release and icudtl.dat in Resources...
|
||||
// The rest might be optional.
|
||||
// Maybe. Good luck! If you get odd crashes with no info and a weird exit code, use GDB!
|
||||
// -------------------------------------------------------------------------------------
|
||||
|
||||
_app = new RobustCefApp();
|
||||
|
||||
// We pass no main arguments...
|
||||
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
|
||||
|
||||
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
|
||||
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
|
||||
// And nothing seemed to work. Odd.
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public void CheckInitialized()
|
||||
{
|
||||
if (!_initialized)
|
||||
throw new InvalidOperationException("CefManager has not been initialized!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Needs to be called regularly for CEF to keep working.
|
||||
/// </summary>
|
||||
public void Update()
|
||||
{
|
||||
DebugTools.Assert(_initialized);
|
||||
|
||||
// Calling this makes CEF do its work, without using its own update loop.
|
||||
CefRuntime.DoMessageLoopWork();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call before program shutdown.
|
||||
/// </summary>
|
||||
public void Shutdown()
|
||||
{
|
||||
DebugTools.Assert(_initialized);
|
||||
|
||||
CefRuntime.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public interface IBrowserWindow : IBrowserControl, IDisposable
|
||||
{
|
||||
bool Closed { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
// This is a workaround for this to work on UNIX.
|
||||
var argv = args;
|
||||
if (CefRuntime.Platform != CefRuntimePlatform.Windows)
|
||||
{
|
||||
argv = new string[args.Length + 1];
|
||||
Array.Copy(args, 0, argv, 1, args.Length);
|
||||
argv[0] = "-";
|
||||
}
|
||||
|
||||
var mainArgs = new CefMainArgs(argv);
|
||||
|
||||
// This will block executing until the subprocess is shut down.
|
||||
var code = CefRuntime.ExecuteProcess(mainArgs, null, IntPtr.Zero);
|
||||
|
||||
if (code != 0)
|
||||
{
|
||||
System.Console.WriteLine($"CEF Subprocess exited unsuccessfully with exit code {code}! Arguments: {string.Join(' ', argv)}");
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.props" />
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>WinExe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<Target Name="RobustAfterBuild" AfterTargets="Build" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\cefglue\CefGlue\CefGlue.csproj" />
|
||||
<ProjectReference Include="..\Robust.Client\Robust.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -13,5 +13,6 @@
|
||||
<Compile Link="XamlX\filename" Include="../XamlX/src/XamlX/**/*.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/**/SreTypeSystem.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/obj/**" />
|
||||
<Compile Include="..\Robust.Client\UserInterface\ControlPropertyAccess.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Robust.Client.UserInterface;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
@@ -37,9 +38,10 @@ namespace Robust.Client.AutoGenerated
|
||||
|
||||
class NameVisitor : IXamlAstVisitor
|
||||
{
|
||||
private List<(string name, string type)> _names = new List<(string name, string type)>();
|
||||
private readonly List<(string name, string type, AccessLevel access)> _names =
|
||||
new List<(string name, string type, AccessLevel access)>();
|
||||
|
||||
public static List<(string name, string type)> GetNames(IXamlAstNode node)
|
||||
public static List<(string name, string type, AccessLevel access)> GetNames(IXamlAstNode node)
|
||||
{
|
||||
var visitor = new NameVisitor();
|
||||
node.Visit(visitor);
|
||||
@@ -56,27 +58,42 @@ namespace Robust.Client.AutoGenerated
|
||||
{
|
||||
var clrtype = objectNode.Type.GetClrType();
|
||||
var isControl = IsControl(clrtype);
|
||||
//clrtype.Interfaces.Any(i =>
|
||||
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
|
||||
|
||||
if (!isControl)
|
||||
return node;
|
||||
|
||||
// Find Name and Access properties
|
||||
XamlAstTextNode nameText = null;
|
||||
XamlAstTextNode accessText = null;
|
||||
foreach (var child in objectNode.Children)
|
||||
{
|
||||
if (child is XamlAstXamlPropertyValueNode propertyValueNode &&
|
||||
propertyValueNode.Property is XamlAstNamePropertyReference namedProperty &&
|
||||
namedProperty.Name == "Name" &&
|
||||
propertyValueNode.Values.Count > 0 &&
|
||||
propertyValueNode.Values[0] is XamlAstTextNode text)
|
||||
{
|
||||
var reg = (text.Text, $@"{clrtype.Namespace}.{clrtype.Name}");
|
||||
if (!_names.Contains(reg))
|
||||
switch (namedProperty.Name)
|
||||
{
|
||||
_names.Add(reg);
|
||||
case "Name":
|
||||
nameText = text;
|
||||
break;
|
||||
case "Access":
|
||||
accessText = text;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (nameText == null)
|
||||
return node;
|
||||
|
||||
var reg = (nameText.Text,
|
||||
$@"{clrtype.Namespace}.{clrtype.Name}",
|
||||
accessText != null ? (AccessLevel) Enum.Parse(typeof(AccessLevel), accessText.Text) : AccessLevel.Protected);
|
||||
if (!_names.Contains(reg))
|
||||
{
|
||||
_names.Add(reg);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
@@ -94,7 +111,8 @@ namespace Robust.Client.AutoGenerated
|
||||
private static string GenerateSourceCode(
|
||||
INamedTypeSymbol classSymbol,
|
||||
string xamlFile,
|
||||
CSharpCompilation comp)
|
||||
CSharpCompilation comp,
|
||||
string fileName)
|
||||
{
|
||||
var className = classSymbol.Name;
|
||||
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
|
||||
@@ -118,11 +136,42 @@ namespace Robust.Client.AutoGenerated
|
||||
if (classSymbol.ToString() != rootTypeString && classSymbol.BaseType?.ToString() != rootTypeString)
|
||||
throw new InvalidXamlRootTypeException(rootType, classSymbol, classSymbol.BaseType);
|
||||
|
||||
var fieldAccess = classSymbol.IsSealed ? "private" : "protected";
|
||||
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
|
||||
var namedControls = names.Select(info => " " +
|
||||
$"{fieldAccess} global::{info.type} {info.name} => " +
|
||||
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
|
||||
var namedControls = names.Select(info =>
|
||||
{
|
||||
(string name, string type, AccessLevel access) = info;
|
||||
|
||||
string accessStr;
|
||||
switch (access)
|
||||
{
|
||||
case AccessLevel.Public:
|
||||
accessStr = "public";
|
||||
break;
|
||||
case AccessLevel.Protected when classSymbol.IsSealed:
|
||||
case AccessLevel.PrivateProtected when classSymbol.IsSealed:
|
||||
case AccessLevel.Private:
|
||||
accessStr = "private";
|
||||
break;
|
||||
case AccessLevel.Protected:
|
||||
accessStr = "protected";
|
||||
break;
|
||||
case AccessLevel.PrivateProtected:
|
||||
accessStr = "private protected";
|
||||
break;
|
||||
case AccessLevel.Internal:
|
||||
case AccessLevel.ProtectedInternal when classSymbol.IsSealed:
|
||||
accessStr = "internal";
|
||||
break;
|
||||
case AccessLevel.ProtectedInternal:
|
||||
accessStr = "protected internal";
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid access level \"{Enum.GetName(typeof(AccessLevel), access)}\" " +
|
||||
$"for control {name} in file {fileName}.");
|
||||
}
|
||||
|
||||
return $" {accessStr} global::{type} {name} => this.FindControl<global::{type}>(\"{name}\");";
|
||||
});
|
||||
|
||||
return $@"// <auto-generated />
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -136,7 +185,6 @@ namespace {nameSpace}
|
||||
";
|
||||
}
|
||||
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
var comp = (CSharpCompilation) context.Compilation;
|
||||
@@ -207,7 +255,7 @@ namespace {nameSpace}
|
||||
|
||||
try
|
||||
{
|
||||
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp);
|
||||
var sourceCode = GenerateSourceCode(typeSymbol, txt, comp, xamlFileName);
|
||||
context.AddSource($"{typeSymbol.Name}.g.cs", SourceText.From(sourceCode, Encoding.UTF8));
|
||||
}
|
||||
catch (InvalidXamlRootTypeException invRootType)
|
||||
@@ -253,7 +301,7 @@ namespace {nameSpace}
|
||||
foreach (var candidateClass in receiver.CandidateClasses)
|
||||
{
|
||||
var model = compilation.GetSemanticModel(candidateClass.SyntaxTree);
|
||||
var typeSymbol = (INamedTypeSymbol) model.GetDeclaredSymbol(candidateClass);
|
||||
var typeSymbol = model.GetDeclaredSymbol(candidateClass);
|
||||
var relevantAttribute = typeSymbol.GetAttributes().FirstOrDefault(attr =>
|
||||
attr.AttributeClass != null &&
|
||||
attr.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
public sealed class BrowserWindowCreateParameters
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public sealed class BeforeBrowseContext
|
||||
internal sealed class CefBeforeBrowseContext : IBeforeBrowseContext
|
||||
{
|
||||
internal readonly CefRequest CefRequest;
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Client.CEF
|
||||
|
||||
public bool IsCancelled { get; private set; }
|
||||
|
||||
internal BeforeBrowseContext(
|
||||
internal CefBeforeBrowseContext(
|
||||
bool isRedirect,
|
||||
bool userGesture,
|
||||
CefRequest cefRequest)
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
@@ -3,9 +3,9 @@ using System.IO;
|
||||
using System.Net;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public sealed class RequestHandlerContext
|
||||
internal sealed class CefRequestHandlerContext : IRequestHandlerContext
|
||||
{
|
||||
internal readonly CefRequest CefRequest;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Robust.Client.CEF
|
||||
|
||||
internal IRequestResult? Result { get; private set; }
|
||||
|
||||
internal RequestHandlerContext(
|
||||
internal CefRequestHandlerContext(
|
||||
bool isNavigation,
|
||||
bool isDownload,
|
||||
string requestInitiator,
|
||||
@@ -1,30 +1,24 @@
|
||||
using System;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Maths;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
internal sealed class ImageBuffer
|
||||
{
|
||||
private readonly Control _control;
|
||||
|
||||
public ImageBuffer(Control control)
|
||||
{
|
||||
_control = control;
|
||||
}
|
||||
|
||||
public Image<Bgra32> Buffer { get; private set; } = new(1, 1);
|
||||
public Image<Rgba32> Buffer { get; private set; } = new(1, 1);
|
||||
|
||||
public unsafe void UpdateBuffer(int width, int height, IntPtr buffer, CefRectangle dirtyRect)
|
||||
{
|
||||
if (width != Buffer.Width || height != Buffer.Height)
|
||||
UpdateSize(width, height);
|
||||
|
||||
var span = new ReadOnlySpan<Bgra32>((void*) buffer, width * height);
|
||||
// NOTE: Image data from CEF is actually BGRA32, not RGBA32.
|
||||
// OpenGL ES does not allow uploading BGRA data, so we pretend it's RGBA32 and use a shader to swizzle it.
|
||||
var span = new ReadOnlySpan<Rgba32>((void*) buffer, width * height);
|
||||
|
||||
ImageSharpExt.Blit(
|
||||
span,
|
||||
@@ -36,7 +30,7 @@ namespace Robust.Client.CEF
|
||||
|
||||
private void UpdateSize(int width, int height)
|
||||
{
|
||||
Buffer = new Image<Bgra32>(width, height);
|
||||
Buffer = new Image<Rgba32>(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Robust.Client.WebView/Cef/Program.cs
Normal file
88
Robust.Client.WebView/Cef/Program.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
// This is a workaround for this to work on UNIX.
|
||||
var argv = args;
|
||||
if (CefRuntime.Platform != CefRuntimePlatform.Windows)
|
||||
{
|
||||
argv = new string[args.Length + 1];
|
||||
Array.Copy(args, 0, argv, 1, args.Length);
|
||||
argv[0] = "-";
|
||||
}
|
||||
/*
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
// Chromium tries to load libEGL.so and libGLESv2.so relative to the process executable on Linux.
|
||||
// (Compared to Windows where it is relative to Chromium's *module*)
|
||||
// There is a TODO "is this correct?" in the Chromium code for this.
|
||||
// Great.
|
||||
|
||||
//CopyDllToExecutableDir("libEGL.so");
|
||||
//CopyDllToExecutableDir("libGLESv2.so");
|
||||
|
||||
// System.Threading.Thread.Sleep(200000);
|
||||
}
|
||||
*/
|
||||
var mainArgs = new CefMainArgs(argv);
|
||||
|
||||
// This will block executing until the subprocess is shut down.
|
||||
var code = CefRuntime.ExecuteProcess(mainArgs, null, IntPtr.Zero);
|
||||
|
||||
if (code != 0)
|
||||
{
|
||||
System.Console.WriteLine($"CEF Subprocess exited unsuccessfully with exit code {code}! Arguments: {string.Join(' ', argv)}");
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
/* private static void CopyDllToExecutableDir(string dllName)
|
||||
{
|
||||
var executableDir = PathHelpers.GetExecutableDirectory();
|
||||
var targetPath = Path.Combine(executableDir, dllName);
|
||||
if (File.Exists(targetPath))
|
||||
return;
|
||||
|
||||
// Find source file.
|
||||
string? srcFile = null;
|
||||
foreach (var searchDir in WebViewManagerCef.NativeDllSearchDirectories())
|
||||
{
|
||||
var searchPath = Path.Combine(searchDir, dllName);
|
||||
if (File.Exists(searchPath))
|
||||
{
|
||||
srcFile = searchPath;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (srcFile == null)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(targetPath))
|
||||
return;
|
||||
|
||||
File.Copy(srcFile, targetPath);
|
||||
return;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Catching race condition lock errors and stuff I guess.
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using System.IO;
|
||||
using System.Net;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
internal interface IRequestResult
|
||||
{
|
||||
@@ -88,6 +88,13 @@ namespace Robust.Client.CEF
|
||||
protected override void Cancel()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
_stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
internal class RobustCefApp : CefApp
|
||||
{
|
||||
@@ -24,6 +24,13 @@ namespace Robust.Client.CEF
|
||||
{
|
||||
// Disable zygote on Linux.
|
||||
commandLine.AppendSwitch("--no-zygote");
|
||||
|
||||
// Work around https://bitbucket.org/chromiumembedded/cef/issues/3213/ozone-egl-initialization-does-not-have
|
||||
// Desktop GL force makes Chromium not try to load its own ANGLE/Swiftshader so load paths aren't problematic.
|
||||
if (OperatingSystem.IsLinux())
|
||||
commandLine.AppendSwitch("--use-gl", "desktop");
|
||||
|
||||
// commandLine.AppendSwitch("--single-process");
|
||||
|
||||
//commandLine.AppendSwitch("--disable-gpu");
|
||||
//commandLine.AppendSwitch("--disable-gpu-compositing");
|
||||
@@ -1,6 +1,6 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
// Simple CEF client.
|
||||
internal class RobustCefClient : CefClient
|
||||
@@ -1,6 +1,6 @@
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public sealed class RobustLoadHandler : CefLoadHandler
|
||||
{
|
||||
@@ -3,20 +3,20 @@ using System.Collections.Generic;
|
||||
using Robust.Shared.Log;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
internal sealed class RobustRequestHandler : CefRequestHandler
|
||||
{
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly List<Action<RequestHandlerContext>> _resourceRequestHandlers = new();
|
||||
private readonly List<Action<BeforeBrowseContext>> _beforeBrowseHandlers = new();
|
||||
private readonly List<Action<IRequestHandlerContext>> _resourceRequestHandlers = new();
|
||||
private readonly List<Action<IBeforeBrowseContext>> _beforeBrowseHandlers = new();
|
||||
|
||||
public RobustRequestHandler(ISawmill sawmill)
|
||||
{
|
||||
_sawmill = sawmill;
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
@@ -24,7 +24,7 @@ namespace Robust.Client.CEF
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
lock (_resourceRequestHandlers)
|
||||
{
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Client.CEF
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client.CEF
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<BeforeBrowseContext> handler)
|
||||
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
@@ -61,7 +61,7 @@ namespace Robust.Client.CEF
|
||||
{
|
||||
_sawmill.Debug($"HANDLING REQUEST: {request.Url}");
|
||||
|
||||
var context = new RequestHandlerContext(isNavigation, isDownload, requestInitiator, request);
|
||||
var context = new CefRequestHandlerContext(isNavigation, isDownload, requestInitiator, request);
|
||||
|
||||
foreach (var handler in _resourceRequestHandlers)
|
||||
{
|
||||
@@ -85,7 +85,7 @@ namespace Robust.Client.CEF
|
||||
{
|
||||
lock (_beforeBrowseHandlers)
|
||||
{
|
||||
var context = new BeforeBrowseContext(isRedirect, userGesture, request);
|
||||
var context = new CefBeforeBrowseContext(isRedirect, userGesture, request);
|
||||
|
||||
foreach (var handler in _beforeBrowseHandlers)
|
||||
{
|
||||
@@ -6,26 +6,25 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public partial class CefManager
|
||||
internal partial class WebViewManagerCef
|
||||
{
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
|
||||
private readonly List<BrowserWindowImpl> _browserWindows = new();
|
||||
private readonly List<WebViewWindowImpl> _browserWindows = new();
|
||||
|
||||
public IEnumerable<IBrowserWindow> AllBrowserWindows => _browserWindows;
|
||||
public IEnumerable<IWebViewWindow> AllBrowserWindows => _browserWindows;
|
||||
|
||||
public IBrowserWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
|
||||
public IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
|
||||
{
|
||||
var mainHWnd = (_clyde.MainWindow as IClydeWindowInternal)?.WindowsHWnd ?? 0;
|
||||
|
||||
var info = CefWindowInfo.Create();
|
||||
info.Width = createParams.Width;
|
||||
info.Height = createParams.Height;
|
||||
info.Bounds = new CefRectangle(0, 0, createParams.Width, createParams.Height);
|
||||
info.SetAsPopup(mainHWnd, "ss14cef");
|
||||
|
||||
var impl = new BrowserWindowImpl(this);
|
||||
var impl = new WebViewWindowImpl(this);
|
||||
|
||||
var lifeSpanHandler = new WindowLifeSpanHandler(impl);
|
||||
var reqHandler = new RobustRequestHandler(Logger.GetSawmill("root"));
|
||||
@@ -40,13 +39,13 @@ namespace Robust.Client.CEF
|
||||
return impl;
|
||||
}
|
||||
|
||||
private sealed class BrowserWindowImpl : IBrowserWindow
|
||||
private sealed class WebViewWindowImpl : IWebViewWindow
|
||||
{
|
||||
private readonly CefManager _manager;
|
||||
private readonly WebViewManagerCef _manager;
|
||||
internal CefBrowser Browser = default!;
|
||||
internal RobustRequestHandler RequestHandler = default!;
|
||||
|
||||
public Action<RequestHandlerContext>? OnResourceRequest { get; set; }
|
||||
public Action<CefRequestHandlerContext>? OnResourceRequest { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
@@ -73,7 +72,7 @@ namespace Robust.Client.CEF
|
||||
}
|
||||
}
|
||||
|
||||
public BrowserWindowImpl(CefManager manager)
|
||||
public WebViewWindowImpl(WebViewManagerCef manager)
|
||||
{
|
||||
_manager = manager;
|
||||
}
|
||||
@@ -116,12 +115,12 @@ namespace Robust.Client.CEF
|
||||
Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
RequestHandler.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler)
|
||||
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
RequestHandler.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
@@ -168,9 +167,9 @@ namespace Robust.Client.CEF
|
||||
|
||||
private sealed class WindowLifeSpanHandler : CefLifeSpanHandler
|
||||
{
|
||||
private readonly BrowserWindowImpl _windowImpl;
|
||||
private readonly WebViewWindowImpl _windowImpl;
|
||||
|
||||
public WindowLifeSpanHandler(BrowserWindowImpl windowImpl)
|
||||
public WindowLifeSpanHandler(WebViewWindowImpl windowImpl)
|
||||
{
|
||||
_windowImpl = windowImpl;
|
||||
}
|
||||
584
Robust.Client.WebView/Cef/WebViewManagerCef.Control.cs
Normal file
584
Robust.Client.WebView/Cef/WebViewManagerCef.Control.cs
Normal file
@@ -0,0 +1,584 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Xilium.CefGlue;
|
||||
using static Robust.Client.WebView.Cef.CefKeyCodes.ChromiumKeyboardCode;
|
||||
using static Robust.Client.Input.Keyboard;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
internal partial class WebViewManagerCef
|
||||
{
|
||||
public IWebViewControlImpl MakeControlImpl(WebViewControl owner)
|
||||
{
|
||||
var shader = _prototypeManager.Index<ShaderPrototype>("bgra");
|
||||
var shaderInstance = shader.Instance();
|
||||
var impl = new ControlImpl(owner, shaderInstance);
|
||||
_dependencyCollection.InjectDependencies(impl);
|
||||
return impl;
|
||||
}
|
||||
|
||||
private sealed class ControlImpl : IWebViewControlImpl
|
||||
{
|
||||
private static readonly Dictionary<Key, CefKeyCodes.ChromiumKeyboardCode> KeyMap = new()
|
||||
{
|
||||
[Key.A] = VKEY_A,
|
||||
[Key.B] = VKEY_B,
|
||||
[Key.C] = VKEY_C,
|
||||
[Key.D] = VKEY_D,
|
||||
[Key.E] = VKEY_E,
|
||||
[Key.F] = VKEY_F,
|
||||
[Key.G] = VKEY_G,
|
||||
[Key.H] = VKEY_H,
|
||||
[Key.I] = VKEY_I,
|
||||
[Key.J] = VKEY_J,
|
||||
[Key.K] = VKEY_K,
|
||||
[Key.L] = VKEY_L,
|
||||
[Key.M] = VKEY_M,
|
||||
[Key.N] = VKEY_N,
|
||||
[Key.O] = VKEY_O,
|
||||
[Key.P] = VKEY_P,
|
||||
[Key.Q] = VKEY_Q,
|
||||
[Key.R] = VKEY_R,
|
||||
[Key.S] = VKEY_S,
|
||||
[Key.T] = VKEY_T,
|
||||
[Key.U] = VKEY_U,
|
||||
[Key.V] = VKEY_V,
|
||||
[Key.W] = VKEY_W,
|
||||
[Key.X] = VKEY_X,
|
||||
[Key.Y] = VKEY_Y,
|
||||
[Key.Z] = VKEY_Z,
|
||||
[Key.Num0] = VKEY_0,
|
||||
[Key.Num1] = VKEY_1,
|
||||
[Key.Num2] = VKEY_2,
|
||||
[Key.Num3] = VKEY_3,
|
||||
[Key.Num4] = VKEY_4,
|
||||
[Key.Num5] = VKEY_5,
|
||||
[Key.Num6] = VKEY_6,
|
||||
[Key.Num7] = VKEY_7,
|
||||
[Key.Num8] = VKEY_8,
|
||||
[Key.Num9] = VKEY_9,
|
||||
[Key.NumpadNum0] = VKEY_NUMPAD0,
|
||||
[Key.NumpadNum1] = VKEY_NUMPAD1,
|
||||
[Key.NumpadNum2] = VKEY_NUMPAD2,
|
||||
[Key.NumpadNum3] = VKEY_NUMPAD3,
|
||||
[Key.NumpadNum4] = VKEY_NUMPAD4,
|
||||
[Key.NumpadNum5] = VKEY_NUMPAD5,
|
||||
[Key.NumpadNum6] = VKEY_NUMPAD6,
|
||||
[Key.NumpadNum7] = VKEY_NUMPAD7,
|
||||
[Key.NumpadNum8] = VKEY_NUMPAD8,
|
||||
[Key.NumpadNum9] = VKEY_NUMPAD9,
|
||||
[Key.Escape] = VKEY_ESCAPE,
|
||||
[Key.Control] = VKEY_CONTROL,
|
||||
[Key.Shift] = VKEY_SHIFT,
|
||||
[Key.Alt] = VKEY_MENU,
|
||||
[Key.LSystem] = VKEY_LWIN,
|
||||
[Key.RSystem] = VKEY_RWIN,
|
||||
[Key.LBracket] = VKEY_OEM_4,
|
||||
[Key.RBracket] = VKEY_OEM_6,
|
||||
[Key.SemiColon] = VKEY_OEM_1,
|
||||
[Key.Comma] = VKEY_OEM_COMMA,
|
||||
[Key.Period] = VKEY_OEM_PERIOD,
|
||||
[Key.Apostrophe] = VKEY_OEM_7,
|
||||
[Key.Slash] = VKEY_OEM_2,
|
||||
[Key.BackSlash] = VKEY_OEM_5,
|
||||
[Key.Tilde] = VKEY_OEM_3,
|
||||
[Key.Equal] = VKEY_OEM_PLUS,
|
||||
[Key.Space] = VKEY_SPACE,
|
||||
[Key.Return] = VKEY_RETURN,
|
||||
[Key.BackSpace] = VKEY_BACK,
|
||||
[Key.Tab] = VKEY_TAB,
|
||||
[Key.PageUp] = VKEY_PRIOR,
|
||||
[Key.PageDown] = VKEY_NEXT,
|
||||
[Key.End] = VKEY_END,
|
||||
[Key.Home] = VKEY_HOME,
|
||||
[Key.Insert] = VKEY_INSERT,
|
||||
[Key.Delete] = VKEY_DELETE,
|
||||
[Key.Minus] = VKEY_OEM_MINUS,
|
||||
[Key.NumpadAdd] = VKEY_ADD,
|
||||
[Key.NumpadSubtract] = VKEY_SUBTRACT,
|
||||
[Key.NumpadDivide] = VKEY_DIVIDE,
|
||||
[Key.NumpadMultiply] = VKEY_MULTIPLY,
|
||||
[Key.NumpadDecimal] = VKEY_DECIMAL,
|
||||
[Key.Left] = VKEY_LEFT,
|
||||
[Key.Right] = VKEY_RIGHT,
|
||||
[Key.Up] = VKEY_UP,
|
||||
[Key.Down] = VKEY_DOWN,
|
||||
[Key.F1] = VKEY_F1,
|
||||
[Key.F2] = VKEY_F2,
|
||||
[Key.F3] = VKEY_F3,
|
||||
[Key.F4] = VKEY_F4,
|
||||
[Key.F5] = VKEY_F5,
|
||||
[Key.F6] = VKEY_F6,
|
||||
[Key.F7] = VKEY_F7,
|
||||
[Key.F8] = VKEY_F8,
|
||||
[Key.F9] = VKEY_F9,
|
||||
[Key.F10] = VKEY_F10,
|
||||
[Key.F11] = VKEY_F11,
|
||||
[Key.F12] = VKEY_F12,
|
||||
[Key.F13] = VKEY_F13,
|
||||
[Key.F14] = VKEY_F14,
|
||||
[Key.F15] = VKEY_F15,
|
||||
[Key.Pause] = VKEY_PAUSE,
|
||||
};
|
||||
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IInputManager _inputMgr = default!;
|
||||
|
||||
public readonly WebViewControl Owner;
|
||||
private readonly ShaderInstance _shaderInstance;
|
||||
|
||||
public ControlImpl(WebViewControl owner, ShaderInstance shaderInstance)
|
||||
{
|
||||
Owner = owner;
|
||||
_shaderInstance = shaderInstance;
|
||||
}
|
||||
|
||||
private const int ScrollSpeed = 50;
|
||||
|
||||
private readonly RobustRequestHandler _requestHandler = new(Logger.GetSawmill("root"));
|
||||
private LiveData? _data;
|
||||
private string _startUrl = "about:blank";
|
||||
|
||||
public string Url
|
||||
{
|
||||
get => _data == null ? _startUrl : _data.Browser.GetMainFrame().Url;
|
||||
set
|
||||
{
|
||||
if (_data == null)
|
||||
_startUrl = value;
|
||||
else
|
||||
_data.Browser.GetMainFrame().LoadUrl(value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsLoading => _data?.Browser.IsLoading ?? false;
|
||||
|
||||
public void EnteredTree()
|
||||
{
|
||||
DebugTools.AssertNull(_data);
|
||||
|
||||
// A funny render handler that will allow us to render to the control.
|
||||
var renderer = new ControlRenderHandler(this);
|
||||
|
||||
// A funny web cef client. This can actually be shared by multiple browsers, but I'm not sure how the
|
||||
// rendering would work in that case? TODO CEF: Investigate a way to share the web client?
|
||||
var client = new RobustCefClient(renderer, _requestHandler, new RobustLoadHandler());
|
||||
|
||||
var info = CefWindowInfo.Create();
|
||||
|
||||
// FUNFACT: If you DO NOT set these below and set info.Width/info.Height instead, you get an external window
|
||||
// Good to know, huh? Setup is the same, except you can pass a dummy render handler to the CEF client.
|
||||
info.SetAsWindowless(IntPtr.Zero, false); // TODO CEF: Pass parent handle?
|
||||
info.WindowlessRenderingEnabled = true;
|
||||
|
||||
var settings = new CefBrowserSettings()
|
||||
{
|
||||
WindowlessFrameRate = 60
|
||||
};
|
||||
|
||||
// Create the web browser! And by default, we go to about:blank.
|
||||
var browser = CefBrowserHost.CreateBrowserSync(info, client, settings, _startUrl);
|
||||
|
||||
var texture = _clyde.CreateBlankTexture<Rgba32>(Vector2i.One);
|
||||
|
||||
_data = new LiveData(texture, client, browser, renderer);
|
||||
}
|
||||
|
||||
public void ExitedTree()
|
||||
{
|
||||
DebugTools.AssertNotNull(_data);
|
||||
|
||||
_data!.Texture.Dispose();
|
||||
_data.Browser.GetHost().CloseBrowser(true);
|
||||
_data = null;
|
||||
}
|
||||
|
||||
public void MouseMove(GUIMouseMoveEventArgs args)
|
||||
{
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
// Logger.Debug();
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int)args.RelativePosition.X, (int)args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(mouseEvent, false);
|
||||
}
|
||||
|
||||
public void MouseExited()
|
||||
{
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
|
||||
_data.Browser.GetHost().SendMouseMoveEvent(new CefMouseEvent(0, 0, modifiers), true);
|
||||
}
|
||||
|
||||
public void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var modifiers = CalcMouseModifiers();
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
(int)args.RelativePosition.X, (int)args.RelativePosition.Y,
|
||||
modifiers);
|
||||
|
||||
_data.Browser.GetHost().SendMouseWheelEvent(
|
||||
mouseEvent,
|
||||
(int)args.Delta.X * ScrollSpeed,
|
||||
(int)args.Delta.Y * ScrollSpeed);
|
||||
}
|
||||
|
||||
public bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
|
||||
{
|
||||
if (_data == null)
|
||||
return false;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
if (guiRawEvent.Key is Key.MouseLeft or Key.MouseMiddle or Key.MouseRight)
|
||||
{
|
||||
var key = guiRawEvent.Key switch
|
||||
{
|
||||
Key.MouseLeft => CefMouseButtonType.Left,
|
||||
Key.MouseMiddle => CefMouseButtonType.Middle,
|
||||
Key.MouseRight => CefMouseButtonType.Right,
|
||||
_ => default // not possible
|
||||
};
|
||||
|
||||
var mouseEvent = new CefMouseEvent(
|
||||
guiRawEvent.MouseRelative.X, guiRawEvent.MouseRelative.Y,
|
||||
CefEventFlags.None);
|
||||
|
||||
// Logger.Debug($"MOUSE: {guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {key}");
|
||||
|
||||
// TODO: double click support?
|
||||
host.SendMouseClickEvent(mouseEvent, key, guiRawEvent.Action == RawKeyAction.Up, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Handle left/right modifier keys??
|
||||
if (!KeyMap.TryGetValue(guiRawEvent.Key, out var vkKey))
|
||||
vkKey = default;
|
||||
|
||||
// Logger.Debug($"{guiRawEvent.Action} {guiRawEvent.Key} {guiRawEvent.ScanCode} {vkKey}");
|
||||
|
||||
var lParam = 0;
|
||||
lParam |= (guiRawEvent.ScanCode & 0xFF) << 16;
|
||||
if (guiRawEvent.Action != RawKeyAction.Down)
|
||||
lParam |= 1 << 30;
|
||||
|
||||
if (guiRawEvent.Action == RawKeyAction.Up)
|
||||
lParam |= 1 << 31;
|
||||
|
||||
var modifiers = CalcModifiers(guiRawEvent.Key);
|
||||
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
// Repeats are sent as key downs, I guess?
|
||||
EventType = guiRawEvent.Action == RawKeyAction.Up
|
||||
? CefKeyEventType.KeyUp
|
||||
: CefKeyEventType.RawKeyDown,
|
||||
NativeKeyCode = lParam,
|
||||
// NativeKeyCode = guiRawEvent.ScanCode,
|
||||
WindowsKeyCode = (int)vkKey,
|
||||
IsSystemKey = false, // TODO
|
||||
Modifiers = modifiers
|
||||
});
|
||||
|
||||
if (guiRawEvent.Action != RawKeyAction.Up && guiRawEvent.Key == Key.Return)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = '\r',
|
||||
NativeKeyCode = lParam,
|
||||
Modifiers = modifiers
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcModifiers(Key key)
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
private CefEventFlags CalcMouseModifiers()
|
||||
{
|
||||
CefEventFlags modifiers = default;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Control))
|
||||
modifiers |= CefEventFlags.ControlDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Alt))
|
||||
modifiers |= CefEventFlags.AltDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.Shift))
|
||||
modifiers |= CefEventFlags.ShiftDown;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseLeft))
|
||||
modifiers |= CefEventFlags.LeftMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseMiddle))
|
||||
modifiers |= CefEventFlags.MiddleMouseButton;
|
||||
|
||||
if (_inputMgr.IsKeyDown(Key.MouseRight))
|
||||
modifiers |= CefEventFlags.RightMouseButton;
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
public void TextEntered(GUITextEventArgs args)
|
||||
{
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var host = _data.Browser.GetHost();
|
||||
|
||||
Span<char> buf = stackalloc char[2];
|
||||
var written = args.AsRune.EncodeToUtf16(buf);
|
||||
|
||||
for (var i = 0; i < written; i++)
|
||||
{
|
||||
host.SendKeyEvent(new CefKeyEvent
|
||||
{
|
||||
EventType = CefKeyEventType.Char,
|
||||
WindowsKeyCode = buf[i],
|
||||
Character = buf[i],
|
||||
UnmodifiedCharacter = buf[i]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Resized()
|
||||
{
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
_data.Browser.GetHost().NotifyMoveOrResizeStarted();
|
||||
_data.Browser.GetHost().WasResized();
|
||||
_data.Texture.Dispose();
|
||||
_data.Texture = _clyde.CreateBlankTexture<Rgba32>((Owner.PixelWidth, Owner.PixelHeight));
|
||||
}
|
||||
|
||||
public void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
if (_data == null)
|
||||
return;
|
||||
|
||||
var bufImg = _data.Renderer.Buffer.Buffer;
|
||||
|
||||
_data.Texture.SetSubImage(
|
||||
Vector2i.Zero,
|
||||
bufImg,
|
||||
new UIBox2i(
|
||||
0, 0,
|
||||
Math.Min(Owner.PixelWidth, bufImg.Width),
|
||||
Math.Min(Owner.PixelHeight, bufImg.Height)));
|
||||
|
||||
handle.UseShader(_shaderInstance);
|
||||
handle.DrawTexture(_data.Texture, Vector2.Zero);
|
||||
}
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.StopLoad();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
_data.Browser.Reload();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoBack)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoBack();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
if (!_data.Browser.CanGoForward)
|
||||
return false;
|
||||
|
||||
_data.Browser.GoForward();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
if (_data == null)
|
||||
throw new InvalidOperationException();
|
||||
|
||||
// TODO: this should not run until the browser is done loading seriously does this even work?
|
||||
_data.Browser.GetMainFrame().ExecuteJavaScript(code, string.Empty, 1);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.AddBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
|
||||
{
|
||||
_requestHandler.RemoveBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
private sealed class LiveData
|
||||
{
|
||||
public OwnedTexture Texture;
|
||||
public readonly RobustCefClient Client;
|
||||
public readonly CefBrowser Browser;
|
||||
public readonly ControlRenderHandler Renderer;
|
||||
|
||||
public LiveData(
|
||||
OwnedTexture texture,
|
||||
RobustCefClient client,
|
||||
CefBrowser browser,
|
||||
ControlRenderHandler renderer)
|
||||
{
|
||||
Texture = texture;
|
||||
Client = client;
|
||||
Browser = browser;
|
||||
Renderer = renderer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ControlRenderHandler : CefRenderHandler
|
||||
{
|
||||
public ImageBuffer Buffer { get; }
|
||||
private ControlImpl _control;
|
||||
|
||||
internal ControlRenderHandler(ControlImpl control)
|
||||
{
|
||||
Buffer = new ImageBuffer();
|
||||
_control = control;
|
||||
}
|
||||
|
||||
protected override CefAccessibilityHandler? GetAccessibilityHandler() => null;
|
||||
|
||||
protected override void GetViewRect(CefBrowser browser, out CefRectangle rect)
|
||||
{
|
||||
if (_control.Owner.Disposed)
|
||||
{
|
||||
rect = new CefRectangle();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO CEF: Do we need to pass real screen coords? Cause what we do already works...
|
||||
//var screenCoords = _control.ScreenCoordinates;
|
||||
//rect = new CefRectangle((int) screenCoords.X, (int) screenCoords.Y, (int)Math.Max(_control.Size.X, 1), (int)Math.Max(_control.Size.Y, 1));
|
||||
|
||||
// We do the max between size and 1 because it will LITERALLY CRASH WITHOUT AN ERROR otherwise.
|
||||
rect = new CefRectangle(
|
||||
0, 0,
|
||||
(int)Math.Max(_control.Owner.Size.X, 1), (int)Math.Max(_control.Owner.Size.Y, 1));
|
||||
}
|
||||
|
||||
protected override bool GetScreenInfo(CefBrowser browser, CefScreenInfo screenInfo)
|
||||
{
|
||||
if (_control.Owner.Disposed)
|
||||
return false;
|
||||
|
||||
screenInfo.DeviceScaleFactor = _control.Owner.UIScale;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnPopupSize(CefBrowser browser, CefRectangle rect)
|
||||
{
|
||||
if (_control.Owner.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnPaint(CefBrowser browser, CefPaintElementType type, CefRectangle[] dirtyRects,
|
||||
IntPtr buffer, int width, int height)
|
||||
{
|
||||
if (_control.Owner.Disposed)
|
||||
return;
|
||||
|
||||
foreach (var dirtyRect in dirtyRects)
|
||||
{
|
||||
Buffer.UpdateBuffer(width, height, buffer, dirtyRect);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAcceleratedPaint(CefBrowser browser, CefPaintElementType type,
|
||||
CefRectangle[] dirtyRects, IntPtr sharedHandle)
|
||||
{
|
||||
// Unused, but we're forced to implement it so.. NOOP.
|
||||
}
|
||||
|
||||
protected override void OnScrollOffsetChanged(CefBrowser browser, double x, double y)
|
||||
{
|
||||
if (_control.Owner.Disposed)
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnImeCompositionRangeChanged(CefBrowser browser, CefRange selectedRange,
|
||||
CefRectangle[] characterBounds)
|
||||
{
|
||||
if (_control.Owner.Disposed)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
103
Robust.Client.WebView/Cef/WebViewManagerCef.cs
Normal file
103
Robust.Client.WebView/Cef/WebViewManagerCef.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
internal partial class WebViewManagerCef : IWebViewManagerImpl
|
||||
{
|
||||
private CefApp _app = default!;
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
IoCManager.Instance!.InjectDependencies(this, oneOff: true);
|
||||
|
||||
string subProcessName;
|
||||
if (OperatingSystem.IsWindows())
|
||||
subProcessName = "Robust.Client.WebView.exe";
|
||||
else if (OperatingSystem.IsLinux())
|
||||
subProcessName = "Robust.Client.WebView";
|
||||
else
|
||||
throw new NotSupportedException("Unsupported platform for CEF!");
|
||||
|
||||
var subProcessPath = PathHelpers.ExecutableRelativeFile(subProcessName);
|
||||
var cefResourcesPath = LocateCefResources();
|
||||
|
||||
// System.Console.WriteLine(AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES"));
|
||||
|
||||
if (cefResourcesPath == null)
|
||||
throw new InvalidOperationException("Unable to locate cef_resources directory!");
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
WindowlessRenderingEnabled = true, // So we can render to our UI controls.
|
||||
ExternalMessagePump = false, // Unsure, honestly. TODO CEF: Research this?
|
||||
NoSandbox = true, // Not disabling the sandbox crashes CEF.
|
||||
BrowserSubprocessPath = subProcessPath,
|
||||
LocalesDirPath = Path.Combine(cefResourcesPath, "locales"),
|
||||
ResourcesDirPath = cefResourcesPath,
|
||||
RemoteDebuggingPort = 9222,
|
||||
};
|
||||
|
||||
Logger.Info($"CEF Version: {CefRuntime.ChromeVersion}");
|
||||
|
||||
_app = new RobustCefApp();
|
||||
|
||||
// We pass no main arguments...
|
||||
CefRuntime.Initialize(new CefMainArgs(null), settings, _app, IntPtr.Zero);
|
||||
|
||||
// TODO CEF: After this point, debugging breaks. No, literally. My client crashes but ONLY with the debugger.
|
||||
// I have tried using the DEBUG and RELEASE versions of libcef.so, stripped or non-stripped...
|
||||
// And nothing seemed to work. Odd.
|
||||
}
|
||||
|
||||
private static string? LocateCefResources()
|
||||
{
|
||||
if (ProbeDir(PathHelpers.GetExecutableDirectory(), out var path))
|
||||
return path;
|
||||
|
||||
|
||||
foreach (var searchDir in NativeDllSearchDirectories())
|
||||
{
|
||||
if (ProbeDir(searchDir, out path))
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
static bool ProbeDir(string dir, out string path)
|
||||
{
|
||||
path = Path.Combine(dir, "cef_resources");
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string[] NativeDllSearchDirectories()
|
||||
{
|
||||
var sepChar = OperatingSystem.IsWindows() ? ';' : ':';
|
||||
|
||||
var searchDirectories = ((string)AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES")!)
|
||||
.Split(sepChar, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
return searchDirectories;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Calling this makes CEF do its work, without using its own update loop.
|
||||
CefRuntime.DoMessageLoopWork();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
CefRuntime.Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
127
Robust.Client.WebView/Headless/WebViewManagerHeadless.cs
Normal file
127
Robust.Client.WebView/Headless/WebViewManagerHeadless.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Robust.Client.WebView.Headless
|
||||
{
|
||||
internal sealed class WebViewManagerHeadless : IWebViewManagerImpl
|
||||
{
|
||||
public IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
|
||||
{
|
||||
return new WebViewWindowDummy();
|
||||
}
|
||||
|
||||
public IWebViewControlImpl MakeControlImpl(WebViewControl owner)
|
||||
{
|
||||
return new WebViewControlImplDummy();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
// Nop
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Nop
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
// Nop
|
||||
}
|
||||
|
||||
private abstract class DummyBase : IWebViewControl
|
||||
{
|
||||
public string Url { get; set; } = "about:blank";
|
||||
public bool IsLoading => true;
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WebViewControlImplDummy : DummyBase, IWebViewControlImpl
|
||||
{
|
||||
public void EnteredTree()
|
||||
{
|
||||
}
|
||||
|
||||
public void ExitedTree()
|
||||
{
|
||||
}
|
||||
|
||||
public void MouseMove(GUIMouseMoveEventArgs args)
|
||||
{
|
||||
}
|
||||
|
||||
public void MouseExited()
|
||||
{
|
||||
}
|
||||
|
||||
public void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
}
|
||||
|
||||
public bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void TextEntered(GUITextEventArgs args)
|
||||
{
|
||||
}
|
||||
|
||||
public void Resized()
|
||||
{
|
||||
}
|
||||
|
||||
public void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
|
||||
{
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class WebViewWindowDummy : DummyBase, IWebViewWindow
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool Closed => false;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Robust.Client.WebView/IBeforeBrowseContext.cs
Normal file
12
Robust.Client.WebView/IBeforeBrowseContext.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
public interface IBeforeBrowseContext
|
||||
{
|
||||
string Url { get; }
|
||||
string Method { get; }
|
||||
bool IsRedirect { get; }
|
||||
bool UserGesture { get; }
|
||||
bool IsCancelled { get; }
|
||||
void DoCancel();
|
||||
}
|
||||
}
|
||||
18
Robust.Client.WebView/IRequestHandlerContext.cs
Normal file
18
Robust.Client.WebView/IRequestHandlerContext.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
public interface IRequestHandlerContext
|
||||
{
|
||||
bool IsNavigation { get; }
|
||||
bool IsDownload { get; }
|
||||
string RequestInitiator { get; }
|
||||
string Url { get; }
|
||||
string Method { get; }
|
||||
bool IsHandled { get; }
|
||||
bool IsCancelled { get; }
|
||||
void DoCancel();
|
||||
void DoRespondStream(Stream stream, string contentType, HttpStatusCode code = HttpStatusCode.OK);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using Robust.Client.WebView.Cef;
|
||||
|
||||
namespace Robust.Client.CEF
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
public interface IBrowserControl
|
||||
public interface IWebViewControl
|
||||
{
|
||||
/// <summary>
|
||||
/// Current URL of the browser. Set to load a new page.
|
||||
@@ -42,7 +43,7 @@ namespace Robust.Client.CEF
|
||||
/// <param name="code">JavaScript code.</param>
|
||||
void ExecuteJavaScript(string code);
|
||||
|
||||
void AddResourceRequestHandler(Action<RequestHandlerContext> handler);
|
||||
void RemoveResourceRequestHandler(Action<RequestHandlerContext> handler);
|
||||
void AddResourceRequestHandler(Action<IRequestHandlerContext> handler);
|
||||
void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler);
|
||||
}
|
||||
}
|
||||
24
Robust.Client.WebView/IWebViewControlImpl.cs
Normal file
24
Robust.Client.WebView/IWebViewControlImpl.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal swappable implementation of <see cref="WebViewControl"/>.
|
||||
/// </summary>
|
||||
internal interface IWebViewControlImpl : IWebViewControl
|
||||
{
|
||||
void EnteredTree();
|
||||
void ExitedTree();
|
||||
void MouseMove(GUIMouseMoveEventArgs args);
|
||||
void MouseExited();
|
||||
void MouseWheel(GUIMouseWheelEventArgs args);
|
||||
bool RawKeyEvent(in GuiRawKeyEvent guiRawEvent);
|
||||
void TextEntered(GUITextEventArgs args);
|
||||
void Resized();
|
||||
void Draw(DrawingHandleScreen handle);
|
||||
void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler);
|
||||
void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler);
|
||||
}
|
||||
}
|
||||
7
Robust.Client.WebView/IWebViewManager.cs
Normal file
7
Robust.Client.WebView/IWebViewManager.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
public interface IWebViewManager
|
||||
{
|
||||
IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams);
|
||||
}
|
||||
}
|
||||
14
Robust.Client.WebView/IWebViewManagerImpl.cs
Normal file
14
Robust.Client.WebView/IWebViewManagerImpl.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Robust.Client.WebViewHook;
|
||||
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal implementation of WebViewManager that is switched out by <see cref="IWebViewManagerHook"/>.
|
||||
/// </summary>
|
||||
internal interface IWebViewManagerImpl : IWebViewManagerInternal
|
||||
{
|
||||
void Initialize();
|
||||
void Update();
|
||||
void Shutdown();
|
||||
}
|
||||
}
|
||||
7
Robust.Client.WebView/IWebViewManagerInternal.cs
Normal file
7
Robust.Client.WebView/IWebViewManagerInternal.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
internal interface IWebViewManagerInternal : IWebViewManager
|
||||
{
|
||||
IWebViewControlImpl MakeControlImpl(WebViewControl owner);
|
||||
}
|
||||
}
|
||||
9
Robust.Client.WebView/IWebViewWindow.cs
Normal file
9
Robust.Client.WebView/IWebViewWindow.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
public interface IWebViewWindow : IWebViewControl, IDisposable
|
||||
{
|
||||
bool Closed { get; }
|
||||
}
|
||||
}
|
||||
25
Robust.Client.WebView/Robust.Client.WebView.csproj
Normal file
25
Robust.Client.WebView/Robust.Client.WebView.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>WinExe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<Target Name="RobustAfterBuild" AfterTargets="Build" />
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
|
||||
<PackageReference Include="Robust.Natives.Cef" Version="95.7.14" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\cefglue\CefGlue\CefGlue.csproj" />
|
||||
<ProjectReference Include="..\Robust.Client\Robust.Client.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
145
Robust.Client.WebView/WebViewControl.cs
Normal file
145
Robust.Client.WebView/WebViewControl.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.WebView.Cef;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
/// <summary>
|
||||
/// An UI control that presents web content.
|
||||
/// </summary>
|
||||
public sealed class WebViewControl : Control, IWebViewControl, IRawInputControl
|
||||
{
|
||||
[Dependency] private readonly IWebViewManagerInternal _webViewManager = default!;
|
||||
|
||||
private readonly IWebViewControlImpl _controlImpl;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
{
|
||||
get => _controlImpl.Url;
|
||||
set => _controlImpl.Url = value;
|
||||
}
|
||||
|
||||
[ViewVariables] public bool IsLoading => _controlImpl.IsLoading;
|
||||
|
||||
public WebViewControl()
|
||||
{
|
||||
CanKeyboardFocus = true;
|
||||
KeyboardFocusOnClick = true;
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_controlImpl = _webViewManager.MakeControlImpl(this);
|
||||
}
|
||||
|
||||
protected override void EnteredTree()
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
_controlImpl.EnteredTree();
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
_controlImpl.ExitedTree();
|
||||
}
|
||||
|
||||
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
{
|
||||
base.MouseMove(args);
|
||||
|
||||
_controlImpl.MouseMove(args);
|
||||
}
|
||||
|
||||
protected internal override void MouseExited()
|
||||
{
|
||||
base.MouseExited();
|
||||
|
||||
_controlImpl.MouseExited();
|
||||
}
|
||||
|
||||
protected internal override void MouseWheel(GUIMouseWheelEventArgs args)
|
||||
{
|
||||
base.MouseWheel(args);
|
||||
|
||||
_controlImpl.MouseWheel(args);
|
||||
}
|
||||
|
||||
bool IRawInputControl.RawKeyEvent(in GuiRawKeyEvent guiRawEvent)
|
||||
{
|
||||
return _controlImpl.RawKeyEvent(guiRawEvent);
|
||||
}
|
||||
|
||||
protected internal override void TextEntered(GUITextEventArgs args)
|
||||
{
|
||||
base.TextEntered(args);
|
||||
|
||||
_controlImpl.TextEntered(args);
|
||||
}
|
||||
|
||||
protected override void Resized()
|
||||
{
|
||||
base.Resized();
|
||||
|
||||
_controlImpl.Resized();
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
_controlImpl.Draw(handle);
|
||||
}
|
||||
|
||||
public void StopLoad()
|
||||
{
|
||||
_controlImpl.StopLoad();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
_controlImpl.Reload();
|
||||
}
|
||||
|
||||
public bool GoBack()
|
||||
{
|
||||
return _controlImpl.GoBack();
|
||||
}
|
||||
|
||||
public bool GoForward()
|
||||
{
|
||||
return _controlImpl.GoForward();
|
||||
}
|
||||
|
||||
public void ExecuteJavaScript(string code)
|
||||
{
|
||||
_controlImpl.ExecuteJavaScript(code);
|
||||
}
|
||||
|
||||
public void AddResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
_controlImpl.AddResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveResourceRequestHandler(Action<IRequestHandlerContext> handler)
|
||||
{
|
||||
_controlImpl.RemoveResourceRequestHandler(handler);
|
||||
}
|
||||
|
||||
public void AddBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
|
||||
{
|
||||
_controlImpl.AddBeforeBrowseHandler(handler);
|
||||
}
|
||||
|
||||
public void RemoveBeforeBrowseHandler(Action<IBeforeBrowseContext> handler)
|
||||
{
|
||||
_controlImpl.RemoveBeforeBrowseHandler(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Robust.Client.WebView/WebViewManager.cs
Normal file
59
Robust.Client.WebView/WebViewManager.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Robust.Client.WebView;
|
||||
using Robust.Client.WebView.Cef;
|
||||
using Robust.Client.WebView.Headless;
|
||||
using Robust.Client.WebViewHook;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
[assembly: WebViewManagerImpl(typeof(WebViewManager))]
|
||||
|
||||
namespace Robust.Client.WebView
|
||||
{
|
||||
internal sealed class WebViewManager : IWebViewManagerInternal, IWebViewManagerHook
|
||||
{
|
||||
private IWebViewManagerImpl? _impl;
|
||||
|
||||
public void Initialize(GameController.DisplayMode mode)
|
||||
{
|
||||
DebugTools.Assert(_impl == null, "WebViewManager has already been initialized!");
|
||||
|
||||
IoCManager.RegisterInstance<IWebViewManager>(this);
|
||||
IoCManager.RegisterInstance<IWebViewManagerInternal>(this);
|
||||
|
||||
if (mode == GameController.DisplayMode.Headless)
|
||||
_impl = new WebViewManagerHeadless();
|
||||
else
|
||||
_impl = new WebViewManagerCef();
|
||||
|
||||
_impl.Initialize();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
|
||||
|
||||
_impl!.Update();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
|
||||
|
||||
_impl!.Shutdown();
|
||||
}
|
||||
|
||||
public IWebViewWindow CreateBrowserWindow(BrowserWindowCreateParameters createParams)
|
||||
{
|
||||
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
|
||||
|
||||
return _impl!.CreateBrowserWindow(createParams);
|
||||
}
|
||||
|
||||
public IWebViewControlImpl MakeControlImpl(WebViewControl owner)
|
||||
{
|
||||
DebugTools.Assert(_impl != null, "WebViewManager has not yet been initialized!");
|
||||
|
||||
return _impl!.MakeControlImpl(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Animations
|
||||
{
|
||||
@@ -18,8 +19,8 @@ namespace Robust.Client.Animations
|
||||
throw new InvalidOperationException("Must set parameters to non-null values.");
|
||||
}
|
||||
|
||||
var entity = (IEntity) context;
|
||||
var component = entity.GetComponent(ComponentType);
|
||||
var entity = (EntityUid) context;
|
||||
var component = IoCManager.Resolve<IEntityManager>().GetComponent(entity, ComponentType);
|
||||
|
||||
if (component is IAnimationProperties properties)
|
||||
{
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Robust.Client.Animations
|
||||
public override (int KeyFrameIndex, float FramePlayingTime)
|
||||
AdvancePlayback(object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
|
||||
{
|
||||
var entity = (IEntity) context;
|
||||
var entity = (EntityUid) context;
|
||||
|
||||
var playingTime = prevPlayingTime + frameTime;
|
||||
var keyFrameIndex = prevKeyFrameIndex;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Animations
|
||||
@@ -38,8 +39,8 @@ namespace Robust.Client.Animations
|
||||
{
|
||||
DebugTools.AssertNotNull(LayerKey);
|
||||
|
||||
var entity = (IEntity) context;
|
||||
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||
var entity = (EntityUid) context;
|
||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(entity);
|
||||
|
||||
var playingTime = prevPlayingTime + frameTime;
|
||||
var keyFrameIndex = prevKeyFrameIndex;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Client.Audio.Midi
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
|
||||
private SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
private SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsAvailable
|
||||
@@ -88,6 +88,7 @@ namespace Robust.Client.Audio.Midi
|
||||
private float _volume = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
// Not reliable until Fluidsynth is initialized!
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
{
|
||||
@@ -134,6 +135,7 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
if (FluidsynthInitialized || _failedInitialize) return;
|
||||
|
||||
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
@@ -175,7 +177,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadphaseSystem>();
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
@@ -254,11 +256,12 @@ namespace Robust.Client.Audio.Midi
|
||||
renderer.LoadSoundfont(WindowsSoundfont, true);
|
||||
}
|
||||
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
|
||||
return renderer;
|
||||
}
|
||||
finally
|
||||
@@ -296,7 +299,7 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
else if (renderer.TrackingEntity != null)
|
||||
{
|
||||
mapPos = renderer.TrackingEntity.Transform.MapPosition;
|
||||
mapPos = _entityManager.GetComponent<TransformComponent>(renderer.TrackingEntity.Value).MapPosition;
|
||||
}
|
||||
|
||||
if (mapPos != null)
|
||||
@@ -332,7 +335,7 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
if (renderer.TrackingEntity != null)
|
||||
{
|
||||
renderer.Source.SetVelocity(renderer.TrackingEntity.GlobalLinearVelocity());
|
||||
renderer.Source.SetVelocity(renderer.TrackingEntity.Value.GlobalLinearVelocity());
|
||||
}
|
||||
|
||||
if (float.IsNaN(pos.Position.X) || float.IsNaN(pos.Position.Y))
|
||||
|
||||
@@ -37,6 +37,11 @@ namespace Robust.Client.Audio.Midi
|
||||
/// </summary>
|
||||
bool LoopMidi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This increases all note on velocities to 127.
|
||||
/// </summary>
|
||||
bool VolumeBoost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The midi program (instrument) the renderer is using.
|
||||
/// </summary>
|
||||
@@ -145,7 +150,7 @@ namespace Robust.Client.Audio.Midi
|
||||
/// The entity whose position will be used for positional audio.
|
||||
/// This is only used if <see cref="Mono"/> is set to True.
|
||||
/// </summary>
|
||||
IEntity? TrackingEntity { get; set; }
|
||||
EntityUid? TrackingEntity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The position that will be used for positional audio.
|
||||
@@ -292,13 +297,16 @@ namespace Robust.Client.Audio.Midi
|
||||
set
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
_player?.SetLoop(value ? -1 : 1);
|
||||
_player?.SetLoop(value ? -1 : 0);
|
||||
_loopMidi = value;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public IEntity? TrackingEntity { get; set; } = null;
|
||||
public bool VolumeBoost { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityUid? TrackingEntity { get; set; } = null;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public EntityCoordinates? TrackingCoordinates { get; set; } = null;
|
||||
@@ -539,6 +547,8 @@ namespace Robust.Client.Audio.Midi
|
||||
|
||||
// Note On 0x90
|
||||
case 144:
|
||||
if (VolumeBoost)
|
||||
midiEvent.Velocity = 127;
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
break;
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _gameStates = default!;
|
||||
[Dependency] private readonly IDebugDrawingManager _debugDrawMan = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ushort DefaultPort { get; } = 1212;
|
||||
@@ -57,7 +56,6 @@ namespace Robust.Client
|
||||
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
|
||||
|
||||
_playMan.Initialize();
|
||||
_debugDrawMan.Initialize();
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Client.Debugging;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Audio;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
@@ -72,7 +73,6 @@ namespace Robust.Client
|
||||
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
|
||||
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
|
||||
IoCManager.Register<IDebugDrawing, DebugDrawing>();
|
||||
IoCManager.Register<IDebugDrawingManager, DebugDrawingManager>();
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
@@ -83,8 +83,9 @@ namespace Robust.Client
|
||||
case GameController.DisplayMode.Headless:
|
||||
IoCManager.Register<IClyde, ClydeHeadless>();
|
||||
IoCManager.Register<IClipboardManager, ClydeHeadless>();
|
||||
IoCManager.Register<IClydeAudio, ClydeHeadless>();
|
||||
IoCManager.Register<IClydeInternal, ClydeHeadless>();
|
||||
IoCManager.Register<IClydeAudio, ClydeAudioHeadless>();
|
||||
IoCManager.Register<IClydeAudioInternal, ClydeAudioHeadless>();
|
||||
IoCManager.Register<IInputManager, InputManager>();
|
||||
IoCManager.Register<IFileDialogManager, DummyFileDialogManager>();
|
||||
IoCManager.Register<IUriOpener, UriOpenerDummy>();
|
||||
@@ -92,8 +93,9 @@ namespace Robust.Client
|
||||
case GameController.DisplayMode.Clyde:
|
||||
IoCManager.Register<IClyde, Clyde>();
|
||||
IoCManager.Register<IClipboardManager, Clyde>();
|
||||
IoCManager.Register<IClydeAudio, Clyde>();
|
||||
IoCManager.Register<IClydeInternal, Clyde>();
|
||||
IoCManager.Register<IClydeAudio, FallbackProxyClydeAudio>();
|
||||
IoCManager.Register<IClydeAudioInternal, FallbackProxyClydeAudio>();
|
||||
IoCManager.Register<IInputManager, ClydeInputManager>();
|
||||
IoCManager.Register<IFileDialogManager, FileDialogManager>();
|
||||
IoCManager.Register<IUriOpener, UriOpener>();
|
||||
|
||||
@@ -4,177 +4,186 @@ using Robust.Shared;
|
||||
using Robust.Shared.Utility;
|
||||
using C = System.Console;
|
||||
|
||||
namespace Robust.Client
|
||||
namespace Robust.Client;
|
||||
|
||||
internal sealed class CommandLineArgs
|
||||
{
|
||||
internal sealed class CommandLineArgs
|
||||
public MountOptions MountOptions { get; }
|
||||
public bool Headless { get; }
|
||||
public bool SelfContained { get; }
|
||||
public bool Connect { get; }
|
||||
public string ConnectAddress { get; }
|
||||
public string? Ss14Address { get; }
|
||||
public bool Launcher { get; }
|
||||
public string? Username { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> CVars { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
|
||||
public IReadOnlyList<string> ExecCommands { get; set; }
|
||||
|
||||
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
|
||||
// Also I don't like spending 100ms parsing command line args. Do you?
|
||||
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
|
||||
{
|
||||
public MountOptions MountOptions { get; }
|
||||
public bool Headless { get; }
|
||||
public bool SelfContained { get; }
|
||||
public bool Connect { get; }
|
||||
public string ConnectAddress { get; }
|
||||
public string? Ss14Address { get; }
|
||||
public bool Launcher { get; }
|
||||
public string? Username { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> CVars { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
|
||||
parsed = null;
|
||||
var headless = false;
|
||||
var selfContained = false;
|
||||
var connect = false;
|
||||
var connectAddress = "localhost";
|
||||
string? ss14Address = null;
|
||||
var launcher = false;
|
||||
string? username = null;
|
||||
var cvars = new List<(string, string)>();
|
||||
var logLevels = new List<(string, string)>();
|
||||
var mountOptions = new MountOptions();
|
||||
var execCommands = new List<string>();
|
||||
|
||||
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
|
||||
// Also I don't like spending 100ms parsing command line args. Do you?
|
||||
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
|
||||
using var enumerator = args.GetEnumerator();
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
parsed = null;
|
||||
var headless = false;
|
||||
var selfContained = false;
|
||||
var connect = false;
|
||||
var connectAddress = "localhost";
|
||||
string? ss14Address = null;
|
||||
var launcher = false;
|
||||
string? username = null;
|
||||
var cvars = new List<(string, string)>();
|
||||
var logLevels = new List<(string, string)>();
|
||||
var mountOptions = new MountOptions();
|
||||
|
||||
using var enumerator = args.GetEnumerator();
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
var arg = enumerator.Current;
|
||||
if (arg == "--connect")
|
||||
{
|
||||
var arg = enumerator.Current;
|
||||
if (arg == "--connect")
|
||||
connect = true;
|
||||
}
|
||||
else if (arg == "--connect-address")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
connect = true;
|
||||
}
|
||||
else if (arg == "--connect-address")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing connection address.");
|
||||
return false;
|
||||
}
|
||||
|
||||
connectAddress = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--ss14-address")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing SS14 address.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ss14Address = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--self-contained")
|
||||
{
|
||||
selfContained = true;
|
||||
}
|
||||
else if (arg == "--launcher")
|
||||
{
|
||||
launcher = true;
|
||||
}
|
||||
else if (arg == "--headless")
|
||||
{
|
||||
headless = true;
|
||||
}
|
||||
else if (arg == "--username")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing username.");
|
||||
return false;
|
||||
}
|
||||
|
||||
username = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--cvar")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing cvar value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var cvar = enumerator.Current;
|
||||
DebugTools.AssertNotNull(cvar);
|
||||
var pos = cvar.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in cvar.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cvars.Add((cvar[..pos], cvar[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--mount-zip")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.ZipMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--mount-dir")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.DirMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--loglevel")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing loglevel sawmill.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var loglevel = enumerator.Current;
|
||||
DebugTools.AssertNotNull(loglevel);
|
||||
var pos = loglevel.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in loglevel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
C.WriteLine("Missing connection address.");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
C.WriteLine("Unknown argument: {0}", arg);
|
||||
}
|
||||
|
||||
connectAddress = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--ss14-address")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing SS14 address.");
|
||||
return false;
|
||||
}
|
||||
|
||||
parsed = new CommandLineArgs(
|
||||
headless,
|
||||
selfContained,
|
||||
connect,
|
||||
launcher,
|
||||
username,
|
||||
cvars,
|
||||
logLevels,
|
||||
connectAddress,
|
||||
ss14Address,
|
||||
mountOptions);
|
||||
ss14Address = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--self-contained")
|
||||
{
|
||||
selfContained = true;
|
||||
}
|
||||
else if (arg == "--launcher")
|
||||
{
|
||||
launcher = true;
|
||||
}
|
||||
else if (arg == "--headless")
|
||||
{
|
||||
headless = true;
|
||||
}
|
||||
else if (arg == "--username")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing username.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
username = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--cvar")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing cvar value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var cvar = enumerator.Current;
|
||||
DebugTools.AssertNotNull(cvar);
|
||||
var pos = cvar.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in cvar.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cvars.Add((cvar[..pos], cvar[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--mount-zip")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.ZipMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--mount-dir")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.DirMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--loglevel")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing loglevel sawmill.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var loglevel = enumerator.Current;
|
||||
DebugTools.AssertNotNull(loglevel);
|
||||
var pos = loglevel.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in loglevel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
return false;
|
||||
}
|
||||
else if (arg.StartsWith("+"))
|
||||
{
|
||||
execCommands.Add(arg[1..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
C.WriteLine("Unknown argument: {0}", arg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
C.WriteLine(@"
|
||||
parsed = new CommandLineArgs(
|
||||
headless,
|
||||
selfContained,
|
||||
connect,
|
||||
launcher,
|
||||
username,
|
||||
cvars,
|
||||
logLevels,
|
||||
connectAddress,
|
||||
ss14Address,
|
||||
mountOptions,
|
||||
execCommands);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
C.WriteLine(@"
|
||||
Usage: Robust.Client [options] [+command [+command]]
|
||||
|
||||
Options:
|
||||
--headless Run without graphics/audio/input.
|
||||
--self-contained Store data relative to executable instead of user-global locations.
|
||||
@@ -189,30 +198,34 @@ Options:
|
||||
--mount-dir Resource directory to mount.
|
||||
--mount-zip Resource zip to mount.
|
||||
--help Display this help text and exit.
|
||||
");
|
||||
}
|
||||
|
||||
private CommandLineArgs(
|
||||
bool headless,
|
||||
bool selfContained,
|
||||
bool connect,
|
||||
bool launcher,
|
||||
string? username,
|
||||
IReadOnlyCollection<(string key, string value)> cVars,
|
||||
IReadOnlyCollection<(string key, string value)> logLevels,
|
||||
string connectAddress, string? ss14Address,
|
||||
MountOptions mountOptions)
|
||||
{
|
||||
Headless = headless;
|
||||
SelfContained = selfContained;
|
||||
Connect = connect;
|
||||
Launcher = launcher;
|
||||
Username = username;
|
||||
CVars = cVars;
|
||||
LogLevels = logLevels;
|
||||
ConnectAddress = connectAddress;
|
||||
Ss14Address = ss14Address;
|
||||
MountOptions = mountOptions;
|
||||
}
|
||||
+command: You can pass a set of commands, prefixed by +,
|
||||
to be executed in the console in order after the game has finished initializing.
|
||||
");
|
||||
}
|
||||
|
||||
private CommandLineArgs(
|
||||
bool headless,
|
||||
bool selfContained,
|
||||
bool connect,
|
||||
bool launcher,
|
||||
string? username,
|
||||
IReadOnlyCollection<(string key, string value)> cVars,
|
||||
IReadOnlyCollection<(string key, string value)> logLevels,
|
||||
string connectAddress, string? ss14Address,
|
||||
MountOptions mountOptions,
|
||||
IReadOnlyList<string> execCommands)
|
||||
{
|
||||
Headless = headless;
|
||||
SelfContained = selfContained;
|
||||
Connect = connect;
|
||||
Launcher = launcher;
|
||||
Username = username;
|
||||
CVars = cVars;
|
||||
LogLevels = logLevels;
|
||||
ConnectAddress = connectAddress;
|
||||
Ss14Address = ss14Address;
|
||||
MountOptions = mountOptions;
|
||||
ExecCommands = execCommands;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,13 +21,12 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
var entityUid = EntityUid.Parse(args[0]);
|
||||
var entity = EntityUid.Parse(args[0]);
|
||||
var componentName = args[1];
|
||||
|
||||
var compFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
var entity = entityManager.GetEntity(entityUid);
|
||||
var component = (Component) compFactory.GetComponent(componentName);
|
||||
|
||||
component.Owner = entity;
|
||||
|
||||
@@ -15,15 +15,15 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var player = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
|
||||
if (player?.ControlledEntity == null)
|
||||
var controlled = IoCManager.Resolve<IPlayerManager>().LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
|
||||
if (controlled == EntityUid.Invalid)
|
||||
{
|
||||
shell.WriteLine("You don't have an attached entity.");
|
||||
return;
|
||||
}
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
entityManager.SpawnEntity(args[0], player.ControlledEntity.Transform.Coordinates);
|
||||
entityManager.SpawnEntity(args[0], entityManager.GetComponent<TransformComponent>(controlled).Coordinates);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,12 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -41,9 +40,9 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
foreach (var e in entityManager.GetEntities().OrderBy(e => e.Uid))
|
||||
foreach (var e in entityManager.GetEntities().OrderBy(e => e))
|
||||
{
|
||||
shell.WriteLine($"entity {e.Uid}, {e.Prototype?.ID}, {e.Transform.Coordinates}.");
|
||||
shell.WriteLine($"entity {e}, {entityManager.GetComponent<MetaDataComponent>(e).EntityPrototype?.ID}, {entityManager.GetComponent<TransformComponent>(e).Coordinates}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,19 +160,6 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal class ShowBoundingBoxesCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showbb";
|
||||
public string Help => "";
|
||||
public string Description => "Enables debug drawing over all bounding boxes in the game, showing their size.";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = IoCManager.Resolve<IDebugDrawing>();
|
||||
mgr.DebugColliders = !mgr.DebugColliders;
|
||||
}
|
||||
}
|
||||
|
||||
internal class ShowPositionsCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showpos";
|
||||
@@ -201,16 +187,16 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var id))
|
||||
if (!float.TryParse(args[0], out var duration))
|
||||
{
|
||||
shell.WriteError($"{args[0]} is not a valid integer.");
|
||||
shell.WriteError($"{args[0]} is not a valid float.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mgr = IoCManager.Resolve<IDebugDrawingManager>();
|
||||
var mgr = EntitySystem.Get<DebugRayDrawingSystem>();
|
||||
mgr.DebugDrawRays = !mgr.DebugDrawRays;
|
||||
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays.ToString());
|
||||
mgr.DebugRayLifetime = TimeSpan.FromSeconds((double)int.Parse(args[0], CultureInfo.InvariantCulture));
|
||||
shell.WriteError("Toggled showing rays to:" + mgr.DebugDrawRays);
|
||||
mgr.DebugRayLifetime = TimeSpan.FromSeconds(duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,15 +237,15 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
var uid = EntityUid.Parse(args[0]);
|
||||
var entmgr = IoCManager.Resolve<IEntityManager>();
|
||||
if (!entmgr.TryGetEntity(uid, out var entity))
|
||||
if (!entmgr.EntityExists(uid))
|
||||
{
|
||||
shell.WriteError("That entity does not exist. Sorry lad.");
|
||||
return;
|
||||
}
|
||||
|
||||
shell.WriteLine($"{entity.Uid}: {entity.Prototype?.ID}/{entity.Name}");
|
||||
shell.WriteLine($"init/del/lmt: {entity.Initialized}/{entity.Deleted}/{entity.LastModifiedTick}");
|
||||
foreach (var component in entity.GetAllComponents())
|
||||
var meta = entmgr.GetComponent<MetaDataComponent>(uid);
|
||||
shell.WriteLine($"{uid}: {meta.EntityPrototype?.ID}/{meta.EntityName}");
|
||||
shell.WriteLine($"init/del/lmt: {meta.EntityInitialized}/{meta.EntityDeleted}/{meta.EntityLastModifiedTick}");
|
||||
foreach (var component in entmgr.GetComponents(uid))
|
||||
{
|
||||
shell.WriteLine(component.ToString() ?? "");
|
||||
if (component is IComponentDebug debug)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
var conGroup = IoCManager.Resolve<IClientConGroupController>();
|
||||
foreach (var command in shell.ConsoleHost.RegisteredCommands.Values
|
||||
.Where(p => p.Command.Contains(filter) && conGroup.CanCommand(p.Command))
|
||||
.Where(p => p.Command.Contains(filter) && (p is not ServerDummyCommand || conGroup.CanCommand(p.Command)))
|
||||
.OrderBy(c => c.Command))
|
||||
{
|
||||
shell.WriteLine(command.Command + ": " + command.Description);
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace Robust.Client.Console.Commands
|
||||
public sealed class PhysicsOverlayCommands : IConsoleCommand
|
||||
{
|
||||
public string Command => "physics";
|
||||
public string Description => $"{Command} <contactnormals / contactpoints / shapes>";
|
||||
public string Help => $"{Command} <overlay>";
|
||||
public string Description => $"Shows a debug physics overlay. The arg supplied specifies the overlay.";
|
||||
public string Help => $"{Command} <aabbs / contactnormals / contactpoints / joints / shapeinfo / shapes>";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
@@ -21,12 +21,24 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "aabbs":
|
||||
system.Flags ^= PhysicsDebugFlags.AABBs;
|
||||
break;
|
||||
case "com":
|
||||
system.Flags ^= PhysicsDebugFlags.COM;
|
||||
break;
|
||||
case "contactnormals":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactNormals;
|
||||
break;
|
||||
case "contactpoints":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactPoints;
|
||||
break;
|
||||
case "joints":
|
||||
system.Flags ^= PhysicsDebugFlags.Joints;
|
||||
break;
|
||||
case "shapeinfo":
|
||||
system.Flags ^= PhysicsDebugFlags.ShapeInfo;
|
||||
break;
|
||||
case "shapes":
|
||||
system.Flags ^= PhysicsDebugFlags.Shapes;
|
||||
break;
|
||||
|
||||
127
Robust.Client/Console/Completions.cs
Normal file
127
Robust.Client/Console/Completions.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using static Robust.Shared.Network.Messages.MsgScriptCompletionResponse;
|
||||
|
||||
namespace Robust.Client.Console
|
||||
{
|
||||
public class Completions : SS14Window
|
||||
{
|
||||
private HistoryLineEdit _textBar;
|
||||
private ScrollContainer _suggestPanel = new()
|
||||
{
|
||||
HScrollEnabled = false,
|
||||
};
|
||||
|
||||
private BoxContainer _suggestBox = new()
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
HorizontalAlignment = HAlignment.Left,
|
||||
};
|
||||
|
||||
public Completions(HistoryLineEdit textBar) : base()
|
||||
{
|
||||
Title = "Suggestions";
|
||||
MouseFilter = MouseFilterMode.Pass;
|
||||
|
||||
_textBar = textBar;
|
||||
_suggestPanel.AddChild(_suggestBox);
|
||||
ContentsContainer.AddChild(_suggestPanel);
|
||||
}
|
||||
|
||||
private bool _firstopen = true;
|
||||
public void OpenAt(Vector2 position, Vector2 size)
|
||||
{
|
||||
if (_firstopen)
|
||||
{
|
||||
SetSize = size;
|
||||
LayoutContainer.SetPosition(this, position);
|
||||
_firstopen = false;
|
||||
}
|
||||
Open();
|
||||
}
|
||||
|
||||
private ImmutableArray<LiteResult> _results;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_suggestBox.RemoveAllChildren();
|
||||
foreach (var res in _results)
|
||||
{
|
||||
var label = new Entry(res);
|
||||
|
||||
label.OnKeyBindDown += ev =>
|
||||
{
|
||||
if (ev.Function == EngineKeyFunctions.UIClick)
|
||||
_textBar.InsertAtCursor(label.Result.Properties["InsertionText"]);
|
||||
};
|
||||
|
||||
_suggestBox.AddChild(label);
|
||||
}
|
||||
}
|
||||
|
||||
public void TextChanged() => Close();
|
||||
|
||||
public void SetSuggestions(MsgScriptCompletionResponse response)
|
||||
{
|
||||
_results = response.Results;
|
||||
Update();
|
||||
}
|
||||
|
||||
// Label and ghetto button.
|
||||
public class Entry : RichTextLabel
|
||||
{
|
||||
public readonly LiteResult Result;
|
||||
|
||||
public Entry(LiteResult result)
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
Result = result;
|
||||
var compl = new FormattedMessage();
|
||||
var dim = Color.FromHsl((0f, 0f, 0.8f, 1f));
|
||||
|
||||
// warning: ew ahead
|
||||
string basen = "default";
|
||||
if (Result.Tags.Contains("Interface"))
|
||||
basen = "interface name";
|
||||
else if (Result.Tags.Contains("Class"))
|
||||
basen = "class name";
|
||||
else if (Result.Tags.Contains("Struct"))
|
||||
basen = "struct name";
|
||||
else if (Result.Tags.Contains("Keyword"))
|
||||
basen = "keyword";
|
||||
else if (Result.Tags.Contains("Namespace"))
|
||||
basen = "namespace name";
|
||||
else if (Result.Tags.Contains("Method"))
|
||||
basen = "method name";
|
||||
else if (Result.Tags.Contains("Property"))
|
||||
basen = "property name";
|
||||
else if (Result.Tags.Contains("Field"))
|
||||
basen = "field name";
|
||||
|
||||
Color basec = ScriptingColorScheme.ColorScheme[basen];
|
||||
compl.PushColor(basec * dim);
|
||||
compl.AddText(Result.DisplayTextPrefix);
|
||||
compl.PushColor(basec);
|
||||
compl.AddText(Result.DisplayText);
|
||||
compl.PushColor(basec * dim);
|
||||
compl.AddText(Result.DisplayTextSuffix);
|
||||
compl.AddText(" [" + String.Join(", ", Result.Tags) + "]");
|
||||
if (Result.InlineDescription.Length != 0)
|
||||
{
|
||||
compl.PushNewline();
|
||||
compl.AddText(": ");
|
||||
compl.PushColor(Color.LightSlateGray);
|
||||
compl.AddText(Result.InlineDescription);
|
||||
}
|
||||
SetMessage(compl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,16 @@ namespace Robust.Client.Console
|
||||
|
||||
}
|
||||
|
||||
protected override void Complete()
|
||||
{
|
||||
var msg = _client._netManager.CreateNetMessage<MsgScriptCompletion>();
|
||||
msg.ScriptSession = _session;
|
||||
msg.Code = InputBar.Text;
|
||||
msg.Cursor = InputBar.CursorPosition;
|
||||
|
||||
_client._netManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
@@ -95,6 +105,12 @@ namespace Robust.Client.Console
|
||||
|
||||
OutputPanel.AddText(">");
|
||||
}
|
||||
|
||||
public void ReceiveCompletionResponse(MsgScriptCompletionResponse response)
|
||||
{
|
||||
Suggestions.SetSuggestions(response);
|
||||
Suggestions.OpenAt((Position.X + Size.X, Position.Y), (Size.X / 2, Size.Y));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace Robust.Client.Console
|
||||
_netManager.RegisterNetMessage<MsgScriptStop>();
|
||||
_netManager.RegisterNetMessage<MsgScriptEval>();
|
||||
_netManager.RegisterNetMessage<MsgScriptStart>();
|
||||
_netManager.RegisterNetMessage<MsgScriptCompletion>();
|
||||
_netManager.RegisterNetMessage<MsgScriptCompletionResponse>(ReceiveScriptCompletionResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptResponse>(ReceiveScriptResponse);
|
||||
_netManager.RegisterNetMessage<MsgScriptStartAck>(ReceiveScriptStartAckResponse);
|
||||
}
|
||||
@@ -44,6 +46,16 @@ namespace Robust.Client.Console
|
||||
console.ReceiveResponse(message);
|
||||
}
|
||||
|
||||
private void ReceiveScriptCompletionResponse(MsgScriptCompletionResponse message)
|
||||
{
|
||||
if (!_activeConsoles.TryGetValue(message.ScriptSession, out var console))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
console.ReceiveCompletionResponse(message);
|
||||
}
|
||||
|
||||
public bool CanScript => _conGroupController.CanScript();
|
||||
|
||||
public void StartSession()
|
||||
|
||||
@@ -53,6 +53,9 @@ namespace Robust.Client.Console
|
||||
OutputPanel.AddText(">");
|
||||
}
|
||||
|
||||
// No-op for now.
|
||||
protected override void Complete() { }
|
||||
|
||||
protected override async void Run()
|
||||
{
|
||||
var code = InputBar.Text;
|
||||
|
||||
@@ -79,13 +79,13 @@ namespace Robust.Client.Debugging
|
||||
|
||||
foreach (var ent in grid.GetAnchoredEntities(spot))
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(ent, out var entity))
|
||||
if (EntityManager.TryGetComponent<MetaDataComponent>(ent, out var meta))
|
||||
{
|
||||
text.AppendLine($"uid: {ent}, invalid");
|
||||
text.AppendLine($"uid: {ent}, {meta.EntityName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
text.AppendLine($"uid: {ent}, {entity.Name}");
|
||||
text.AppendLine($"uid: {ent}, invalid");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
@@ -22,39 +11,11 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IEntityLookup _lookup = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private bool _debugColliders;
|
||||
private bool _debugPositions;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool DebugColliders
|
||||
{
|
||||
get => _debugColliders;
|
||||
set
|
||||
{
|
||||
if (value == DebugColliders)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_debugColliders = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new PhysicsOverlay(_eyeManager,
|
||||
_prototypeManager, _inputManager, _mapManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<PhysicsOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool DebugPositions
|
||||
{
|
||||
@@ -70,7 +31,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _eyeManager));
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _eyeManager, _entityManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -79,205 +40,19 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
}
|
||||
|
||||
private class PhysicsOverlay : Overlay
|
||||
{
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly IInputManager _inputManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||
private readonly ShaderInstance _shader;
|
||||
private readonly Font _font;
|
||||
|
||||
private Vector2 _hoverStartScreen = Vector2.Zero;
|
||||
private List<IPhysBody> _hoverBodies = new();
|
||||
|
||||
|
||||
public PhysicsOverlay(IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IMapManager mapManager)
|
||||
{
|
||||
_eyeManager = eyeMan;
|
||||
_inputManager = inputManager;
|
||||
_mapManager = mapManager;
|
||||
|
||||
_shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
|
||||
var cache = IoCManager.Resolve<IResourceCache>();
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
switch (args.Space)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen(args);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld(args);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DrawScreen(in OverlayDrawArgs args)
|
||||
{
|
||||
var screenHandle = args.ScreenHandle;
|
||||
var lineHeight = _font.GetLineHeight(1f);
|
||||
Vector2 drawPos = _hoverStartScreen + new Vector2(20, 0) + new Vector2(0, -(_hoverBodies.Count * 4 * lineHeight / 2f));
|
||||
int row = 0;
|
||||
|
||||
foreach (var body in _hoverBodies)
|
||||
{
|
||||
if (body != _hoverBodies[0])
|
||||
{
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
row++;
|
||||
}
|
||||
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
row++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void DrawWorld(in OverlayDrawArgs args)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
worldHandle.UseShader(_shader);
|
||||
var drawing = new PhysDrawingAdapter(worldHandle);
|
||||
|
||||
_hoverBodies.Clear();
|
||||
var mouseScreenPos = _inputManager.MouseScreenPosition;
|
||||
var mouseWorldPos = _eyeManager.ScreenToMap(mouseScreenPos).Position;
|
||||
_hoverStartScreen = mouseScreenPos.Position;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
var viewBounds = _eyeManager.GetWorldViewbounds();
|
||||
|
||||
if (viewport.IsEmpty()) return;
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
var drawnJoints = new HashSet<Joint>();
|
||||
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadphaseSystem>().GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
if (physBody.Owner.HasComponent<MapGridComponent>()) continue;
|
||||
|
||||
// all entities have a TransformComponent
|
||||
var transform = physBody.Owner.Transform;
|
||||
|
||||
var worldBox = physBody.GetWorldAABB();
|
||||
if (worldBox.IsEmpty()) continue;
|
||||
|
||||
var pTransform = physBody.GetTransform();
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
var shape = fixture.Shape;
|
||||
var sleepPercent = physBody.Awake ? 0.0f : 1.0f;
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
|
||||
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
|
||||
var aabb = shape.ComputeAABB(pTransform, 0);
|
||||
worldHandle.DrawRect(aabb, Color.Blue, false);
|
||||
}
|
||||
|
||||
foreach (var joint in physBody.Joints)
|
||||
{
|
||||
if (drawnJoints.Contains(joint)) continue;
|
||||
drawnJoints.Add(joint);
|
||||
|
||||
joint.DebugDraw(drawing, in viewport);
|
||||
drawing.SetTransform(in Matrix3.Identity);
|
||||
}
|
||||
|
||||
if (worldBox.Contains(mouseWorldPos))
|
||||
{
|
||||
_hoverBodies.Add(physBody);
|
||||
}
|
||||
|
||||
// draw AABB
|
||||
worldHandle.DrawRect(worldBox, colorEdge, false);
|
||||
}
|
||||
}
|
||||
|
||||
private class PhysDrawingAdapter : DebugDrawingHandle
|
||||
{
|
||||
private readonly DrawingHandleWorld _handle;
|
||||
|
||||
public PhysDrawingAdapter(DrawingHandleWorld worldHandle)
|
||||
{
|
||||
_handle = worldHandle;
|
||||
}
|
||||
|
||||
public override Color WakeMixColor => Color.White;
|
||||
public override Color GridFillColor => Color.Blue.WithAlpha(0.05f);
|
||||
public override Color RectFillColor => Color.Green.WithAlpha(0.25f);
|
||||
|
||||
public override Color CalcWakeColor(Color color, float wakePercent)
|
||||
{
|
||||
var percent = MathHelper.Clamp(wakePercent, 0, 1);
|
||||
|
||||
var r = 1 - (percent * (1 - color.R));
|
||||
var g = 1 - (percent * (1 - color.G));
|
||||
var b = 1 - (percent * (1 - color.B));
|
||||
|
||||
return new Color(r, g, b, color.A);
|
||||
}
|
||||
|
||||
public override void DrawRect(in Box2 box, in Color color)
|
||||
{
|
||||
_handle.DrawRect(box, color);
|
||||
}
|
||||
|
||||
public override void DrawRect(in Box2Rotated box, in Color color)
|
||||
{
|
||||
_handle.DrawRect(box, color);
|
||||
}
|
||||
|
||||
public override void DrawCircle(Vector2 origin, float radius, in Color color)
|
||||
{
|
||||
_handle.DrawCircle(origin, radius, color);
|
||||
}
|
||||
|
||||
public override void DrawPolygonShape(Vector2[] vertices, in Color color)
|
||||
{
|
||||
_handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, vertices, color);
|
||||
}
|
||||
|
||||
public override void DrawLine(Vector2 start, Vector2 end, in Color color)
|
||||
{
|
||||
_handle.DrawLine(start, end, color);
|
||||
}
|
||||
|
||||
public override void SetTransform(in Matrix3 transform)
|
||||
{
|
||||
_handle.SetTransform(transform);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityPositionOverlay : Overlay
|
||||
{
|
||||
private readonly IEntityLookup _lookup;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager)
|
||||
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager, IEntityManager entityManager)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_eyeManager = eyeManager;
|
||||
_entityManager = entityManager;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
@@ -289,7 +64,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
|
||||
{
|
||||
var transform = entity.Transform;
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(entity);
|
||||
|
||||
var center = transform.WorldPosition;
|
||||
var worldRotation = transform.WorldRotation;
|
||||
|
||||
@@ -20,22 +20,49 @@
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
// MIT License
|
||||
|
||||
// Copyright (c) 2019 Erin Catto
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
/* Heavily inspired by Farseer */
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
using Robust.Shared.Physics.Dynamics.Joints;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
|
||||
public sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
|
||||
{
|
||||
/*
|
||||
* Used for debugging shapes, controllers, joints, contacts
|
||||
@@ -46,7 +73,7 @@ namespace Robust.Client.Debugging
|
||||
private const int MaxContactPoints = 2048;
|
||||
internal int PointCount;
|
||||
|
||||
internal ContactPoint[] _points = new ContactPoint[MaxContactPoints];
|
||||
internal ContactPoint[] Points = new ContactPoint[MaxContactPoints];
|
||||
|
||||
public PhysicsDebugFlags Flags
|
||||
{
|
||||
@@ -56,7 +83,14 @@ namespace Robust.Client.Debugging
|
||||
if (value == _flags) return;
|
||||
|
||||
if (_flags == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(
|
||||
new PhysicsDebugOverlay(
|
||||
EntityManager,
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IInputManager>(),
|
||||
IoCManager.Resolve<IResourceCache>(),
|
||||
this,
|
||||
Get<SharedPhysicsSystem>()));
|
||||
|
||||
if (value == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
|
||||
@@ -69,7 +103,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
public override void HandlePreSolve(Contact contact, in Manifold oldManifold)
|
||||
{
|
||||
if ((Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
if ((Flags & (PhysicsDebugFlags.ContactPoints | PhysicsDebugFlags.ContactNormals)) != 0)
|
||||
{
|
||||
Manifold manifold = contact.Manifold;
|
||||
|
||||
@@ -78,22 +112,24 @@ namespace Robust.Client.Debugging
|
||||
|
||||
Fixture fixtureA = contact.FixtureA!;
|
||||
|
||||
PointState[] state1, state2;
|
||||
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
|
||||
var state1 = new PointState[2];
|
||||
var state2 = new PointState[2];
|
||||
|
||||
CollisionManager.GetPointStates(ref state1, ref state2, oldManifold, manifold);
|
||||
|
||||
Span<Vector2> points = stackalloc Vector2[2];
|
||||
contact.GetWorldManifold(_physicsManager, out var normal, points);
|
||||
|
||||
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
|
||||
ContactPoint cp = Points[PointCount];
|
||||
for (var i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
|
||||
{
|
||||
if (fixtureA == null)
|
||||
_points[i] = new ContactPoint();
|
||||
Points[i] = new ContactPoint();
|
||||
|
||||
ContactPoint cp = _points[PointCount];
|
||||
cp.Position = points[i];
|
||||
cp.Normal = normal;
|
||||
cp.State = state2[i];
|
||||
_points[PointCount] = cp;
|
||||
Points[PointCount] = cp;
|
||||
++PointCount;
|
||||
}
|
||||
}
|
||||
@@ -108,58 +144,348 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum PhysicsDebugFlags : byte
|
||||
public enum PhysicsDebugFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
/// <summary>
|
||||
/// Shows the world point for each contact in the viewport.
|
||||
/// </summary>
|
||||
ContactPoints = 1 << 0,
|
||||
/// <summary>
|
||||
/// Shows the world normal for each contact in the viewport.
|
||||
/// </summary>
|
||||
ContactNormals = 1 << 1,
|
||||
/// <summary>
|
||||
/// Shows all physics shapes in the viewport.
|
||||
/// </summary>
|
||||
Shapes = 1 << 2,
|
||||
ShapeInfo = 1 << 3,
|
||||
Joints = 1 << 4,
|
||||
AABBs = 1 << 5,
|
||||
COM = 1 << 6,
|
||||
}
|
||||
|
||||
internal sealed class PhysicsDebugOverlay : Overlay
|
||||
{
|
||||
private DebugPhysicsSystem _physics = default!;
|
||||
private IEntityManager _entityManager = default!;
|
||||
private IEyeManager _eyeManager = default!;
|
||||
private IInputManager _inputManager = default!;
|
||||
private DebugPhysicsSystem _debugPhysicsSystem = default!;
|
||||
private SharedPhysicsSystem _physicsSystem = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace | OverlaySpace.ScreenSpace;
|
||||
|
||||
public PhysicsDebugOverlay(DebugPhysicsSystem system)
|
||||
private static readonly Color JointColor = new(0.5f, 0.8f, 0.8f);
|
||||
|
||||
private readonly Font _font;
|
||||
|
||||
private HashSet<Joint> _drawnJoints = new();
|
||||
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
|
||||
{
|
||||
_physics = system;
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_inputManager = inputManager;
|
||||
_debugPhysicsSystem = system;
|
||||
_physicsSystem = physicsSystem;
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
private void DrawWorld(DrawingHandleWorld worldHandle, OverlayDrawArgs args)
|
||||
{
|
||||
var viewBounds = args.WorldBounds;
|
||||
var viewAABB = args.WorldAABB;
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
{
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner)) continue;
|
||||
|
||||
var xform = physBody.GetTransform();
|
||||
|
||||
const float AlphaModifier = 0.2f;
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
// Invalid shape - Box2D doesn't check for IsSensor
|
||||
if (physBody.BodyType == BodyType.Dynamic && fixture.Mass == 0f)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, Color.Red.WithAlpha(AlphaModifier));
|
||||
}
|
||||
else if (!physBody.CanCollide)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.3f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
else if (physBody.BodyType == BodyType.Static)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.9f, 0.5f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
else if ((physBody.BodyType & (BodyType.Kinematic | BodyType.KinematicController)) != 0x0)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.9f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
else if (!physBody.Awake)
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.6f, 0.6f, 0.6f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawShape(worldHandle, fixture, xform, new Color(0.9f, 0.7f, 0.7f).WithAlpha(AlphaModifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.COM) != 0)
|
||||
{
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
Color color;
|
||||
const float Alpha = 0.25f;
|
||||
float size;
|
||||
|
||||
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner))
|
||||
{
|
||||
color = Color.Orange.WithAlpha(Alpha);
|
||||
size = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.Purple.WithAlpha(Alpha);
|
||||
size = 0.2f;
|
||||
}
|
||||
|
||||
var transform = physBody.GetTransform();
|
||||
|
||||
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), size, color);
|
||||
}
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.AABBs) != 0)
|
||||
{
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner)) continue;
|
||||
|
||||
var xform = physBody.GetTransform();
|
||||
|
||||
const float AlphaModifier = 0.2f;
|
||||
Box2? aabb = null;
|
||||
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
for (var i = 0; i < fixture.Shape.ChildCount; i++)
|
||||
{
|
||||
var shapeBB = fixture.Shape.ComputeAABB(xform, i);
|
||||
aabb = aabb?.Union(shapeBB) ?? shapeBB;
|
||||
}
|
||||
}
|
||||
|
||||
if (aabb == null) continue;
|
||||
|
||||
worldHandle.DrawRect(aabb.Value, Color.Red.WithAlpha(AlphaModifier), false);
|
||||
}
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Joints) != 0x0)
|
||||
{
|
||||
_drawnJoints.Clear();
|
||||
|
||||
foreach (var jointComponent in _entityManager.EntityQuery<JointComponent>(true))
|
||||
{
|
||||
if (jointComponent.JointCount == 0 ||
|
||||
!_entityManager.TryGetComponent(jointComponent.Owner, out TransformComponent? xf1) ||
|
||||
!viewAABB.Contains(xf1.WorldPosition)) continue;
|
||||
|
||||
foreach (var (_, joint) in jointComponent.Joints)
|
||||
{
|
||||
if (_drawnJoints.Contains(joint)) continue;
|
||||
DrawJoint(worldHandle, joint);
|
||||
_drawnJoints.Add(joint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & (PhysicsDebugFlags.ContactPoints | PhysicsDebugFlags.ContactNormals)) != 0)
|
||||
{
|
||||
const float axisScale = 0.3f;
|
||||
|
||||
for (var i = 0; i < _debugPhysicsSystem.PointCount; ++i)
|
||||
{
|
||||
var point = _debugPhysicsSystem.Points[i];
|
||||
|
||||
const float radius = 0.1f;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
{
|
||||
if (point.State == PointState.Add)
|
||||
worldHandle.DrawCircle(point.Position, radius, new Color(255, 77, 243, 255));
|
||||
else if (point.State == PointState.Persist)
|
||||
worldHandle.DrawCircle(point.Position, radius, new Color(255, 77, 77, 255));
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ContactNormals) != 0)
|
||||
{
|
||||
Vector2 p1 = point.Position;
|
||||
Vector2 p2 = p1 + point.Normal * axisScale;
|
||||
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 255));
|
||||
}
|
||||
}
|
||||
|
||||
_debugPhysicsSystem.PointCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
|
||||
{
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
|
||||
{
|
||||
var hoverBodies = new List<PhysicsComponent>();
|
||||
var bounds = Box2.UnitCentered.Translated(_eyeManager.ScreenToMap(mousePos.Position).Position);
|
||||
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, bounds))
|
||||
{
|
||||
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner)) continue;
|
||||
hoverBodies.Add(physBody);
|
||||
}
|
||||
|
||||
var lineHeight = _font.GetLineHeight(1f);
|
||||
var drawPos = mousePos.Position + new Vector2(20, 0) + new Vector2(0, -(hoverBodies.Count * 4 * lineHeight / 2f));
|
||||
int row = 0;
|
||||
|
||||
foreach (var body in hoverBodies)
|
||||
{
|
||||
if (body != hoverBodies[0])
|
||||
{
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
|
||||
row++;
|
||||
}
|
||||
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {body.Owner}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
row++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (_physics.Flags == PhysicsDebugFlags.None) return;
|
||||
if (_debugPhysicsSystem.Flags == PhysicsDebugFlags.None) return;
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
switch (args.Space)
|
||||
{
|
||||
// Port DebugDrawing over.
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen((DrawingHandleScreen) args.DrawingHandle, args);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld((DrawingHandleWorld) args.DrawingHandle, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
private void DrawShape(DrawingHandleWorld worldHandle, Fixture fixture, Transform xform, Color color)
|
||||
{
|
||||
switch (fixture.Shape)
|
||||
{
|
||||
const float axisScale = 0.3f;
|
||||
case PhysShapeCircle circle:
|
||||
var center = Transform.Mul(xform, circle.Position);
|
||||
worldHandle.DrawCircle(center, circle.Radius, color);
|
||||
break;
|
||||
case EdgeShape edge:
|
||||
var v1 = Transform.Mul(xform, edge.Vertex1);
|
||||
var v2 = Transform.Mul(xform, edge.Vertex2);
|
||||
worldHandle.DrawLine(v1, v2, color);
|
||||
|
||||
for (int i = 0; i < _physics.PointCount; ++i)
|
||||
{
|
||||
DebugPhysicsSystem.ContactPoint point = _physics._points[i];
|
||||
|
||||
if (point.State == PointState.Add)
|
||||
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 243, 77));
|
||||
else if (point.State == PointState.Persist)
|
||||
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 77, 77));
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.ContactNormals) != 0)
|
||||
if (edge.OneSided)
|
||||
{
|
||||
Vector2 p1 = point.Position;
|
||||
Vector2 p2 = p1 + point.Normal * axisScale;
|
||||
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 102));
|
||||
worldHandle.DrawCircle(v1, 0.1f, color);
|
||||
worldHandle.DrawCircle(v2, 0.1f, color);
|
||||
}
|
||||
}
|
||||
|
||||
_physics.PointCount = 0;
|
||||
break;
|
||||
case PolygonShape poly:
|
||||
Span<Vector2> verts = stackalloc Vector2[poly.Vertices.Length];
|
||||
|
||||
for (var i = 0; i < verts.Length; i++)
|
||||
{
|
||||
verts[i] = Transform.Mul(xform, poly.Vertices[i]);
|
||||
}
|
||||
|
||||
worldHandle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawJoint(DrawingHandleWorld worldHandle, Joint joint)
|
||||
{
|
||||
if (!_entityManager.TryGetComponent(joint.BodyAUid, out TransformComponent? xform1) ||
|
||||
!_entityManager.TryGetComponent(joint.BodyBUid, out TransformComponent? xform2)) return;
|
||||
|
||||
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 p1 = matrix1.Transform(joint.LocalAnchorA);
|
||||
var p2 = matrix2.Transform(joint.LocalAnchorB);
|
||||
|
||||
var xfa = new Transform(xf1, xform1.WorldRotation);
|
||||
var xfb = new Transform(xf2, xform2.WorldRotation);
|
||||
|
||||
switch (joint)
|
||||
{
|
||||
case DistanceJoint:
|
||||
worldHandle.DrawLine(xf1, xf2, JointColor);
|
||||
break;
|
||||
case PrismaticJoint prisma:
|
||||
var pA = Transform.Mul(xfa, joint.LocalAnchorA);
|
||||
var pB = Transform.Mul(xfb, joint.LocalAnchorB);
|
||||
|
||||
var axis = Transform.Mul(xfa.Quaternion2D, prisma._localXAxisA);
|
||||
|
||||
Color c1 = new(0.7f, 0.7f, 0.7f);
|
||||
Color c2 = new(0.3f, 0.9f, 0.3f);
|
||||
Color c3 = new(0.9f, 0.3f, 0.3f);
|
||||
Color c4 = new(0.3f, 0.3f, 0.9f);
|
||||
Color c5 = new(0.4f, 0.4f, 0.4f);
|
||||
|
||||
worldHandle.DrawLine(pA, pB, c5);
|
||||
|
||||
if (prisma.EnableLimit)
|
||||
{
|
||||
var lower = pA + axis * prisma.LowerTranslation;
|
||||
var upper = pA + axis * prisma.UpperTranslation;
|
||||
var perp = Transform.Mul(xfa.Quaternion2D, prisma._localYAxisA);
|
||||
worldHandle.DrawLine(lower, upper, c1);
|
||||
worldHandle.DrawLine(lower - perp * 0.5f, lower + perp * 0.5f, c2);
|
||||
worldHandle.DrawLine(upper - perp * 0.5f, upper + perp * 0.5f, c3);
|
||||
}
|
||||
else
|
||||
{
|
||||
worldHandle.DrawLine(pA - axis * 1.0f, pA + axis * 1.0f, c1);
|
||||
}
|
||||
|
||||
worldHandle.DrawCircle(pA, 0.5f, c1);
|
||||
worldHandle.DrawCircle(pB, 0.5f, c4);
|
||||
break;
|
||||
default:
|
||||
worldHandle.DrawLine(xf1, p1, JointColor);
|
||||
worldHandle.DrawLine(p1, p2, JointColor);
|
||||
worldHandle.DrawLine(xf2, p2, JointColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,22 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
internal class DebugDrawingManager : IDebugDrawingManager
|
||||
internal sealed class DebugRayDrawingSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IClientNetManager _net = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTimer = default!;
|
||||
|
||||
private readonly List<RayWithLifetime> raysWithLifeTime = new();
|
||||
private readonly List<RayWithLifetime> _raysWithLifeTime = new();
|
||||
private bool _debugDrawRays;
|
||||
|
||||
private struct RayWithLifetime
|
||||
@@ -52,9 +53,30 @@ namespace Robust.Client.Debugging
|
||||
|
||||
public TimeSpan DebugRayLifetime { get; set; } = TimeSpan.FromSeconds(5);
|
||||
|
||||
public void Initialize()
|
||||
public override void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgRay>(HandleDrawRay);
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<MsgRay>(HandleDrawRay);
|
||||
// To catch anything that's client-only and not sent by the server.
|
||||
SubscribeLocalEvent<DebugDrawRayMessage>(OnDebugDrawRay);
|
||||
}
|
||||
|
||||
private void OnDebugDrawRay(DebugDrawRayMessage ev)
|
||||
{
|
||||
if (!_debugDrawRays)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var newRayWithLifetime = new RayWithLifetime
|
||||
{
|
||||
DidActuallyHit = ev.Data.Results != null,
|
||||
RayOrigin = ev.Data.Ray.Position,
|
||||
RayHit = ev.Data.Results?.HitPos ?? ev.Data.Ray.Direction * ev.Data.MaxLength + ev.Data.Ray.Position,
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
};
|
||||
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
}
|
||||
|
||||
private void HandleDrawRay(MsgRay msg)
|
||||
@@ -74,15 +96,15 @@ namespace Robust.Client.Debugging
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
};
|
||||
|
||||
raysWithLifeTime.Add(newRayWithLifetime);
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
}
|
||||
|
||||
private sealed class DebugDrawRayOverlay : Overlay
|
||||
{
|
||||
private readonly DebugDrawingManager _owner;
|
||||
private readonly DebugRayDrawingSystem _owner;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public DebugDrawRayOverlay(DebugDrawingManager owner)
|
||||
public DebugDrawRayOverlay(DebugRayDrawingSystem owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
@@ -90,7 +112,7 @@ namespace Robust.Client.Debugging
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var handle = args.WorldHandle;
|
||||
foreach (var ray in _owner.raysWithLifeTime)
|
||||
foreach (var ray in _owner._raysWithLifeTime)
|
||||
{
|
||||
handle.DrawLine(
|
||||
ray.RayOrigin,
|
||||
@@ -103,7 +125,7 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
_owner.raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
|
||||
_owner._raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,6 @@
|
||||
/// </summary>
|
||||
public interface IDebugDrawing
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of physics bodies for each entity on screen.
|
||||
/// </summary>
|
||||
bool DebugColliders { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
public interface IDebugDrawingManager
|
||||
{
|
||||
bool DebugDrawRays { get; set; }
|
||||
TimeSpan DebugRayLifetime { get; set; }
|
||||
void Initialize();
|
||||
}
|
||||
}
|
||||
52
Robust.Client/GameController/GameController.RobustModules.cs
Normal file
52
Robust.Client/GameController/GameController.RobustModules.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Robust.Client.WebViewHook;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
internal sealed partial class GameController
|
||||
{
|
||||
private void LoadOptionalRobustModules(GameController.DisplayMode mode)
|
||||
{
|
||||
// In the future, this manifest should be loaded somewhere else and used for more parts of init.
|
||||
// For now, this is fine.
|
||||
var manifest = LoadResourceManifest();
|
||||
|
||||
foreach (var module in manifest.Modules)
|
||||
{
|
||||
switch (module)
|
||||
{
|
||||
case "Robust.Client.WebView":
|
||||
LoadRobustWebView(mode);
|
||||
break;
|
||||
default:
|
||||
Logger.Error($"Unknown Robust module: {module}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadRobustWebView(GameController.DisplayMode mode)
|
||||
{
|
||||
Logger.Debug("Loading Robust.Client.WebView");
|
||||
|
||||
var assembly = LoadRobustModuleAssembly("Robust.Client.WebView");
|
||||
var attribute = assembly.GetCustomAttribute<WebViewManagerImplAttribute>()!;
|
||||
DebugTools.AssertNotNull(attribute);
|
||||
|
||||
var managerType = attribute.ImplementationType;
|
||||
_webViewHook = (IWebViewManagerHook)Activator.CreateInstance(managerType)!;
|
||||
_webViewHook.Initialize(mode);
|
||||
|
||||
Logger.Debug("Done initializing Robust.Client.WebView");
|
||||
}
|
||||
|
||||
private Assembly LoadRobustModuleAssembly(string assemblyName)
|
||||
{
|
||||
// TODO: Launcher distribution and all that stuff.
|
||||
return Assembly.Load(assemblyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Runtime;
|
||||
@@ -11,12 +10,12 @@ using Robust.Client.GameStates;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Client.WebViewHook;
|
||||
using Robust.LoaderApi;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
@@ -32,6 +31,7 @@ using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Client
|
||||
{
|
||||
@@ -59,6 +59,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IViewVariablesManagerInternal _viewVariablesManager = default!;
|
||||
[Dependency] private readonly IDiscordRichPresence _discord = default!;
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly IClydeAudioInternal _clydeAudio = default!;
|
||||
[Dependency] private readonly IFontManagerInternal _fontManager = default!;
|
||||
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
|
||||
[Dependency] private readonly IScriptClient _scriptClient = default!;
|
||||
@@ -67,6 +68,8 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IMidiManager _midiManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
|
||||
private IWebViewManagerHook? _webViewHook;
|
||||
|
||||
private CommandLineArgs? _commandLineArgs;
|
||||
|
||||
// Arguments for loader-load. Not used otherwise.
|
||||
@@ -84,10 +87,11 @@ namespace Robust.Client
|
||||
internal bool StartupContinue(DisplayMode displayMode)
|
||||
{
|
||||
_clyde.InitializePostWindowing();
|
||||
_clydeAudio.InitializePostWindowing();
|
||||
_clyde.SetWindowTitle(Options.DefaultWindowTitle);
|
||||
|
||||
_taskManager.Initialize();
|
||||
_fontManager.SetFontDpi((uint) _configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
_fontManager.SetFontDpi((uint)_configurationManager.GetCVar(CVars.DisplayFontDpi));
|
||||
|
||||
// Disable load context usage on content start.
|
||||
// This prevents Content.Client being loaded twice and things like csi blowing up because of it.
|
||||
@@ -107,6 +111,9 @@ namespace Robust.Client
|
||||
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
|
||||
// Load optional Robust modules.
|
||||
LoadOptionalRobustModules(displayMode);
|
||||
|
||||
// Call Init in game assemblies.
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
|
||||
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
|
||||
@@ -193,9 +200,44 @@ namespace Robust.Client
|
||||
_client.ConnectToServer(LaunchState.ConnectEndpoint);
|
||||
}
|
||||
|
||||
ProgramShared.RunExecCommands(_console, _commandLineArgs?.ExecCommands);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ResourceManifestData LoadResourceManifest()
|
||||
{
|
||||
// Parses /manifest.yml for game-specific settings that cannot be exclusively set up by content code.
|
||||
if (!_resourceCache.TryContentFileRead("/manifest.yml", out var stream))
|
||||
return new ResourceManifestData(Array.Empty<string>());
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
using (stream)
|
||||
{
|
||||
using var streamReader = new StreamReader(stream, EncodingHelpers.UTF8);
|
||||
yamlStream.Load(streamReader);
|
||||
}
|
||||
|
||||
if (yamlStream.Documents.Count != 1 || yamlStream.Documents[0].RootNode is not YamlMappingNode mapping)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Expected a single YAML document with root mapping for /manifest.yml");
|
||||
}
|
||||
|
||||
var modules = Array.Empty<string>();
|
||||
if (mapping.TryGetNode("modules", out var modulesMap))
|
||||
{
|
||||
var sequence = (YamlSequenceNode)modulesMap;
|
||||
modules = new string[sequence.Children.Count];
|
||||
for (var i = 0; i < modules.Length; i++)
|
||||
{
|
||||
modules[i] = sequence[i].AsString();
|
||||
}
|
||||
}
|
||||
|
||||
return new ResourceManifestData(modules);
|
||||
}
|
||||
|
||||
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
|
||||
{
|
||||
Options = options;
|
||||
@@ -217,8 +259,10 @@ namespace Robust.Client
|
||||
System.Console.WriteLine($"LogLevel {level} does not exist!");
|
||||
continue;
|
||||
}
|
||||
|
||||
logLevel = result;
|
||||
}
|
||||
|
||||
_logManager.GetSawmill(sawmill).Level = logLevel;
|
||||
}
|
||||
}
|
||||
@@ -259,7 +303,7 @@ namespace Robust.Client
|
||||
|
||||
{
|
||||
// Handle GameControllerOptions implicit CVar overrides.
|
||||
_configurationManager.OverrideConVars(new []
|
||||
_configurationManager.OverrideConVars(new[]
|
||||
{
|
||||
(CVars.DisplayWindowIconSet.Name, options.WindowIconSet.ToString()),
|
||||
(CVars.DisplaySplashLogo.Name, options.SplashLogo.ToString())
|
||||
@@ -271,9 +315,11 @@ namespace Robust.Client
|
||||
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
: Options.MountOptions;
|
||||
|
||||
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory, Options.AssemblyDirectory,
|
||||
ProgramShared.DoMounts(_resourceCache, mountOptions, Options.ContentBuildDirectory,
|
||||
Options.AssemblyDirectory,
|
||||
Options.LoadContentResources, _loaderArgs != null && !Options.ResourceMountDisabled, ContentStart);
|
||||
|
||||
if (_loaderArgs != null)
|
||||
@@ -394,6 +440,8 @@ namespace Robust.Client
|
||||
|
||||
private void Update(FrameEventArgs frameEventArgs)
|
||||
{
|
||||
_webViewHook?.Update();
|
||||
_clydeAudio.FrameProcess(frameEventArgs);
|
||||
_clyde.FrameProcess(frameEventArgs);
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePreEngine, frameEventArgs);
|
||||
_stateManager.FrameUpdate(frameEventArgs);
|
||||
@@ -442,7 +490,7 @@ namespace Robust.Client
|
||||
var uh = logManager.GetSawmill("unhandled");
|
||||
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
|
||||
{
|
||||
var message = ((Exception) args.ExceptionObject).ToString();
|
||||
var message = ((Exception)args.ExceptionObject).ToString();
|
||||
uh.Log(args.IsTerminating ? LogLevel.Fatal : LogLevel.Error, message);
|
||||
};
|
||||
|
||||
@@ -485,11 +533,16 @@ namespace Robust.Client
|
||||
{
|
||||
_modLoader.Shutdown();
|
||||
|
||||
_webViewHook?.Shutdown();
|
||||
|
||||
_networkManager.Shutdown("Client shutting down");
|
||||
_midiManager.Shutdown();
|
||||
IoCManager.Resolve<IEntityLookup>().Shutdown();
|
||||
_entityManager.Shutdown();
|
||||
_clyde.Shutdown();
|
||||
_clydeAudio.Shutdown();
|
||||
}
|
||||
|
||||
private sealed record ResourceManifestData(string[] Modules);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace Robust.Client.GameObjects
|
||||
RegisterClass<ClientOccluderComponent>();
|
||||
RegisterClass<OccluderTreeComponent>();
|
||||
RegisterClass<EyeComponent>();
|
||||
RegisterClass<AppearanceComponent>();
|
||||
RegisterClass<AnimationPlayerComponent>();
|
||||
RegisterClass<TimerComponent>();
|
||||
|
||||
|
||||
@@ -33,19 +33,19 @@ namespace Robust.Client.GameObjects
|
||||
base.Initialize();
|
||||
}
|
||||
|
||||
IEntity IClientEntityManagerInternal.CreateEntity(string? prototypeName, EntityUid? uid)
|
||||
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, EntityUid uid)
|
||||
{
|
||||
return base.CreateEntity(prototypeName, uid);
|
||||
}
|
||||
|
||||
void IClientEntityManagerInternal.InitializeEntity(IEntity entity)
|
||||
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity)
|
||||
{
|
||||
EntityManager.InitializeEntity((Entity)entity);
|
||||
base.InitializeEntity(entity);
|
||||
}
|
||||
|
||||
void IClientEntityManagerInternal.StartEntity(IEntity entity)
|
||||
void IClientEntityManagerInternal.StartEntity(EntityUid entity)
|
||||
{
|
||||
base.StartEntity((Entity)entity);
|
||||
base.StartEntity(entity);
|
||||
}
|
||||
|
||||
#region IEntityNetworkManager impl
|
||||
@@ -107,7 +107,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
/// <inheritdoc />
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendComponentNetworkMessage(INetChannel? channel, IEntity entity, IComponent component, ComponentMessage message)
|
||||
public void SendComponentNetworkMessage(INetChannel? channel, EntityUid entity, IComponent component, ComponentMessage message)
|
||||
{
|
||||
var netId = ComponentFactory.GetRegistration(component.GetType()).NetID;
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
msg.Type = EntityMessageType.ComponentMessage;
|
||||
msg.EntityUid = entity.Uid;
|
||||
msg.EntityUid = entity;
|
||||
msg.NetId = netId.Value;
|
||||
msg.ComponentMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
@@ -52,7 +52,9 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
internal void AnimationComplete(string key)
|
||||
{
|
||||
#pragma warning disable 618
|
||||
AnimationCompleted?.Invoke(key);
|
||||
#pragma warning restore 618
|
||||
}
|
||||
|
||||
[Obsolete("Use AnimationCompletedEvent instead")]
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ComponentReference(typeof(SharedAppearanceComponent))]
|
||||
public sealed class AppearanceComponent : SharedAppearanceComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
private Dictionary<object, object> data = new();
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("visuals")]
|
||||
internal List<AppearanceVisualizer> Visualizers = new();
|
||||
|
||||
[ViewVariables]
|
||||
private bool _appearanceDirty;
|
||||
|
||||
public override void SetData(string key, object value)
|
||||
{
|
||||
SetData(key, value);
|
||||
}
|
||||
|
||||
public override void SetData(Enum key, object value)
|
||||
{
|
||||
SetData(key, value);
|
||||
}
|
||||
|
||||
public override T GetData<T>(string key)
|
||||
{
|
||||
return (T) data[key];
|
||||
}
|
||||
|
||||
public override T GetData<T>(Enum key)
|
||||
{
|
||||
return (T) data[key];
|
||||
}
|
||||
|
||||
internal T GetData<T>(object key)
|
||||
{
|
||||
return (T) data[key];
|
||||
}
|
||||
|
||||
public override bool TryGetData<T>(Enum key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
return TryGetData(key, out data);
|
||||
}
|
||||
|
||||
public override bool TryGetData<T>(string key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
return TryGetData(key, out data);
|
||||
}
|
||||
|
||||
internal bool TryGetData<T>(object key, [NotNullWhen(true)] out T data)
|
||||
{
|
||||
if (this.data.TryGetValue(key, out var dat))
|
||||
{
|
||||
data = (T) dat;
|
||||
return true;
|
||||
}
|
||||
|
||||
data = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void SetData(object key, object value)
|
||||
{
|
||||
if (data.TryGetValue(key, out var existing) && existing.Equals(value)) return;
|
||||
|
||||
data[key] = value;
|
||||
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
if (curState is not AppearanceComponentState actualState)
|
||||
return;
|
||||
|
||||
var stateDiff = data.Count != actualState.Data.Count;
|
||||
|
||||
if (!stateDiff)
|
||||
{
|
||||
foreach (var (key, value) in data)
|
||||
{
|
||||
if (!actualState.Data.TryGetValue(key, out var stateValue) ||
|
||||
!value.Equals(stateValue))
|
||||
{
|
||||
stateDiff = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!stateDiff) return;
|
||||
|
||||
data = actualState.Data;
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
internal void MarkDirty()
|
||||
{
|
||||
if (_appearanceDirty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<AppearanceSystem>().EnqueueUpdate(this);
|
||||
_appearanceDirty = true;
|
||||
}
|
||||
|
||||
internal void UnmarkDirty()
|
||||
{
|
||||
_appearanceDirty = false;
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
foreach (var visual in Visualizers)
|
||||
{
|
||||
visual.InitializeEntity(Owner);
|
||||
}
|
||||
|
||||
MarkDirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the visualization of data inside of an appearance component.
|
||||
/// Implementations of this class are NOT bound to a specific entity, they are flyweighted across multiple.
|
||||
/// </summary>
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public abstract class AppearanceVisualizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes an entity to be managed by this appearance controller.
|
||||
/// DO NOT assume this is your only entity. Visualizers are shared.
|
||||
/// </summary>
|
||||
public virtual void InitializeEntity(IEntity entity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever appearance data for an entity changes.
|
||||
/// Update its visuals here.
|
||||
/// </summary>
|
||||
/// <param name="component">The appearance component of the entity that might need updating.</param>
|
||||
public virtual void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the visualization of data inside of an appearance component.
|
||||
/// Implementations of this class are NOT bound to a specific entity, they are flyweighted across multiple.
|
||||
/// </summary>
|
||||
[ImplicitDataDefinitionForInheritors]
|
||||
public abstract class AppearanceVisualizer
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes an entity to be managed by this appearance controller.
|
||||
/// DO NOT assume this is your only entity. Visualizers are shared.
|
||||
/// </summary>
|
||||
public virtual void InitializeEntity(EntityUid entity)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called whenever appearance data for an entity changes.
|
||||
/// Update its visuals here.
|
||||
/// </summary>
|
||||
/// <param name="component">The appearance component of the entity that might need updating.</param>
|
||||
public virtual void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// This is the client instance of <see cref="AppearanceComponent"/>.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(AppearanceComponent))]
|
||||
public sealed class ClientAppearanceComponent : AppearanceComponent
|
||||
{
|
||||
[ViewVariables]
|
||||
private bool _appearanceDirty;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("visuals")]
|
||||
internal List<AppearanceVisualizer> Visualizers = new();
|
||||
|
||||
protected override void MarkDirty()
|
||||
{
|
||||
if (_appearanceDirty)
|
||||
return;
|
||||
|
||||
EntitySystem.Get<AppearanceSystem>().EnqueueUpdate(this);
|
||||
_appearanceDirty = true;
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
foreach (var visual in Visualizers)
|
||||
{
|
||||
visual.InitializeEntity(Owner);
|
||||
}
|
||||
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
internal void UnmarkDirty()
|
||||
{
|
||||
_appearanceDirty = false;
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -14,6 +12,7 @@ namespace Robust.Client.GameObjects
|
||||
public class EyeComponent : SharedEyeComponent
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Eye";
|
||||
@@ -27,7 +26,6 @@ namespace Robust.Client.GameObjects
|
||||
private bool _setDrawFovOnInitialize = true;
|
||||
[DataField("zoom")]
|
||||
private Vector2 _setZoomOnInitialize = Vector2.One;
|
||||
private Vector2 _offset = Vector2.Zero;
|
||||
|
||||
public IEye? Eye => _eye;
|
||||
|
||||
@@ -85,14 +83,11 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public override Vector2 Offset
|
||||
{
|
||||
get => _offset;
|
||||
get => _eye?.Offset ?? default;
|
||||
set
|
||||
{
|
||||
if(_offset.EqualsApprox(value))
|
||||
return;
|
||||
|
||||
_offset = value;
|
||||
UpdateEyePosition();
|
||||
if (_eye != null)
|
||||
_eye.Offset = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +117,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
_eye = new Eye
|
||||
{
|
||||
Position = Owner.Transform.MapPosition,
|
||||
Position = _entityManager.GetComponent<TransformComponent>(Owner).MapPosition,
|
||||
Zoom = _setZoomOnInitialize,
|
||||
DrawFov = _setDrawFovOnInitialize
|
||||
};
|
||||
@@ -170,8 +165,8 @@ namespace Robust.Client.GameObjects
|
||||
public void UpdateEyePosition()
|
||||
{
|
||||
if (_eye == null) return;
|
||||
var mapPos = Owner.Transform.MapPosition;
|
||||
_eye.Position = new MapCoordinates(mapPos.Position + _offset, mapPos.MapId);
|
||||
var mapPos = _entityManager.GetComponent<TransformComponent>(Owner).MapPosition;
|
||||
_eye.Position = new MapCoordinates(mapPos.Position, mapPos.MapId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
@@ -12,6 +11,7 @@ namespace Robust.Client.GameObjects
|
||||
internal sealed class ClientOccluderComponent : OccluderComponent
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
[ViewVariables] private (GridId, Vector2i) _lastPosition;
|
||||
[ViewVariables] internal OccluderDir Occluding { get; private set; }
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
if (Owner.Transform.Anchored)
|
||||
if (_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
|
||||
{
|
||||
AnchorStateChanged();
|
||||
}
|
||||
@@ -42,11 +42,11 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
SendDirty();
|
||||
|
||||
if(!Owner.Transform.Anchored)
|
||||
if(!_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
|
||||
return;
|
||||
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
_lastPosition = (Owner.Transform.GridID, grid.TileIndicesFor(Owner.Transform.Coordinates));
|
||||
var grid = _mapManager.GetGrid(_entityManager.GetComponent<TransformComponent>(Owner).GridID);
|
||||
_lastPosition = (_entityManager.GetComponent<TransformComponent>(Owner).GridID, grid.TileIndicesFor(_entityManager.GetComponent<TransformComponent>(Owner).Coordinates));
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
@@ -58,9 +58,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void SendDirty()
|
||||
{
|
||||
if (Owner.Transform.Anchored)
|
||||
if (_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new OccluderDirtyEvent(Owner, _lastPosition));
|
||||
}
|
||||
}
|
||||
@@ -69,18 +69,18 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Occluding = OccluderDir.None;
|
||||
|
||||
if (Deleted || !Owner.Transform.Anchored)
|
||||
if (Deleted || !_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var grid = _mapManager.GetGrid(_entityManager.GetComponent<TransformComponent>(Owner).GridID);
|
||||
var position = _entityManager.GetComponent<TransformComponent>(Owner).Coordinates;
|
||||
void CheckDir(Direction dir, OccluderDir oclDir)
|
||||
{
|
||||
var grid = _mapManager.GetGrid(Owner.Transform.GridID);
|
||||
var position = Owner.Transform.Coordinates;
|
||||
foreach (var neighbor in grid.GetInDir(position, dir))
|
||||
{
|
||||
if (Owner.EntityManager.TryGetComponent(neighbor, out ClientOccluderComponent? comp) && comp.Enabled)
|
||||
if (_entityManager.TryGetComponent(neighbor, out ClientOccluderComponent? comp) && comp.Enabled)
|
||||
{
|
||||
Occluding |= oclDir;
|
||||
break;
|
||||
@@ -88,10 +88,20 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
CheckDir(Direction.North, OccluderDir.North);
|
||||
CheckDir(Direction.East, OccluderDir.East);
|
||||
CheckDir(Direction.South, OccluderDir.South);
|
||||
CheckDir(Direction.West, OccluderDir.West);
|
||||
var angle = _entityManager.GetComponent<TransformComponent>(Owner).LocalRotation;
|
||||
var dirRolling = angle.GetCardinalDir();
|
||||
// dirRolling starts at effective south
|
||||
|
||||
CheckDir(dirRolling, OccluderDir.South);
|
||||
dirRolling = dirRolling.GetClockwise90Degrees();
|
||||
|
||||
CheckDir(dirRolling, OccluderDir.West);
|
||||
dirRolling = dirRolling.GetClockwise90Degrees();
|
||||
|
||||
CheckDir(dirRolling, OccluderDir.North);
|
||||
dirRolling = dirRolling.GetClockwise90Degrees();
|
||||
|
||||
CheckDir(dirRolling, OccluderDir.East);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -12,6 +13,8 @@ namespace Robust.Client.GameObjects
|
||||
[ComponentReference(typeof(SharedPointLightComponent))]
|
||||
public class PointLightComponent : SharedPointLightComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
internal bool TreeUpdateQueued { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
@@ -31,7 +34,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
base.Enabled = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
|
||||
_entityManager.EventBus.RaiseLocalEvent(Owner, new PointLightUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +47,7 @@ namespace Robust.Client.GameObjects
|
||||
if (_containerOccluded == value) return;
|
||||
|
||||
_containerOccluded = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new PointLightUpdateEvent());
|
||||
_entityManager.EventBus.RaiseLocalEvent(Owner, new PointLightUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +123,12 @@ namespace Robust.Client.GameObjects
|
||||
set => _visibleNested = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this pointlight should cast shadows
|
||||
/// </summary>
|
||||
[DataField("castShadows")]
|
||||
public bool CastShadows = true;
|
||||
|
||||
[DataField("nestedvisible")]
|
||||
private bool _visibleNested = true;
|
||||
[DataField("autoRot")]
|
||||
@@ -145,7 +154,7 @@ namespace Robust.Client.GameObjects
|
||||
if (MathHelper.CloseToPercent(value, _radius)) return;
|
||||
|
||||
base.Radius = value;
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
|
||||
_entityManager.EventBus.RaiseEvent(EventSource.Local, new PointLightRadiusChangedEvent(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,15 +43,6 @@ namespace Robust.Client.GameObjects
|
||||
[Animatable]
|
||||
Color Color { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether we use RSI directions to rotate, or just get angular rotation applied.
|
||||
/// If true, all rotation to this sprite component is negated (that is rotation from say the owner being rotated).
|
||||
/// Rotation transformations on individual layers still apply.
|
||||
/// If false, all layers get locked to south and rotation is a transformation.
|
||||
/// </summary>
|
||||
[Obsolete("Use NoRotation and/or DirectionOverride")]
|
||||
bool Directional { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All sprite rotation is locked, and will always be drawn upright on
|
||||
/// the screen, regardless of world or view orientation.
|
||||
|
||||
@@ -23,6 +23,11 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public sealed class SpriteBoundsSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eye = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly RenderingTreeSystem _renderingTree = default!;
|
||||
|
||||
private SpriteBoundsOverlay? _overlay;
|
||||
|
||||
public bool Enabled
|
||||
@@ -37,15 +42,13 @@ namespace Robust.Client.GameObjects
|
||||
if (_enabled)
|
||||
{
|
||||
DebugTools.AssertNull(_overlay);
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
_overlay = new SpriteBoundsOverlay(EntitySystem.Get<RenderingTreeSystem>(), IoCManager.Resolve<IEyeManager>());
|
||||
overlayManager.AddOverlay(_overlay);
|
||||
_overlay = new SpriteBoundsOverlay(_renderingTree, _eye, _entityManager);
|
||||
_overlayManager.AddOverlay(_overlay);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_overlay == null) return;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
overlayManager.RemoveOverlay(_overlay);
|
||||
_overlayManager.RemoveOverlay(_overlay);
|
||||
_overlay = null;
|
||||
}
|
||||
}
|
||||
@@ -58,13 +61,15 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private readonly IEyeManager _eyeManager = default!;
|
||||
private readonly IEyeManager _eyeManager;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private RenderingTreeSystem _renderTree;
|
||||
|
||||
public SpriteBoundsOverlay(RenderingTreeSystem renderTree, IEyeManager eyeManager)
|
||||
public SpriteBoundsOverlay(RenderingTreeSystem renderTree, IEyeManager eyeManager, IEntityManager entityManager)
|
||||
{
|
||||
_renderTree = renderTree;
|
||||
_eyeManager = eyeManager;
|
||||
_entityManager = entityManager;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
@@ -75,11 +80,11 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var comp in _renderTree.GetRenderTrees(currentMap, viewport))
|
||||
{
|
||||
var localAABB = comp.Owner.Transform.InvWorldMatrix.TransformBox(viewport);
|
||||
var localAABB = _entityManager.GetComponent<TransformComponent>(comp.Owner).InvWorldMatrix.TransformBox(viewport);
|
||||
|
||||
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
|
||||
{
|
||||
var worldPos = sprite.Owner.Transform.WorldPosition;
|
||||
var worldPos = _entityManager.GetComponent<TransformComponent>(sprite.Owner).WorldPosition;
|
||||
var bounds = sprite.CalculateBoundingBox(worldPos);
|
||||
handle.DrawRect(bounds, Color.Red.WithAlpha(0.2f));
|
||||
handle.DrawRect(bounds.Scale(0.2f).Translated(-new Vector2(0f, bounds.Extents.Y)), Color.Blue.WithAlpha(0.5f));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -13,14 +12,11 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
@@ -34,6 +30,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IResourceCache resourceCache = default!;
|
||||
[Dependency] private readonly IPrototypeManager prototypes = default!;
|
||||
[Dependency] private readonly IEntityManager entities = default!;
|
||||
|
||||
[DataField("visible")]
|
||||
private bool _visible = true;
|
||||
@@ -47,7 +44,7 @@ namespace Robust.Client.GameObjects
|
||||
if (_visible == value) return;
|
||||
_visible = value;
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
entities.EventBus.RaiseLocalEvent(Owner, new SpriteUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,23 +111,6 @@ namespace Robust.Client.GameObjects
|
||||
set => color = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether we use RSI directions to rotate, or just get angular rotation applied.
|
||||
/// If true, all rotation to this sprite component is negated (that is rotation from say the owner being rotated).
|
||||
/// Rotation transformations on individual layers still apply.
|
||||
/// If false, all layers get locked to south and rotation is a transformation.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Obsolete("Use NoRotation and/or DirectionOverride")]
|
||||
public bool Directional
|
||||
{
|
||||
get => _directional;
|
||||
set => _directional = value;
|
||||
}
|
||||
|
||||
[DataField("directional")]
|
||||
private bool _directional = true;
|
||||
|
||||
[ViewVariables]
|
||||
internal RenderingTreeComponent? RenderTree { get; set; } = null;
|
||||
|
||||
@@ -230,6 +210,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
layer.Color = layerDatum.Color;
|
||||
layer.Rotation = layerDatum.Rotation;
|
||||
layer._offset = layerDatum.Offset;
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = anyTextureAttempted && layerDatum.Visible;
|
||||
layer.Scale = layerDatum.Scale;
|
||||
@@ -319,7 +300,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
if (_containerOccluded == value) return;
|
||||
_containerOccluded = value;
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
entities.EventBus.RaiseLocalEvent(Owner, new SpriteUpdateEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +370,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
if (layerDatums.Count != 0)
|
||||
{
|
||||
LayerMap.Clear();
|
||||
LayerDatums = layerDatums;
|
||||
}
|
||||
}
|
||||
@@ -402,7 +384,6 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
//deep copying things to avoid entanglement
|
||||
_baseRsi = other._baseRsi;
|
||||
_directional = other._directional;
|
||||
_visible = other._visible;
|
||||
_layerMapShared = other._layerMapShared;
|
||||
color = other.color;
|
||||
@@ -564,7 +545,7 @@ namespace Robust.Client.GameObjects
|
||||
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, Environment.StackTrace);
|
||||
}
|
||||
|
||||
return AddLayer(stateId, res?.RSI);
|
||||
return AddLayer(stateId, res?.RSI, newIndex);
|
||||
}
|
||||
|
||||
public int AddLayerState(string stateId, ResourcePath rsiPath, int? newIndex = null)
|
||||
@@ -1270,7 +1251,13 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
|
||||
{
|
||||
var localMatrix = GetLocalMatrix();
|
||||
// Reduce the angles to fix math shenanigans
|
||||
worldRotation = worldRotation.Reduced();
|
||||
|
||||
if (worldRotation.Theta < 0)
|
||||
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
|
||||
|
||||
var spriteMatrix = GetLocalMatrix();
|
||||
|
||||
foreach (var layer in Layers)
|
||||
{
|
||||
@@ -1280,12 +1267,14 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
var numDirs = GetLayerDirectionCount(layer);
|
||||
var layerRotation = worldRotation + layer.Rotation;
|
||||
var layerPosition = worldPosition + layerRotation.RotateVec(layer._offset);
|
||||
|
||||
CalcModelMatrix(numDirs, eyeRotation, worldRotation, worldPosition, out var modelMatrix);
|
||||
Matrix3.Multiply(ref localMatrix, ref modelMatrix, out var transformMatrix);
|
||||
CalcModelMatrix(numDirs, eyeRotation, layerRotation, layerPosition, out var modelMatrix);
|
||||
Matrix3.Multiply(ref spriteMatrix, ref modelMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
RenderLayer(drawingHandle, layer, eyeRotation, worldRotation, overrideDirection);
|
||||
RenderLayer(drawingHandle, layer, eyeRotation, layerRotation, overrideDirection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1300,7 +1289,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var layerColor = color * layer.Color;
|
||||
|
||||
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
|
||||
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter);
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(position, textureSize);
|
||||
|
||||
@@ -1321,7 +1310,7 @@ namespace Robust.Client.GameObjects
|
||||
public static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
|
||||
{
|
||||
var theta = worldAngle.Theta;
|
||||
var segSize = (MathF.PI * 2) / (numDirections * 2);
|
||||
var segSize = (Math.PI * 2) / (numDirections * 2);
|
||||
var segments = (int)(theta / segSize);
|
||||
var odd = segments % 2;
|
||||
var result = theta - (segments * segSize) - (odd * segSize);
|
||||
@@ -1513,7 +1502,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
// Look this was an easy way to get bounds checks for layer updates.
|
||||
// If you really want it optimal you'll need to comb through all 2k lines of spritecomponent.
|
||||
if (Owner?.EntityManager?.EventBus != null)
|
||||
if ((Owner != default ? entities : null)?.EventBus != null)
|
||||
UpdateBounds();
|
||||
|
||||
if (_inertUpdateQueued)
|
||||
@@ -1522,7 +1511,7 @@ namespace Robust.Client.GameObjects
|
||||
_inertUpdateQueued = true;
|
||||
// Yes that null check is valid because of that stupid fucking dummy IEntity.
|
||||
// Who thought that was a good idea.
|
||||
Owner?.EntityManager?.EventBus?.RaiseEvent(EventSource.Local, new SpriteUpdateInertEvent {Sprite = this});
|
||||
(Owner != default ? entities : null)?.EventBus?.RaiseEvent(EventSource.Local, new SpriteUpdateInertEvent {Sprite = this});
|
||||
}
|
||||
|
||||
internal void DoUpdateIsInert()
|
||||
@@ -1609,7 +1598,7 @@ namespace Robust.Client.GameObjects
|
||||
builder.AppendFormat(
|
||||
"vis/depth/scl/rot/ofs/col/norot/override/dir: {0}/{1}/{2}/{3}/{4}/{5}/{6}/{8}/{7}\n",
|
||||
Visible, DrawDepth, Scale, Rotation, Offset,
|
||||
Color, NoRotation, GetDir(RSI.State.DirectionType.Dir8, Owner.Transform.WorldRotation),
|
||||
Color, NoRotation, GetDir(RSI.State.DirectionType.Dir8, entities.GetComponent<TransformComponent>(Owner).WorldRotation),
|
||||
DirectionOverride
|
||||
);
|
||||
|
||||
@@ -1663,7 +1652,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
internal void UpdateBounds()
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new SpriteUpdateEvent());
|
||||
entities.EventBus.RaiseLocalEvent(Owner, new SpriteUpdateEvent());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1733,7 +1722,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 _offset;
|
||||
internal Vector2 _offset;
|
||||
|
||||
[ViewVariables]
|
||||
public DirectionOffset DirOffset { get; set; }
|
||||
@@ -2061,36 +2050,35 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
return GetPrototypeTextures(prototype, resourceCache, out var _);
|
||||
}
|
||||
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot)
|
||||
{
|
||||
var results = new List<IDirectionalTextureProvider>();
|
||||
noRot = false;
|
||||
var icon = IconComponent.GetPrototypeIcon(prototype, resourceCache);
|
||||
if (icon != null)
|
||||
{
|
||||
yield return icon;
|
||||
yield break;
|
||||
results.Add(icon);
|
||||
return results;
|
||||
}
|
||||
|
||||
if (!prototype.Components.TryGetValue("Sprite", out _))
|
||||
{
|
||||
yield return resourceCache.GetFallback<TextureResource>().Texture;
|
||||
yield break;
|
||||
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
|
||||
return results;
|
||||
}
|
||||
|
||||
var dummy = new DummyIconEntity { Prototype = prototype };
|
||||
var spriteComponent = dummy.AddComponent<SpriteComponent>();
|
||||
|
||||
if (prototype.Components.TryGetValue("Appearance", out _))
|
||||
{
|
||||
var appearanceComponent = dummy.AddComponent<AppearanceComponent>();
|
||||
foreach (var layer in appearanceComponent.Visualizers)
|
||||
{
|
||||
layer.InitializeEntity(dummy);
|
||||
layer.OnChangeData(appearanceComponent);
|
||||
}
|
||||
}
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
|
||||
|
||||
var anyTexture = false;
|
||||
foreach (var layer in spriteComponent.AllLayers)
|
||||
{
|
||||
if (layer.Texture != null) yield return layer.Texture;
|
||||
if (layer.Texture != null)
|
||||
results.Add(layer.Texture);
|
||||
if (!layer.RsiState.IsValid || !layer.Visible) continue;
|
||||
|
||||
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
|
||||
@@ -2098,14 +2086,17 @@ namespace Robust.Client.GameObjects
|
||||
!rsi.TryGetState(layer.RsiState, out var state))
|
||||
continue;
|
||||
|
||||
yield return state;
|
||||
results.Add(state);
|
||||
anyTexture = true;
|
||||
}
|
||||
|
||||
dummy.Delete();
|
||||
noRot = spriteComponent.NoRotation;
|
||||
|
||||
entityManager.DeleteEntity(dummy);
|
||||
|
||||
if (!anyTexture)
|
||||
yield return resourceCache.GetFallback<TextureResource>().Texture;
|
||||
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
|
||||
return results;
|
||||
}
|
||||
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
@@ -2118,144 +2109,14 @@ namespace Robust.Client.GameObjects
|
||||
return GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
var dummy = new DummyIconEntity { Prototype = prototype };
|
||||
var spriteComponent = dummy.AddComponent<SpriteComponent>();
|
||||
dummy.Delete();
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
|
||||
var result = spriteComponent.Icon ?? GetFallbackState(resourceCache);
|
||||
entityManager.DeleteEntity(dummy);
|
||||
|
||||
return spriteComponent.Icon ?? GetFallbackState(resourceCache);
|
||||
return result;
|
||||
}
|
||||
|
||||
#region DummyIconEntity
|
||||
private class DummyIconEntity : IEntity
|
||||
{
|
||||
public GameTick LastModifiedTick { get; } = GameTick.Zero;
|
||||
public IEntityManager EntityManager { get; } = null!;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public EntityUid Uid { get; } = EntityUid.Invalid;
|
||||
EntityLifeStage IEntity.LifeStage { get => _lifeStage; set => _lifeStage = value; }
|
||||
public bool Initialized { get; } = false;
|
||||
public bool Initializing { get; } = false;
|
||||
public bool Deleted { get; } = true;
|
||||
public bool Paused { get; set; }
|
||||
public EntityPrototype? Prototype { get; set; }
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public bool IsValid()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public ITransformComponent Transform { get; } = null!;
|
||||
public IMetaDataComponent MetaData { get; } = null!;
|
||||
|
||||
private Dictionary<Type, IComponent> _components = new();
|
||||
private EntityLifeStage _lifeStage;
|
||||
|
||||
public T AddComponent<T>() where T : Component, new()
|
||||
{
|
||||
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
|
||||
var serializationManager = IoCManager.Resolve<ISerializationManager>();
|
||||
var comp = (T)typeFactory.CreateInstanceUnchecked(typeof(T));
|
||||
_components[typeof(T)] = comp;
|
||||
comp.Owner = this;
|
||||
|
||||
if (typeof(ISpriteComponent).IsAssignableFrom(typeof(T)))
|
||||
{
|
||||
_components[typeof(ISpriteComponent)] = comp;
|
||||
}
|
||||
|
||||
if (Prototype != null && Prototype.TryGetComponent<T>(comp.Name, out var node))
|
||||
{
|
||||
comp = serializationManager.Copy(node, comp)!;
|
||||
}
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
public void RemoveComponent<T>()
|
||||
{
|
||||
_components.Remove(typeof(T));
|
||||
}
|
||||
|
||||
public bool HasComponent<T>()
|
||||
{
|
||||
return _components.ContainsKey(typeof(T));
|
||||
}
|
||||
|
||||
public bool HasComponent(Type type)
|
||||
{
|
||||
return _components.ContainsKey(type);
|
||||
}
|
||||
|
||||
public T GetComponent<T>()
|
||||
{
|
||||
return (T)_components[typeof(T)];
|
||||
}
|
||||
|
||||
public IComponent GetComponent(Type type)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component) where T : class
|
||||
{
|
||||
component = null;
|
||||
if (!_components.TryGetValue(typeof(T), out var value)) return false;
|
||||
component = (T)value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public T? GetComponentOrNull<T>() where T : class
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryGetComponent(Type type, [NotNullWhen(true)] out IComponent? component)
|
||||
{
|
||||
component = null;
|
||||
if (!_components.TryGetValue(type, out var value)) return false;
|
||||
component = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public IComponent? GetComponentOrNull(Type type)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void QueueDelete()
|
||||
{
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerable<IComponent> GetAllComponents()
|
||||
{
|
||||
return Enumerable.Empty<IComponent>();
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetAllComponents<T>()
|
||||
{
|
||||
return Enumerable.Empty<T>();
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendMessage(IComponent? owner, ComponentMessage message)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("Component Messages are deprecated, use Entity Events instead.")]
|
||||
public void SendNetworkMessage(IComponent owner, ComponentMessage message, INetChannel? channel = null)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dirty()
|
||||
{
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal sealed class SpriteUpdateEvent : EntityEventArgs
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -9,55 +8,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public override string Name => "RenderingTree";
|
||||
|
||||
internal DynamicTree<SpriteComponent> SpriteTree { get; private set; } = new(SpriteAabbFunc);
|
||||
internal DynamicTree<PointLightComponent> LightTree { get; private set; } = new(LightAabbFunc);
|
||||
|
||||
private static Box2 SpriteAabbFunc(in SpriteComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
var worldRot = value.Owner.Transform.WorldRotation;
|
||||
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
var localAABB = tree?.Owner.Transform.InvWorldMatrix.TransformBox(bounds) ?? bounds.CalcBoundingBox();
|
||||
|
||||
return localAABB;
|
||||
}
|
||||
|
||||
private static Box2 LightAabbFunc(in PointLightComponent value)
|
||||
{
|
||||
var worldPos = value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
|
||||
var localPos = tree?.Owner.Transform.InvWorldMatrix.Transform(worldPos) ?? worldPos;
|
||||
|
||||
return Box2.CenteredAround(localPos, (boxSize, boxSize));
|
||||
}
|
||||
|
||||
internal static Box2 SpriteAabbFunc(SpriteComponent value, Vector2? worldPos = null, Angle? worldRot = null)
|
||||
{
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
worldRot ??= value.Owner.Transform.WorldRotation;
|
||||
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos.Value), worldRot.Value, worldPos.Value);
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
|
||||
var localAABB = tree?.Owner.Transform.InvWorldMatrix.TransformBox(bounds) ?? bounds.CalcBoundingBox();
|
||||
|
||||
return localAABB;
|
||||
}
|
||||
|
||||
internal static Box2 LightAabbFunc(PointLightComponent value, Vector2? worldPos = null)
|
||||
{
|
||||
// Lights are circles so don't need entity's rotation
|
||||
|
||||
worldPos ??= value.Owner.Transform.WorldPosition;
|
||||
var tree = RenderingTreeSystem.GetRenderTree(value.Owner);
|
||||
var boxSize = value.Radius * 2;
|
||||
|
||||
var localPos = tree?.Owner.Transform.InvWorldMatrix.Transform(worldPos.Value) ?? worldPos.Value;
|
||||
|
||||
return Box2.CenteredAround(localPos, (boxSize, boxSize));
|
||||
}
|
||||
internal DynamicTree<SpriteComponent> SpriteTree { get; set; } = default!;
|
||||
internal DynamicTree<PointLightComponent> LightTree { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private readonly Dictionary<object, BoundUserInterface> _openInterfaces =
|
||||
new();
|
||||
@@ -87,15 +89,15 @@ namespace Robust.Client.GameObjects
|
||||
_openInterfaces.Remove(uiKey);
|
||||
boundUserInterface.Dispose();
|
||||
|
||||
var playerSession = IoCManager.Resolve<IPlayerManager>().LocalPlayer?.Session;
|
||||
var playerSession = _playerManager.LocalPlayer?.Session;
|
||||
if(playerSession != null)
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new BoundUIClosedEvent(uiKey, Owner.Uid, playerSession));
|
||||
_entityManager.EventBus.RaiseLocalEvent(Owner, new BoundUIClosedEvent(uiKey, Owner, playerSession));
|
||||
}
|
||||
|
||||
internal void SendMessage(BoundUserInterfaceMessage message, object uiKey)
|
||||
{
|
||||
EntitySystem.Get<UserInterfaceSystem>()
|
||||
.Send(new BoundUIWrapMessage(Owner.Uid, message, uiKey));
|
||||
.Send(new BoundUIWrapMessage(Owner, message, uiKey));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Robust.Client.GameObjects
|
||||
foreach (var key in remie)
|
||||
{
|
||||
component.PlayingAnimations.Remove(key);
|
||||
EntityManager.EventBus.RaiseLocalEvent(component.Owner.Uid, new AnimationCompletedEvent {Uid = component.Owner.Uid, Key = key});
|
||||
EntityManager.EventBus.RaiseLocalEvent(component.Owner, new AnimationCompletedEvent {Uid = component.Owner, Key = key});
|
||||
component.AnimationComplete(key);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public void Play(EntityUid uid, Animation animation, string key)
|
||||
{
|
||||
var component = EntityManager.EnsureComponent<AnimationPlayerComponent>(EntityManager.GetEntity(uid));
|
||||
var component = EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
|
||||
Play(component, animation, key);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -7,9 +7,9 @@ namespace Robust.Client.GameObjects
|
||||
[UsedImplicitly]
|
||||
internal sealed class AppearanceSystem : EntitySystem
|
||||
{
|
||||
private readonly Queue<AppearanceComponent> _queuedUpdates = new();
|
||||
private readonly Queue<ClientAppearanceComponent> _queuedUpdates = new();
|
||||
|
||||
public void EnqueueUpdate(AppearanceComponent component)
|
||||
public void EnqueueUpdate(ClientAppearanceComponent component)
|
||||
{
|
||||
_queuedUpdates.Enqueue(component);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -12,21 +11,20 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AudioSystem : EntitySystem, IAudioSystem
|
||||
public class AudioSystem : SharedAudioSystem, IAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IClydeAudio _clyde = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly SharedBroadphaseSystem _broadPhaseSystem = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
|
||||
@@ -65,7 +63,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.AudioParams);
|
||||
var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams);
|
||||
if (stream != null)
|
||||
{
|
||||
stream.NetIdentifier = ev.Identifier;
|
||||
@@ -83,9 +81,9 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void PlayAudioEntityHandler(PlayAudioEntityMessage ev)
|
||||
{
|
||||
var stream = EntityManager.TryGetEntity(ev.EntityUid, out var entity) ?
|
||||
(PlayingStream?) Play(ev.FileName, entity, ev.AudioParams)
|
||||
: (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.AudioParams);
|
||||
var stream = EntityManager.EntityExists(ev.EntityUid) ?
|
||||
(PlayingStream?) Play(ev.FileName, ev.EntityUid, ev.FallbackCoordinates, ev.AudioParams)
|
||||
: (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams);
|
||||
|
||||
if (stream != null)
|
||||
{
|
||||
@@ -124,17 +122,21 @@ namespace Robust.Client.GameObjects
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (stream.TrackingEntity != null)
|
||||
else if (stream.TrackingEntity != default)
|
||||
{
|
||||
if (stream.TrackingEntity.Deleted)
|
||||
if (EntityManager.Deleted(stream.TrackingEntity))
|
||||
{
|
||||
StreamDone(stream);
|
||||
continue;
|
||||
}
|
||||
|
||||
mapPos = stream.TrackingEntity.Transform.MapPosition;
|
||||
mapPos = EntityManager.GetComponent<TransformComponent>(stream.TrackingEntity).MapPosition;
|
||||
}
|
||||
|
||||
// TODO Remove when coordinates can't be NaN
|
||||
if (mapPos == null || !float.IsFinite(mapPos.Value.X) || !float.IsFinite(mapPos.Value.Y))
|
||||
mapPos = stream.TrackingFallbackCoordinates?.ToMap(_entityManager);
|
||||
|
||||
if (mapPos != null)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
@@ -208,11 +210,10 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.TrackingEntity != null)
|
||||
if (stream.TrackingEntity != default)
|
||||
{
|
||||
stream.Source.SetVelocity(stream.TrackingEntity.GlobalLinearVelocity());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,12 +278,14 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when entity is invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(string filename, IEntity entity, AudioParams? audioParams = null)
|
||||
private IPlayingAudioStream? Play(string filename, EntityUid entity, EntityCoordinates fallbackCoordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
|
||||
{
|
||||
return Play(audio, entity, audioParams);
|
||||
return Play(audio, entity, fallbackCoordinates, audioParams);
|
||||
}
|
||||
|
||||
Logger.Error($"Server tried to play audio file {filename} which does not exist.");
|
||||
@@ -294,15 +297,15 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when entity is invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(AudioStream stream, IEntity entity, AudioParams? audioParams = null)
|
||||
private IPlayingAudioStream? Play(AudioStream stream, EntityUid entity, EntityCoordinates fallbackCoordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
var source = _clyde.CreateAudioSource(stream);
|
||||
if (!source.SetPosition(entity.Transform.WorldPosition))
|
||||
if (!source.SetPosition(EntityManager.GetComponent<TransformComponent>(entity).WorldPosition))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
return Play(stream, fallbackCoordinates, fallbackCoordinates, audioParams);
|
||||
}
|
||||
|
||||
ApplyAudioParams(audioParams, source);
|
||||
@@ -312,6 +315,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Source = source,
|
||||
TrackingEntity = entity,
|
||||
TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null,
|
||||
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
|
||||
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
|
||||
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
|
||||
@@ -327,12 +331,14 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
/// <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="fallbackCoordinates">The map or grid coordinates at which to play the audio when coordinates are invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
private IPlayingAudioStream? Play(string filename, EntityCoordinates coordinates, EntityCoordinates fallbackCoordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (_resourceCache.TryGetResource<AudioResource>(new ResourcePath(filename), out var audio))
|
||||
{
|
||||
return Play(audio, coordinates, audioParams);
|
||||
return Play(audio, coordinates, fallbackCoordinates, audioParams);
|
||||
}
|
||||
|
||||
Logger.Error($"Server tried to play audio file {filename} which does not exist.");
|
||||
@@ -344,18 +350,24 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="fallbackCoordinates">The map or grid coordinates at which to play the audio when coordinates are invalid.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private IPlayingAudioStream? Play(AudioStream stream, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
EntityCoordinates fallbackCoordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
var source = _clyde.CreateAudioSource(stream);
|
||||
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
|
||||
if (!source.SetPosition(fallbackCoordinates.Position))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!coordinates.IsValid(_entityManager))
|
||||
{
|
||||
coordinates = fallbackCoordinates;
|
||||
}
|
||||
|
||||
ApplyAudioParams(audioParams, source);
|
||||
|
||||
source.StartPlaying();
|
||||
@@ -363,6 +375,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Source = source,
|
||||
TrackingCoordinates = coordinates,
|
||||
TrackingFallbackCoordinates = fallbackCoordinates != EntityCoordinates.Invalid ? fallbackCoordinates : null,
|
||||
Attenuation = audioParams?.Attenuation ?? Attenuation.Default,
|
||||
MaxDistance = audioParams?.MaxDistance ?? float.MaxValue,
|
||||
ReferenceDistance = audioParams?.ReferenceDistance ?? 1f,
|
||||
@@ -393,8 +406,9 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public uint? NetIdentifier;
|
||||
public IClydeAudioSource Source = default!;
|
||||
public IEntity TrackingEntity = default!;
|
||||
public EntityUid TrackingEntity = default!;
|
||||
public EntityCoordinates? TrackingCoordinates;
|
||||
public EntityCoordinates? TrackingFallbackCoordinates;
|
||||
public bool Done;
|
||||
public float Volume;
|
||||
|
||||
@@ -425,9 +439,6 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int DefaultSoundRange => 25;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
@@ -438,21 +449,15 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityUid entity, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, entity, audioParams);
|
||||
}
|
||||
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return EntityManager.TryGetEntity(uid, out var entity)
|
||||
? Play(filename, entity, audioParams) : null;
|
||||
return Play(filename, entity, GetFallbackCoordinates(EntityManager.GetComponent<TransformComponent>(entity).MapPosition), audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return Play(filename, coordinates, audioParams);
|
||||
return Play(filename, coordinates, GetFallbackCoordinates(coordinates.ToMap(_entityManager)), audioParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,14 +70,14 @@ namespace Robust.Client.GameObjects
|
||||
private void HandleDirtyEvent(OccluderDirtyEvent ev)
|
||||
{
|
||||
var sender = ev.Sender;
|
||||
if (sender.IsValid() &&
|
||||
sender.TryGetComponent(out ClientOccluderComponent? iconSmooth)
|
||||
if (EntityManager.EntityExists(sender) &&
|
||||
EntityManager.TryGetComponent(sender, out ClientOccluderComponent? iconSmooth)
|
||||
&& iconSmooth.Running)
|
||||
{
|
||||
var grid1 = _mapManager.GetGrid(sender.Transform.GridID);
|
||||
var coords = sender.Transform.Coordinates;
|
||||
var grid1 = _mapManager.GetGrid(EntityManager.GetComponent<TransformComponent>(sender).GridID);
|
||||
var coords = EntityManager.GetComponent<TransformComponent>(sender).Coordinates;
|
||||
|
||||
_dirtyEntities.Enqueue(sender.Uid);
|
||||
_dirtyEntities.Enqueue(sender);
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.North));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.South));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.East));
|
||||
@@ -113,13 +113,13 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
internal sealed class OccluderDirtyEvent : EntityEventArgs
|
||||
{
|
||||
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition)
|
||||
public OccluderDirtyEvent(EntityUid sender, (GridId grid, Vector2i pos)? lastPosition)
|
||||
{
|
||||
LastPosition = lastPosition;
|
||||
Sender = sender;
|
||||
}
|
||||
|
||||
public (GridId grid, Vector2i pos)? LastPosition { get; }
|
||||
public IEntity Sender { get; }
|
||||
public EntityUid Sender { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IRobustSerializer _serializer = default!;
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
|
||||
private readonly HashSet<IEntity> _updateQueue = new();
|
||||
private readonly HashSet<EntityUid> _updateQueue = new();
|
||||
|
||||
public readonly Dictionary<EntityUid, IContainer> ExpectedEntities = new();
|
||||
|
||||
@@ -37,10 +37,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void HandleEntityInitialized(EntityInitializedMessage ev)
|
||||
{
|
||||
if (!ExpectedEntities.TryGetValue(ev.Entity.Uid, out var container))
|
||||
if (!ExpectedEntities.TryGetValue(ev.Entity, out var container))
|
||||
return;
|
||||
|
||||
RemoveExpectedEntity(ev.Entity.Uid);
|
||||
RemoveExpectedEntity(ev.Entity);
|
||||
|
||||
if (container.Deleted)
|
||||
return;
|
||||
@@ -89,12 +89,12 @@ namespace Robust.Client.GameObjects
|
||||
container.OccludesLight = occludesLight;
|
||||
|
||||
// Remove gone entities.
|
||||
List<IEntity>? toRemove = null;
|
||||
List<EntityUid>? toRemove = null;
|
||||
foreach (var entity in container.ContainedEntities)
|
||||
{
|
||||
if (!entityUids.Contains(entity.Uid))
|
||||
if (!entityUids.Contains(entity))
|
||||
{
|
||||
toRemove ??= new List<IEntity>();
|
||||
toRemove ??= new List<EntityUid>();
|
||||
toRemove.Add(entity);
|
||||
}
|
||||
}
|
||||
@@ -123,11 +123,11 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
// Add new entities.
|
||||
foreach (var entityUid in entityUids)
|
||||
foreach (var entity in entityUids)
|
||||
{
|
||||
if (!EntityManager.TryGetEntity(entityUid, out var entity))
|
||||
if (!EntityManager.EntityExists(entity))
|
||||
{
|
||||
AddExpectedEntity(entityUid, container);
|
||||
AddExpectedEntity(entity, container);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var toUpdate in _updateQueue)
|
||||
{
|
||||
if (toUpdate.Deleted)
|
||||
if (EntityManager.Deleted(toUpdate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -183,22 +183,22 @@ namespace Robust.Client.GameObjects
|
||||
_updateQueue.Clear();
|
||||
}
|
||||
|
||||
private static void UpdateEntityRecursively(IEntity entity)
|
||||
private void UpdateEntityRecursively(EntityUid entity)
|
||||
{
|
||||
// TODO: Since we are recursing down,
|
||||
// we could cache ShowContents data here to speed it up for children.
|
||||
// Am lazy though.
|
||||
UpdateEntity(entity);
|
||||
|
||||
foreach (var child in entity.Transform.Children)
|
||||
foreach (var child in EntityManager.GetComponent<TransformComponent>(entity).Children)
|
||||
{
|
||||
UpdateEntityRecursively(child.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateEntity(IEntity entity)
|
||||
private void UpdateEntity(EntityUid entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out SpriteComponent? sprite))
|
||||
if (EntityManager.TryGetComponent(entity, out SpriteComponent? sprite))
|
||||
{
|
||||
sprite.ContainerOccluded = false;
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out PointLightComponent? light))
|
||||
if (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
|
||||
{
|
||||
light.ContainerOccluded = false;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user