Compare commits

..

18 Commits

Author SHA1 Message Date
PJB3005
d6338ba8e2 Version: 156.0.9 2025-12-01 16:08:23 +01:00
PJB3005
62b03ed9d1 Backport BitArray .NET 10 serializer fix
83ad6042a7 & b267cd6fb4

Does not include test code to avoid risking merge conflicts.

(cherry picked from commit 415585a30d74fcae61f581808220a7aaeca3eaf5)
(cherry picked from commit e36628a6d436ea08d6d31441c101a88a5504c515)
(cherry picked from commit 41a4a542df2d62d081a943b027f0f56694283198)
2025-12-01 16:08:22 +01:00
PJB3005
c74ec36560 Version: 156.0.8 2025-09-26 13:42:22 +02:00
PJB3005
f8a6491d13 Validate that content assemblies have a limited list of names.
Also, only read assemblies once from disk

(cherry picked from commit 443a8dfca65be7d60c4bd46181b4c749b4756114)
(cherry picked from commit a6a68e8ad91ce19c08c2b9200b4044acbf9c04a2)
2025-09-26 13:42:22 +02:00
PJB3005
d2d67b8ec9 Version: 156.0.7 2025-09-19 09:17:43 +02:00
Skye
0691c5b007 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:43 +02:00
PJB3005
4b07dcb228 Version: 156.0.6 2025-09-14 15:13:21 +02:00
PJB3005
61d1ce54f2 Squashed commit of the following:
commit d4f265c314
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sun Sep 14 14:32:44 2025 +0200

    Fix incorrect path combine in DirLoader and WritableDirProvider

    This (and the other couple past commits) reported by Elelzedel.

commit 7654d38612
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 22:50:51 2025 +0200

    Move CEF cache out of data directory

    Don't want content messing with this...

commit cdcc255123
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:11:16 2025 +0200

    Make Robust.Client.WebView.Cef.Program internal.

commit 2f56a6a110
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:10:46 2025 +0200

    Update SpaceWizards.NFluidSynth to 0.2.2

commit 16fc48cef2
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:09:43 2025 +0200

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

(cherry picked from commit 2f07159336bc640e41fbbccfdec4133a68c13bdb)
(cherry picked from commit d6c3212c74373ed2420cc4be2cf10fcd899c2106)
(cherry picked from commit bfa70d7e2ca6758901b680547fcfa9b24e0610b7)
(cherry picked from commit 06e52f5d58efc1491915822c2650f922673c82c6)
(cherry picked from commit 4413695c77fb705054c2f81fa18ec0a189b685dd)
(cherry picked from commit ff8461a6fb545dec80a77ece07a46cf1df5de830)
(cherry picked from commit 59998668c10e58c7f1fb759de6497bf9c23b450a)
2025-09-14 15:13:21 +02:00
Pieter-Jan Briers
6660471f01 Version: 156.0.5 2024-08-11 19:54:53 +02:00
Pieter-Jan Briers
007cd84384 Use absolute path for explorer.exe
frick me

(cherry picked from commit 0284eb0430)
2024-08-11 19:54:53 +02:00
Pieter-Jan Briers
1dce85a0d1 Version: 156.0.4 2024-08-11 19:33:08 +02:00
Pieter-Jan Briers
e96ef3be6d Compile compat fixes
(cherry picked from commit 025d90d281)
(cherry picked from commit 799702b814)
(cherry picked from commit 4600ee8e5788891f1b610e2d5141fb4e1228d323)
(cherry picked from commit e5bce321669ec50c05b86bf3f5fa9dbd2dfe40ef)
2024-08-11 19:33:07 +02:00
Pieter-Jan Briers
54d94f0257 Version: 156.0.3 2024-08-11 18:04:19 +02:00
Pieter-Jan Briers
f8c6d00fed Security updates (#5353)
* Fix security bug in WritableDirProvider.OpenOsWindow()

Reported by @NarryG and @nyeogmi

* Sandbox updates

* Update ImageSharp again

(cherry picked from commit 7d778248ee)
(cherry picked from commit f66cda74e95619ddba2221bda644bf4394619805)
(cherry picked from commit db8ba83866c523e08e4fba0b80cd954f4f190613)
(cherry picked from commit 5ea7aa07c26a499a2fb9930a09ef8d13f85494c0)
(cherry picked from commit 229b60b6af8c36b5f741de840d37e6cd95d5760d)
2024-08-11 18:04:19 +02:00
Pieter-Jan Briers
a654a6cf43 Version: 156.0.2 2024-03-10 21:23:36 +01:00
Pieter-Jan Briers
6211cf2e03 global.json force .NET 7 2024-03-10 21:23:14 +01:00
Pieter-Jan Briers
a522b4cf86 Version: 156.0.1 2024-03-10 20:50:29 +01:00
Pieter-Jan Briers
6d33be8c0f Backport 859f150404
(cherry picked from commit 24d5c26fa6)
(cherry picked from commit 688efac67b634c613539b783a9fb6e679948cd53)
2024-03-10 20:50:29 +01:00
549 changed files with 9967 additions and 15947 deletions

19
.github/CODEOWNERS vendored
View File

@@ -1,12 +1,23 @@
# Last match in file takes precedence.
# Ping for all PRs
* @PJB3005 @DrSmugleaf
* @Acruid @PJB3005 @ZoldorfTheWizard
/Robust.Client.NameGenerator @PaulRitter
/Robust.Client.Injectors @PaulRitter
/Robust.Generators @PaulRitter
/Robust.Analyzers @PaulRitter
/Robust.*/GameStates @PaulRitter
/Robust.Shared/Analyzers @PaulRitter
/Robust.*/Serialization @PaulRitter @DrSmugleaf
/Robust.*/Prototypes @PaulRitter
/Robust.Shared/GameObjects/ComponentDependencies @PaulRitter
/Robust.*/Containers @PaulRitter
# Be they Fluent translations or Freemarker templates, I know them both!
*.ftl @RemieRichards
# commands commands commands commands
**/Toolshed/** @moonheart08
*Command.cs @moonheart08
*Commands.cs @moonheart08
# Physics
**/Robust.Shared/Physics/** @metalgearsloth

View File

@@ -33,10 +33,10 @@ jobs:
mkdir "release/${{ steps.parse_version.outputs.version }}"
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
- name: Upload files to centcomm
- name: Upload files to Suns
uses: appleboy/scp-action@master
with:
host: centcomm.spacestation14.io
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
source: "release/${{ steps.parse_version.outputs.version }}"
@@ -46,7 +46,7 @@ jobs:
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: centcomm.spacestation14.io
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}

3
.gitmodules vendored
View File

@@ -13,6 +13,3 @@
[submodule "cefglue"]
path = cefglue
url = https://github.com/space-wizards/cefglue.git
[submodule "Arch/Arch"]
path = Arch/Arch
url = https://github.com/space-wizards/Arch.git

Submodule Arch/Arch deleted from c76d18feb7

View File

@@ -1,94 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<TargetFramework>net7.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<Nullable>enable</Nullable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<PackageId>Arch</PackageId>
<Title>Arch</Title>
<Version>1.2.7.1-alpha</Version>
<Authors>genaray</Authors>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<Description>A high performance c# net.6 and net.7 archetype based ECS ( Entity component system ).</Description>
<PackageReleaseNotes>Updated LowLevel which fixes bugs. </PackageReleaseNotes>
<PackageTags>c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system;stride;unity;godot;</PackageTags>
<PackageProjectUrl>https://github.com/genaray/Arch</PackageProjectUrl>
<RepositoryUrl>https://github.com/genaray/Arch.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<IsPackable>true</IsPackable>
<LangVersion>11</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Copyright>Apache2.0</Copyright>
<NoWarn>1701;1702;1591</NoWarn>
<Configurations>Debug;Debug-PureECS;Debug-Events;Release;Release-PureECS;Release-Events;</Configurations>
<AssemblyName>Arch</AssemblyName>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
<DefaultItemExcludes>src/Arch/**/*</DefaultItemExcludes>
<DefineConstants>$(DefineConstants);PURE_ECS;CONTRACTS_FULL</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DefineConstants>$(DefineConstants);PURE_ECS;CONTRACTS_FULL;TRACE;</DefineConstants>
<Optimize>false</Optimize>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Arch.Benchmarks" />
<InternalsVisibleTo Include="Arch.Tests" />
</ItemGroup>
<ItemGroup>
<Using Include="System" />
<Using Include="System.Collections" />
<Using Include="System.Collections.Generic" />
<Using Include="System.Diagnostics" />
<Using Include="System.Diagnostics.CodeAnalysis" />
<Using Include="System.IO" />
<Using Include="System.Linq" />
<Using Include="System.Runtime.CompilerServices" />
<Using Include="System.Runtime.InteropServices" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include=".\Arch\src\Arch.SourceGen\Arch.SourceGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Arch.LowLevel" Version="1.1.0" />
<PackageReference Include="Collections.Pooled" Version="2.0.0-preview.27" />
<PackageReference Include="CommunityToolkit.HighPerformance" Version="7.1.2" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageReference Include="ZeroAllocJobScheduler" Version="1.0.2" />
</ItemGroup>
<ItemGroup>
<Compile Include="Arch\src\Arch\**\*.cs">
<Link>Arch\%(RecursiveDir)%(Filename)%(Extension)</Link>
</Compile>
<Compile Remove="Arch\src\Arch\obj\**\*.cs" />
<InternalsVisibleTo Include="Arch.Benchmarks" />
<InternalsVisibleTo Include="Arch.Tests" />
</ItemGroup>
<Import Project="../MSBuild/Robust.Properties.targets" />
</Project>

74
Directory.Packages.props Normal file
View File

@@ -0,0 +1,74 @@
<Project>
<PropertyGroup>
<!--
We actually set ManagePackageVersionsCentrally manually in another import file.
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
https://github.com/NuGet/NuGet.Client/pull/5572
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
-->
<ManagePackageVersionsCentrally />
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="ILReader.Core" Version="1.0.0.4" />
<PackageVersion Include="JetBrains.Annotations" Version="2023.3.0" />
<PackageVersion Include="JetBrains.Profiler.Api" Version="1.4.0" />
<PackageVersion Include="Linguini.Bundle" Version="0.1.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="1.1.1"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.NUnit" Version="1.1.1"/>
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeCoverage" Version="17.8.0" />
<PackageVersion Include="Microsoft.Data.Sqlite.Core" Version="8.0.0" />
<PackageVersion Include="Microsoft.DotNet.RemoteExecutor" Version="8.0.0-beta.24059.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
<PackageVersion Include="Microsoft.ILVerification" Version="8.0.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
<PackageVersion Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="NUnit" Version="4.0.1" />
<PackageVersion Include="NUnit.Analyzers" Version="3.10.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="Nett" Version="0.15.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
<PackageVersion Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageVersion Include="Pidgin" Version="3.2.2" />
<PackageVersion Include="Robust.Natives" Version="0.1.1" />
<PackageVersion Include="Robust.Natives.Cef" Version="120.1.9" />
<PackageVersion Include="Robust.Shared.AuthLib" Version="0.1.2" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.7" />
<PackageVersion Include="SQLitePCLRaw.provider.sqlite3" Version="2.1.7" />
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.5" />
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
<PackageVersion Include="YamlDotNet" Version="13.7.1" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="PolySharp" Version="1.14.1" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -54,570 +54,31 @@ END TEMPLATE-->
*None yet*
## 182.0.0
## 156.0.9
### Breaking changes
* Add EntityUid's generation / version to the hashcode.
## 156.0.8
## 181.0.2
## 156.0.7
### Bugfixes
* Fix exceptions from having too many lights on screen and causing the game to go black.
* Fix components having events raised in ClientGameStateManager before fully set and causing nullable reference exceptions.
* Replace tile intersection IEnumerables with TileEnumerator internally. Also made it public for external callers that wish to avoid IEnumerable.
## 156.0.6
## 181.0.1
## 156.0.5
### Bugfixes
* Fix the non-generic HasComp and add a test for good measure.
## 156.0.4
## 181.0.0
## 156.0.3
### Breaking changes
- Arch is merged refactoring how components are stored on engine. There's minimal changes on the API end to facilitate component nullability with much internal refactoring.
## 156.0.2
## 180.2.1
## 180.2.0
### New features
* Add EnsureEntity variants that take in collections.
* Add more MapSystem helper methods.
### Internal
* Cache some more PVS data to avoid re-allocating every tick.
## 180.1.0
### New features
* Add the map name to lsmap.
* Add net.pool_size to CVars to control the message data pool size in Lidgren and to also toggle pooling.
### Bugfixes
* Fix physics contraints causing enormous heap allocations.
* Fix potential error when writing a runtime log.
* Fix shape lookups for non-hard fixtures in EntityLookupSystem from 180.0.0
## 180.0.0
### Breaking changes
* Removed some obsolete methods from EntityLookupSystem.
### New features
* PhysicsSystem.TryGetNearest now supports chain shapes.
* Add IPhysShape methods to EntityLookupSystem rather than relying on AABB checks.
* Add some more helper methods to SharedTransformSystem.
* Add GetOrNew dictionary extension that also returns a bool on whether the key existed.
* Add a GetAnchoredEntities overload that takes in a list.
### Other
* Use NetEntities for the F3 debug panel to align with command usage.
## 179.0.0
### Breaking changes
* EyeComponent.Eye is no longer nullable
### New features
* Light rendering can now be enabled or disable per eye.
### Bugfixes
* Deserializing old maps with empty grid chunks should now just ignore those chunks.
### Other
* UnknownPrototypeException now also tells you the prototype kind instead of just the unkown ID.
* Adding or removing networked components while resetting predicted entities now results in a more informative exception.
## 178.0.0
### Breaking changes
* Most methods in ActorSystem have been moved to ISharedPlayerManager.
* Several actor/player related components and events have been moved to shared.
### New features
* Added `NetListAsArray<T>.Value` to the sandbox whitelist
## 177.0.0
### Breaking changes
* Removed toInsertXform and added containerXform in SharedContainerSystem.CanInsert.
* Removed EntityQuery parameters from SharedContainerSystem.IsEntityOrParentInContainer.
* Changed the signature of ContainsEntity in SharedTransformSystem to use Entity<T>.
* Removed one obsoleted SharedTransformSystem.AnchorEntity method.
* Changed signature of SharedTransformSystem.SetCoordinates to use Entity<T>.
### New features
* Added more Entity<T> query methods.
* Added BeforeApplyState event to replay playback.
### Bugfixes
* Fixed inverted GetAllMapGrids map id check.
* Fixed transform test warnings.
* Fixed PlacementManager warnings.
* Fixed reparenting bug for entities that are being deleted.
### Other
* Changed VerticalAlignment of RichTextLabel to Center to be consistent with Label.
* Changed PVS error log to be a warning instead.
* Marked insert and remove container methods as obsolete, added container system methods to replace them.
* Marked TransformComponent.MapPosition as obsolete, added GetMapCoordinates system method to replace it.
### Internal
* Moved TryGetUi/TryToggleUi/ToggleUi/TryOpen/OpenUi/TryClose/CloseUi methods from UserInterfaceSystem to SharedUserInterfaceSystem.
## 176.0.0
### Breaking changes
* Reverted audio rework temporarily until packaging is fixed.
* Changes to Robust.Packaging to facilitate Content.Packaging ports from the python packaging scripts.
### New features
* Add a cvar for max game state buffer size.
* Add an overload for GetEntitiesInRange that takes in a set.
### Bugfixes
* Fix PVS initial list capacity always being 0.
* Fix replay lerp error spam.
## 175.0.0
### Breaking changes
* Removed static SoundSystem.Play methods.
* Moved IPlayingAudioStream onto AudioComponent and entities instead of an abstract stream.
* IResourceCache is in shared and IClientResourceCache is the client version to use for textures.
* Default audio attenuation changed from InverseDistanceClamped to LinearDistanceClamped.
* Removed per-source audio attenuation.
### New features
* Add preliminary support for EFX Reverb presets + auxiliary slots; these are also entities.
* Audio on grid entities is now attached to the grid.
### Bugfixes
* If an audio entity comes into PVS range its track will start at the relevant offset and not the beginning.
* Z-Axis offset is considered for ReferenceDistance / MaxDistance for audio.
* Audio will now pause if the attached entity is paused.
### Other
* Changed audio Z-Axis offset from -5m to -1m.
## 174.0.0
### Breaking changes
* ActorComponent has been moved to `Robust.Shared.Player` (namespace changed).
### New features
* Added `SpriteSystem.GetFrame()` method, which takes in an animated RSI and a time and returns a frame/texture.
* Added `IRobustRandom.NextAngle()`
## 173.1.0
### New features
* Add physics chain shapes from Box2D.
## 173.0.0
### Breaking changes
* Remove GridModifiedEvent in favor of TileChangedEvent.
### Bugfixes
* Fix some grid rendering bugs where chunks don't get destroyed correctly.
## 172.0.0
### Breaking changes
* Remove TryLifestage helper methods.
* Refactor IPlayerManager to remove more IPlayerSession, changed PlayerAttachedEvent etc on client to have the Local prefix, and shuffled namespaces around.
### New features
* Add EnsureComponent(ref Entity<\T?>)
### Bugfixes
* Re-add force ask threshold and fix other PVS bugs.
## 171.0.0
### Breaking changes
* Change PlaceNextTo method names to be more descriptive.
* Rename RefreshRelay for joints to SetRelay to match its behaviour.
### Bugfixes
* Fix PVS error spam for joint relays not being cleaned up.
### Other
* Set EntityLastModifiedTick on entity spawn.
## 170.0.0
### Breaking changes
* Removed obsolete methods and properties in VisibilitySystem, SharedContainerSystem and MetaDataComponent.
### Bugfixes
* Fixed duplicate command error.
* Fixed not being able to delete individual entities with the delete command.
### Other
* FileLogHandler logs can now be deleted while the engine is running.
## 169.0.1
### Other
* The client now knows about registered server-side toolshed commands.
## 169.0.0
### Breaking changes
* Entity<T> has been introduced to hold a component and its owning entity. Some methods that returned and accepted components directly have been removed or obsoleted to reflect this.
### Other
* By-value events may now be subscribed to by-ref.
* The manifest's assemblyPrefix value is now respected on the server.
## 168.0.0
### Breaking changes
* The Component.OnRemove method has been removed. Use SubscribeLocalEvent<TComp, ComponentRemove>(OnRemove) from an EntitySystem instead.
## 167.0.0
### Breaking changes
* Remove ComponentExtensions.
* Remove ContainerHelpers.
* Change some TransformSystem methods to fix clientside lerping.
### Bugfixes
* Fixed PVS bugs from dropped entity states.
### Other
* Add more joint debug asserts.
## 166.0.0
### Breaking changes
* EntityUid-NetEntity conversion methods now return null when given a null value, rather than returning an invalid id.
* ExpandPvsEvent now defaults to using null lists to reduce allocations.
* Various component lifestage related methods have been moved from the `Component` class to `EntityManager`.
* Session/client specific PVS overrides are now always recursive, which means that all children of the overriden entity will also get sent.
### New features
* Added a SortedSet yaml serializer.
### Other
* AddComponentUninitialized is now marked as obsolete and will be removed in the future.
* DebugTools.AssertOwner() now accepts null components.
## 165.0.0
### Breaking changes
* The arguments of `SplitContainer`s resize-finished event have changed.
### New features
* The YAML validator now checks the default values of ProtoId<T> and EntProtoId data fields.
### Bugfixes
* The minimum draggable area of split containers now blocks mouse inputs.
## 164.0.0
### Breaking changes
* Make automatic component states infer cloneData.
* Removed cloneData from AutoNetworkedFieldAttribute. This is now automatically inferred.
### Internal
* Reduce Transform GetComponents in RecursiveDeleteEntity.
## 163.0.0
### Breaking changes
* Moved TimedDespawn to engine for a component that deletes the attached entity after a timer has elapsed.
### New features
* Add ExecuteCommand for integration tests.
* Allow adding / removing widgets of cub-controls.
* Give maps / grids a default name to help with debugging.
* Use ToPrettyString in component resolve errors to help with debugging.
### Bugfixes
* Fix console backspace exception.
* Fix rendering invalid maps spamming exceptions every frame.
### Internal
* Move ClientGameStatemanager local variables to fields to avoid re-allocating every tick.
## 162.2.1
## 162.2.0
### New features
* Add support for automatically networking entity lists and sets.
* Add nullable conversion operators for ProtoIds.
* Add LocId serializer for validation.
### Bugfixes
* Fix deleting a contact inside of collision events throwing.
* Localize VV.
### Internal
* Use CollectionsMarshal in GameStateManager.
## 162.1.1
### Bugfixes
* Fixes "NoSpawn" entities appearing in the spawn menu.
## 162.1.0
### New features
* Mark ProtoId as NetSerializable.
### Bugfixes
* Temporarily revert NetForceAckThreshold change as it can lead to client stalling.
* Fix eye visibility layers not updating on children when a parent changes.
### Internal
* Use CollectionsMarshal in RobustTree and AddComponentInternal.
## 162.0.0
### New features
* Add entity categories for prototypes and deprecate the `noSpawn` tag.
* Add missing proxy method for `TryGetEntityData`.
* Add NetForceAckThreshold cvar to forcibly update acks for late clients.
### Internal
* Use CollectionMarshals in PVS and DynamicTree.
* Make the proxy methods use MetaQuery / TransformQuery.
## 161.1.0
### New features
* Add more DebugTools assert variations.
### Bugfixes
* Don't attempt to insert entities into deleted containers.
* Try to fix oldestAck not being set correctly leading to deletion history getting bloated for pvs.
## 161.0.0
### Breaking changes
* Point light animations now need to use different component fields in order to animate the lights. `Enabled` should be replaced with `AnimatedEnable` and `Radius` should be replaced with `AnimatedRadius`
### New features
* EntProtoId is now net-serializable
* Added print_pvs_ack command to debug PVS issues.
### Bugfixes
* Fixes AngleTypeParser not using InvariantCulture
* Fixed a bug that was causing `MetaDataComponent.LastComponentRemoved` to be updated improperly.
### Other
* The string representation of client-side entities now looks nicer and simply uses a 'c' prefix.
## 160.1.0
### New features
* Add optional MetaDataComponent args to Entitymanager methods.
### Internal
* Move _netComponents onto MetaDataComponent.
* Remove some component resolves internally on adding / removing components.
## 160.0.2
### Other
* Transform component and containers have new convenience fields to make using VIewVariables easier.
## 160.0.0
### Breaking changes
* ComponentReference has now been entirely removed.
* Sensor / non-hard physics bodies are now included in EntityLookup by default.
## 159.1.0
## 159.0.3
### Bugfixes
* Fix potentially deleted entities having states re-applied when NetEntities come in.
## 159.0.2
### Bugfixes
* Fix PointLight state handling not queueing ComponentTree updates.
## 159.0.1
### Bugfixes
* Fix pending entity states not being removed when coming in (only on entity deletion).
### Internal
* Remove PhysicsComponent ref from Fixture.
## 159.0.0
### Breaking changes
* Remove ComponentReference from PointLights.
* Move more of UserInterfaceSystem to shared.
* Mark some EntitySystem proxy methods as protected instead of public.
### New features
* Make entity deletion take in a nullable EntityUid.
* Added a method to send predicted messages via BUIs.
### Other
* Add Obsoletions to more sourcegen serv4 methods.
* Remove inactive reviewers from CODEOWNERs.
## 158.0.0
### Breaking changes
* Remove SharedEyeComponent.
* Add Tile Overlay edge priority.
## 157.1.0
### New features
* UI tooltips now use rich text labels.
## 157.0.0
### Breaking changes
* Unrevert container changes from 155.0.0.
* Added server-client EntityUid separation. A given EntityUid will no longer refer to the same entity on the server & client.
* EntityUid is no longer net-serializable, use NetEntity instead, EntityManager & entity systems have helper methods for converting between the two,
## 156.0.1
## 156.0.0

View File

@@ -2203,207 +2203,3 @@
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.
- name: Arch
license: |
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022 Lars Matthäus/genaray
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,7 +1,7 @@
- type: entity
id: debugRotation
abstract: true
categories: [ debug ]
suffix: DEBUG
components:
- type: Sprite
netsync: false

View File

@@ -1,17 +0,0 @@
# debug related entities
- type: entityCategory
id: debug
name: entity-category-name-debug
description: entity-category-desc-debug
# entities that spawn other entities
- type: entityCategory
id: spawner
name: entity-category-name-spawner
description: entity-category-desc-spawner
# entities that should be hidden from the spawn menu
- type: entityCategory
id: hideSpawnMenu
name: entity-category-name-hide
description: entity-category-desc-hide

View File

@@ -17,15 +17,15 @@ cmd-error-dir-not-found = Could not find directory: {$dir}.
cmd-failure-no-attached-entity = There is no entity attached to this shell.
## 'help' command
cmd-help-desc = Display general help or help text for a specific command
cmd-help-help = Usage: help [command name]
cmd-oldhelp-desc = Display general help or help text for a specific command
cmd-oldhelp-help = Usage: help [command name]
When no command name is provided, displays general-purpose help text. If a command name is provided, displays help text for that command.
cmd-help-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
cmd-help-unknown = Unknown command: { $command }
cmd-help-top = { $command } - { $description }
cmd-help-invalid-args = Invalid amount of arguments.
cmd-help-arg-cmdname = [command name]
cmd-oldhelp-no-args = To display help for a specific command, write 'help <command>'. To list all available commands, write 'list'. To search for commands, use 'list <filter>'.
cmd-oldhelp-unknown = Unknown command: { $command }
cmd-oldhelp-top = { $command } - { $description }
cmd-oldhelp-invalid-args = Invalid amount of arguments.
cmd-oldhelp-arg-cmdname = [command name]
## 'cvar' command
cmd-cvar-desc = Gets or sets a CVar.

View File

@@ -1,8 +0,0 @@
entity-category-name-debug = Debug
entity-category-desc-debug = Entity prototypes intended for debugging & testing.
entity-category-name-spawner = Spawner
entity-category-desc-spawner = Entity prototypes that spawn other entities.
entity-category-name-hide = Hidden
entity-category-desc-hide = Entity prototypes that should be hidden from the spawn menu

View File

@@ -1,6 +1,5 @@
## ViewVariablesInstanceEntity
view-variables = View Variables
view-variable-instance-entity-server-components-add-component-button-placeholder = Add Component
view-variable-instance-entity-client-variables-tab-title = Client Variables
view-variable-instance-entity-client-components-tab-title = Client Components
@@ -9,4 +8,4 @@ view-variable-instance-entity-server-components-tab-title = Server Components
view-variable-instance-entity-client-components-search-bar-placeholder = Search
view-variable-instance-entity-server-components-search-bar-placeholder = Search
view-variable-instance-entity-add-window-server-components = Add Component [S]
view-variable-instance-entity-add-window-client-components = Add Component [C]
view-variable-instance-entity-add-window-client-components = Add Component [C]

View File

@@ -23,6 +23,16 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
"Make sure that methods subscribing to a ref event have the ref keyword for the event argument."
);
private static readonly DiagnosticDescriptor ByValueEventSubscribedByRefRule = new(
Diagnostics.IdValueEventRaisedByRef,
"Value event subscribed to by-ref",
"Tried to subscribe to a value event '{0}' by-ref.",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure that methods subscribing to value events do not have the ref keyword for the event argument."
);
private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
Diagnostics.IdByRefEventRaisedByValue,
"By-ref event raised by value",
@@ -45,6 +55,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
ByRefEventSubscribedByValueRule,
ByValueEventSubscribedByRefRule,
ByRefEventRaisedByValueRule,
ByValueEventRaisedByRefRule
);
@@ -53,9 +64,71 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
context.EnableConcurrentExecution();
context.RegisterOperationAction(CheckEventSubscription, OperationKind.Invocation);
context.RegisterOperationAction(CheckEventRaise, OperationKind.Invocation);
}
private void CheckEventSubscription(OperationAnalysisContext context)
{
if (context.Operation is not IInvocationOperation operation)
return;
var subscribeMethods = context.Compilation
.GetTypeByMetadataName("Robust.Shared.GameObjects.EntitySystem")?
.GetMembers()
.Where(m => m.Name.Contains("SubscribeLocalEvent"))
.Cast<IMethodSymbol>();
if (subscribeMethods == null)
return;
if (!subscribeMethods.Any(m => m.Equals(operation.TargetMethod.OriginalDefinition, Default)))
return;
var typeArguments = operation.TargetMethod.TypeArguments;
if (typeArguments.Length < 1 || typeArguments.Length > 2)
return;
if (operation.Arguments.First().Value is not IDelegateCreationOperation delegateCreation)
return;
if (delegateCreation.Target is not IMethodReferenceOperation methodReference)
return;
var eventParameter = methodReference.Method.Parameters.LastOrDefault();
if (eventParameter == null)
return;
ITypeSymbol eventArgument;
switch (typeArguments.Length)
{
case 1:
eventArgument = typeArguments[0];
break;
case 2:
eventArgument = typeArguments[1];
break;
default:
return;
}
var byRefAttribute = context.Compilation.GetTypeByMetadataName(ByRefAttribute);
if (byRefAttribute == null)
return;
var isByRefEventType = eventArgument
.GetAttributes()
.Any(attribute => attribute.AttributeClass?.Equals(byRefAttribute, Default) ?? false);
var parameterIsRef = eventParameter.RefKind == RefKind.Ref;
if (isByRefEventType != parameterIsRef)
{
var descriptor = isByRefEventType ? ByRefEventSubscribedByValueRule : ByValueEventSubscribedByRefRule;
var diagnostic = Diagnostic.Create(descriptor, operation.Syntax.GetLocation(), eventArgument);
context.ReportDiagnostic(diagnostic);
}
}
private void CheckEventRaise(OperationAnalysisContext context)
{
if (context.Operation is not IInvocationOperation operation)

View File

@@ -18,6 +18,7 @@ public static class Diagnostics
public const string IdInvalidNotNullableFlagType = "RA0011";
public const string IdNotNullableFlagValueType = "RA0012";
public const string IdByRefEventSubscribedByValue = "RA0013";
public const string IdValueEventSubscribedByRef = "RA0014";
public const string IdByRefEventRaisedByValue = "RA0015";
public const string IdValueEventRaisedByRef = "RA0016";
public const string IdDataDefinitionPartial = "RA0017";

View File

@@ -1,177 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using Arch.Core;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using Robust.Shared.Analyzers;
using static Robust.Benchmarks.EntityManager.ArchetypeComponentAccessBenchmark;
namespace Robust.Benchmarks.Arch;
[MemoryDiagnoser]
[Virtual]
public class ArchComponentAccessBenchmark
{
private const int N = 10000;
private static readonly Consumer Consumer = new();
private Entity _entity;
private World _world = default!;
private QueryDescription _singleQuery;
private QueryDescription _tenQuery;
[GlobalSetup]
public void GlobalSetup()
{
var _ = new JobScheduler.JobScheduler("ArchBenchmark");
_world = World.Create();
for (var i = 0; i < N; i++)
{
var entity = _world.Create();
// Randomly chosen id
if (entity.Id == 1584)
_entity = entity;
_world.Add(
entity,
new Struct1(),
new Struct2(),
new Struct3(),
new Struct4(),
new Struct5(),
new Struct6(),
new Struct7(),
new Struct8(),
new Struct9(),
new Struct10()
);
}
_singleQuery = new QueryDescription().WithAll<Struct1>();
_tenQuery = new QueryDescription().WithAll<Struct1, Struct2, Struct3, Struct4, Struct5, Struct6, Struct7, Struct8, Struct9, Struct10>();
}
[GlobalCleanup]
public void GlobalCleanup()
{
JobScheduler.JobScheduler.Instance.Dispose();
Environment.Exit(0);
}
[Benchmark]
public Struct1 GetSingle()
{
return _world.Get<Struct1>(_entity);
}
[Benchmark]
public (Struct1, Struct2, Struct3, Struct4, Struct5, Struct6, Struct7, Struct8, Struct9, Struct10)
GetTen()
{
return (
_world.Get<Struct1>(_entity),
_world.Get<Struct2>(_entity),
_world.Get<Struct3>(_entity),
_world.Get<Struct4>(_entity),
_world.Get<Struct5>(_entity),
_world.Get<Struct6>(_entity),
_world.Get<Struct7>(_entity),
_world.Get<Struct8>(_entity),
_world.Get<Struct9>(_entity),
_world.Get<Struct10>(_entity)
);
}
[Benchmark]
public bool HasSingle()
{
return _world.Has<Struct1>(_entity);
}
[Benchmark]
public bool HasTen()
{
return _world.Has<Struct1>(_entity) &&
_world.Has<Struct2>(_entity) &&
_world.Has<Struct3>(_entity) &&
_world.Has<Struct4>(_entity) &&
_world.Has<Struct5>(_entity) &&
_world.Has<Struct6>(_entity) &&
_world.Has<Struct7>(_entity) &&
_world.Has<Struct8>(_entity) &&
_world.Has<Struct9>(_entity) &&
_world.Has<Struct10>(_entity);
}
[Benchmark]
public void IterateSingle()
{
_world.Query(_singleQuery, static (ref Struct1 s) => Consumer.Consume(s));
}
[Benchmark]
public void IterateSingleInline()
{
_world.InlineQuery<QueryConsumer>(_singleQuery);
}
[Benchmark]
public void IterateSingleParallel()
{
_world.ParallelQuery(_singleQuery, static (ref Struct1 s) => Consumer.Consume(s));
}
[Benchmark]
public void IterateSingleInlineParallel()
{
_world.InlineParallelQuery<QueryConsumer>(_singleQuery);
}
[Benchmark]
public void IterateTen()
{
_world.Query(_tenQuery,
static (
ref Struct1 s1, ref Struct2 s2, ref Struct3 s3, ref Struct4 s4,
ref Struct5 s5, ref Struct6 s6, ref Struct7 s7, ref Struct8 s8,
ref Struct9 s9, ref Struct10 s10) =>
Consumer.Consume((s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)));
}
[Benchmark]
public void IterateTenInline()
{
_world.InlineQuery<QueryConsumer>(_tenQuery);
}
[Benchmark]
public void IterateTenParallel()
{
_world.ParallelQuery(_tenQuery,
static (
ref Struct1 s1, ref Struct2 s2, ref Struct3 s3, ref Struct4 s4,
ref Struct5 s5, ref Struct6 s6, ref Struct7 s7, ref Struct8 s8,
ref Struct9 s9, ref Struct10 s10) =>
Consumer.Consume((s1, s2, s3, s4, s5, s6, s7, s8, s9, s10)));
}
[Benchmark]
public void IterateTenInlineParallel()
{
_world.InlineParallelQuery<QueryConsumer>(_tenQuery);
}
private struct QueryConsumer : IForEach
{
private static readonly Consumer Consumer = new();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(Entity entity)
{
Consumer.Consume(entity);
}
}
}

View File

@@ -41,7 +41,7 @@ public partial class AddRemoveComponentBenchmark
{
for (var i = 2; i <= N+1; i++)
{
var uid = new EntityUid(i, -1);
var uid = new EntityUid(i);
_entityManager.AddComponent<A>(uid);
_entityManager.RemoveComponent<A>(uid);
}

View File

@@ -57,7 +57,7 @@ public class ComponentIndexBenchmark
private static class CompArrayIndex<T>
{
// ReSharper disable once StaticMemberInGenericType
public static readonly CompIdx Idx = new(Interlocked.Increment(ref _compIndexMaster), typeof(T));
public static readonly CompIdx Idx = new(Interlocked.Increment(ref _compIndexMaster));
}
private static CompIdx GetCompIdIndex(Type type)

View File

@@ -46,7 +46,7 @@ public partial class GetComponentBenchmark
{
for (var i = 2; i <= N+1; i++)
{
Comps[i] = _entityManager.GetComponent<A>(new EntityUid(i, -1));
Comps[i] = _entityManager.GetComponent<A>(new EntityUid(i));
}
// Return something so the JIT doesn't optimize out all the GetComponent calls.

View File

@@ -8,7 +8,6 @@
<NoWarn>RA0003</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Arch\Arch.csproj" />
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
<ProjectReference Include="..\Robust.UnitTesting\Robust.UnitTesting.csproj" />

View File

@@ -54,7 +54,7 @@ public class RecursiveMoveBenchmark
var mapSys = _entMan.System<SharedMapSystem>();
var mapId = mapMan.CreateMap();
var map = mapMan.GetMapEntityId(mapId);
var gridComp = mapMan.CreateGridEntity(mapId);
var gridComp = mapMan.CreateGrid(mapId);
var grid = gridComp.Owner;
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
_mapCoords = new EntityCoordinates(map, 100, 100);

View File

@@ -3,7 +3,7 @@ using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
public static class Program
internal static class Program
{
// This was supposed to be the main entry for the subprocess program... It doesn't work.
public static int Main(string[] args)

View File

@@ -4,6 +4,7 @@ using System.Net;
using System.Reflection;
using System.Text;
using Robust.Client.Console;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
@@ -23,6 +24,7 @@ namespace Robust.Client.WebView.Cef
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameControllerInternal _gameController = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -60,7 +62,10 @@ namespace Robust.Client.WebView.Cef
var cachePath = "";
if (_resourceManager.UserData is WritableDirProvider userData)
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
{
var rootDir = UserDataDir.GetUserDataDir(_gameController);
cachePath = Path.GetFullPath(Path.Combine(rootDir, "..", "cef_cache", "0"));
}
var settings = new CefSettings()
{

View File

@@ -1,6 +1,8 @@
using System;
using System.Linq;
using System.Net;
using Robust.Client.Configuration;
using Robust.Client.Debugging;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Client.Player;
@@ -8,12 +10,13 @@ using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -62,12 +65,12 @@ namespace Robust.Client
_configManager.OnValueChanged(CVars.NetTickrate, TickRateChanged, invokeImmediately: true);
_playMan.Initialize(0);
_playMan.Initialize();
_playMan.PlayerListUpdated += OnPlayerListUpdated;
Reset();
}
private void OnPlayerListUpdated()
private void OnPlayerListUpdated(object? sender, EventArgs e)
{
var serverPlayers = _playMan.PlayerCount;
if (_net.ServerChannel != null && GameInfo != null && _net.IsConnected)
@@ -127,10 +130,9 @@ namespace Robust.Client
{
DebugTools.Assert(RunLevel < ClientRunLevel.Connecting);
DebugTools.Assert(!_net.IsConnected);
var name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
_playMan.SetupSinglePlayer(name);
_playMan.Startup();
_playMan.LocalPlayer!.Name = PlayerNameOverride ?? _configManager.GetCVar(CVars.PlayerName);
OnRunLevelChanged(ClientRunLevel.SinglePlayerGame);
_playMan.JoinGame(_playMan.LocalSession!);
GameStartedSetup();
}
@@ -171,14 +173,22 @@ namespace Robust.Client
info.ServerName = serverName;
}
var channel = _net.ServerChannel!;
var maxPlayers = _configManager.GetCVar<int>("game.maxplayers");
info.ServerMaxPlayers = maxPlayers;
var userName = _net.ServerChannel!.UserName;
var userId = _net.ServerChannel.UserId;
// start up player management
_playMan.SetupMultiplayer(channel);
_playMan.PlayerStatusChanged += OnStatusChanged;
_playMan.Startup();
_playMan.LocalPlayer!.UserId = userId;
_playMan.LocalPlayer.Name = userName;
_playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged;
var serverPlayers = _playMan.PlayerCount;
_discord.Update(info.ServerName, channel.UserName, info.ServerMaxPlayers.ToString(), serverPlayers.ToString());
_discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString(), serverPlayers.ToString());
}
@@ -211,8 +221,6 @@ namespace Robust.Client
private void Reset()
{
_configManager.ReceivedInitialNwVars -= OnReceivedClientData;
_playMan.PlayerStatusChanged -= OnStatusChanged;
_configManager.ClearReceivedInitialNwVars();
OnRunLevelChanged(ClientRunLevel.Initialize);
}
@@ -255,17 +263,19 @@ namespace Robust.Client
Reset();
}
private void OnStatusChanged(object? sender, SessionStatusEventArgs e)
private void OnLocalStatusChanged(object? obj, StatusEventArgs eventArgs)
{
if (e.Session != _playMan.LocalSession)
return;
// player finished fully connecting to the server.
// OldStatus is used here because it can go from connecting-> connected or connecting-> ingame
if (e.OldStatus == SessionStatus.Connecting)
OnPlayerJoinedServer(e.Session);
else if (e.NewStatus == SessionStatus.InGame)
OnPlayerJoinedGame(e.Session);
if (eventArgs.OldStatus == SessionStatus.Connecting)
{
OnPlayerJoinedServer(_playMan.LocalPlayer!.Session);
}
if (eventArgs.NewStatus == SessionStatus.InGame)
{
OnPlayerJoinedGame(_playMan.LocalPlayer!.Session);
}
}
private void OnRunLevelChanged(ClientRunLevel newRunLevel)

View File

@@ -37,7 +37,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Physics;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Replays;

View File

@@ -2,13 +2,11 @@ using Robust.Client.GameObjects;
using Robust.Shared.ComponentTrees;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.ViewVariables;
namespace Robust.Client.ComponentTrees;
[RegisterComponent]
public sealed partial class LightTreeComponent: Component, IComponentTreeComponent<PointLightComponent>
{
[ViewVariables]
public DynamicTree<ComponentTreeEntry<PointLightComponent>> Tree { get; set; } = default!;
}

View File

@@ -13,7 +13,7 @@ using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;

View File

@@ -22,11 +22,13 @@ namespace Robust.Client.Console.Commands
return;
}
var netEntity = NetEntity.Parse(args[0]);
var entity = _entityManager.GetEntity(netEntity);
var entity = EntityUid.Parse(args[0]);
var componentName = args[1];
var component = _componentFactory.GetComponent(componentName);
var component = (Component) _componentFactory.GetComponent(componentName);
component.Owner = entity;
_entityManager.AddComponent(entity, component);
}
}
@@ -47,8 +49,7 @@ namespace Robust.Client.Console.Commands
return;
}
var netEntity = NetEntity.Parse(args[0]);
var entityUid = _entityManager.GetEntity(netEntity);
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var registration = _componentFactory.GetRegistration(componentName);

View File

@@ -78,7 +78,14 @@ namespace Robust.Client.Console.Commands
message.Append($"net ID: {registration.NetID}");
}
message.Append($", References:");
shell.WriteLine(message.ToString());
foreach (var type in registration.References)
{
shell.WriteLine($" {type}");
}
}
catch (UnknownComponentException)
{
@@ -256,7 +263,7 @@ namespace Robust.Client.Console.Commands
return;
}
var uid = EntityUid.Parse(args[0], "-1");
var uid = EntityUid.Parse(args[0]);
var entmgr = _entityManager;
if (!entmgr.EntityExists(uid))
{
@@ -289,7 +296,6 @@ namespace Robust.Client.Console.Commands
internal sealed class SnapGridGetCell : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "sggcell";
@@ -304,7 +310,7 @@ namespace Robust.Client.Console.Commands
string indices = args[1];
if (!NetEntity.TryParse(args[0], out var gridNet))
if (!EntityUid.TryParse(args[0], out var gridUid))
{
shell.WriteError($"{args[0]} is not a valid entity UID.");
return;
@@ -316,7 +322,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid))
if (_map.TryGetGrid(gridUid, out var grid))
{
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
@@ -424,7 +430,6 @@ namespace Robust.Client.Console.Commands
internal sealed class GridTileCount : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "gridtc";
@@ -437,8 +442,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (!NetEntity.TryParse(args[0], out var gridUidNet) ||
!_entManager.TryGetEntity(gridUidNet, out var gridUid))
if (!EntityUid.TryParse(args[0], out var gridUid))
{
shell.WriteLine($"{args[0]} is not a valid entity UID.");
return;

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var type = Type.GetType(args[0]);
var type = GetType(args[0]);
if (type == null)
{
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
shell.WriteLine(sig);
}
}
private Type? GetType(string name)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.GetType(name) is { } type)
return type;
}
return null;
}
}
#endif
}

View File

@@ -43,7 +43,6 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
GC.Collect();
Span<EntityUid> ents = stackalloc EntityUid[amount];
var stopwatch = new Stopwatch();
stopwatch.Start();
@@ -51,17 +50,12 @@ public sealed class ProfileEntitySpawningCommand : IConsoleCommand
for (var i = 0; i < amount; i++)
{
ents[i] = _entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
_entities.SpawnEntity(prototype, MapCoordinates.Nullspace);
}
MeasureProfiler.SaveData();
shell.WriteLine($"Client: Profiled spawning {amount} entities in {stopwatch.Elapsed.TotalMilliseconds:N3} ms");
foreach (var ent in ents)
{
_entities.DeleteEntity(ent);
}
}
}
#endif

View File

@@ -1,7 +1,6 @@
#if DEBUG
using System.Numerics;
using System.Text;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.UserInterface;
@@ -9,6 +8,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Client.Debugging
@@ -19,7 +19,6 @@ namespace Robust.Client.Debugging
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IUserInterfaceManager _userInterface = default!;
[Dependency] private readonly MapSystem _mapSystem = default!;
private Label? _label;
@@ -71,7 +70,7 @@ namespace Robust.Client.Debugging
return;
}
var tile = _mapSystem.GetTileRef(gridUid, grid, spot);
var tile = grid.GetTileRef(spot);
_label.Position = mouseSpot.Position + new Vector2(32, 0);
if (_hovered?.GridId == gridUid && _hovered?.Tile == tile) return;
@@ -80,7 +79,7 @@ namespace Robust.Client.Debugging
var text = new StringBuilder();
foreach (var ent in _mapSystem.GetAnchoredEntities(gridUid, grid, spot))
foreach (var ent in grid.GetAnchoredEntities(spot))
{
if (EntityManager.TryGetComponent<MetaDataComponent>(ent, out var meta))
{

View File

@@ -46,6 +46,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Input;
@@ -206,7 +207,6 @@ namespace Robust.Client.Debugging
private readonly Font _font;
private HashSet<Joint> _drawnJoints = new();
private List<Entity<MapGridComponent>> _grids = new();
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IPlayerManager playerManager, IResourceCache cache, DebugPhysicsSystem system, EntityLookupSystem lookup, SharedPhysicsSystem physicsSystem)
{
@@ -231,33 +231,32 @@ namespace Robust.Client.Debugging
{
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
if (_entityManager.HasComponent<MapGridComponent>(physBody)) continue;
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner)) continue;
var xform = _physicsSystem.GetPhysicsTransform(physBody);
var comp = physBody.Comp;
var xform = _physicsSystem.GetPhysicsTransform(physBody.Owner);
const float AlphaModifier = 0.2f;
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody).Fixtures.Values)
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody.Owner).Fixtures.Values)
{
// Invalid shape - Box2D doesn't check for IsSensor but we will for sanity.
if (comp.BodyType == BodyType.Dynamic && fixture.Density == 0f && fixture.Hard)
if (physBody.BodyType == BodyType.Dynamic && fixture.Density == 0f && fixture.Hard)
{
DrawShape(worldHandle, fixture, xform, Color.Red.WithAlpha(AlphaModifier));
}
else if (!comp.CanCollide)
else if (!physBody.CanCollide)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.5f, 0.3f).WithAlpha(AlphaModifier));
}
else if (comp.BodyType == BodyType.Static)
else if (physBody.BodyType == BodyType.Static)
{
DrawShape(worldHandle, fixture, xform, new Color(0.5f, 0.9f, 0.5f).WithAlpha(AlphaModifier));
}
else if ((comp.BodyType & (BodyType.Kinematic | BodyType.KinematicController)) != 0x0)
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 (!comp.Awake)
else if (!physBody.Awake)
{
DrawShape(worldHandle, fixture, xform, new Color(0.6f, 0.6f, 0.6f).WithAlpha(AlphaModifier));
}
@@ -276,18 +275,15 @@ namespace Robust.Client.Debugging
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
var color = Color.Purple.WithAlpha(Alpha);
var transform = _physicsSystem.GetPhysicsTransform(physBody);
worldHandle.DrawCircle(Transform.Mul(transform, physBody.Comp.LocalCenter), 0.2f, color);
var transform = _physicsSystem.GetPhysicsTransform(physBody.Owner);
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 0.2f, color);
}
_grids.Clear();
_mapManager.FindGridsIntersecting(mapId, viewBounds, ref _grids);
foreach (var grid in _grids)
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, viewBounds))
{
var physBody = _entityManager.GetComponent<PhysicsComponent>(grid);
var physBody = _entityManager.GetComponent<PhysicsComponent>(grid.Owner);
var color = Color.Orange.WithAlpha(Alpha);
var transform = _physicsSystem.GetPhysicsTransform(grid);
var transform = _physicsSystem.GetPhysicsTransform(grid.Owner);
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 1f, color);
}
}
@@ -296,14 +292,14 @@ namespace Robust.Client.Debugging
{
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
{
if (_entityManager.HasComponent<MapGridComponent>(physBody)) continue;
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner)) continue;
var xform = _physicsSystem.GetPhysicsTransform(physBody);
var xform = _physicsSystem.GetPhysicsTransform(physBody.Owner);
const float AlphaModifier = 0.2f;
Box2? aabb = null;
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody).Fixtures.Values)
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody.Owner).Fixtures.Values)
{
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
@@ -322,11 +318,10 @@ namespace Robust.Client.Debugging
{
_drawnJoints.Clear();
var query = _entityManager.AllEntityQueryEnumerator<JointComponent>();
while (query.MoveNext(out var uid, out var jointComponent))
foreach (var jointComponent in _entityManager.EntityQuery<JointComponent>(true))
{
if (jointComponent.JointCount == 0 ||
!_entityManager.TryGetComponent(uid, out TransformComponent? xf1) ||
!_entityManager.TryGetComponent(jointComponent.Owner, out TransformComponent? xf1) ||
!viewAABB.Contains(xf1.WorldPosition)) continue;
foreach (var (_, joint) in jointComponent.Joints)
@@ -366,9 +361,6 @@ namespace Robust.Client.Debugging
_debugPhysicsSystem.PointCount = 0;
}
worldHandle.UseShader(null);
worldHandle.SetTransform(Matrix3.Identity);
}
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
@@ -378,31 +370,28 @@ namespace Robust.Client.Debugging
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
{
var hoverBodies = new List<Entity<PhysicsComponent>>();
var hoverBodies = new List<PhysicsComponent>();
var bounds = Box2.UnitCentered.Translated(_eyeManager.PixelToMap(mousePos.Position).Position);
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, bounds))
{
var uid = physBody.Owner;
if (_entityManager.HasComponent<MapGridComponent>(uid)) continue;
hoverBodies.Add((uid, physBody));
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 bodyEnt in hoverBodies)
foreach (var body in hoverBodies)
{
if (bodyEnt != hoverBodies[0])
if (body != hoverBodies[0])
{
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), "------");
row++;
}
var body = bodyEnt.Comp;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {bodyEnt.Owner}");
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++;
@@ -441,9 +430,6 @@ namespace Robust.Client.Debugging
}
}
}
screenHandle.UseShader(null);
screenHandle.SetTransform(Matrix3.Identity);
}
protected internal override void Draw(in OverlayDrawArgs args)
@@ -465,26 +451,11 @@ namespace Robust.Client.Debugging
{
switch (fixture.Shape)
{
case ChainShape cShape:
{
var count = cShape.Count;
var vertices = cShape.Vertices;
var v1 = Transform.Mul(xform, vertices[0]);
for (var i = 1; i < count; ++i)
{
var v2 = Transform.Mul(xform, vertices[i]);
worldHandle.DrawLine(v1, v2, color);
v1 = v2;
}
}
break;
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);
@@ -494,7 +465,6 @@ namespace Robust.Client.Debugging
worldHandle.DrawCircle(v1, 0.1f, color);
worldHandle.DrawCircle(v2, 0.1f, color);
}
}
break;
case PolygonShape poly:

View File

@@ -3,7 +3,6 @@ using System.IO;
using System.Reflection;
using System.Runtime.Loader;
using Robust.Client.WebViewHook;
using Robust.Shared.ContentPack;
using Robust.Shared.Log;
using Robust.Shared.Utility;

View File

@@ -287,6 +287,78 @@ namespace Robust.Client
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 ResourceManifestData.Default;
var yamlStream = new YamlStream();
using (stream)
{
using var streamReader = new StreamReader(stream, EncodingHelpers.UTF8);
yamlStream.Load(streamReader);
}
if (yamlStream.Documents.Count == 0)
return ResourceManifestData.Default;
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 = ReadStringArray(mapping, "modules") ?? Array.Empty<string>();
string? assemblyPrefix = null;
if (mapping.TryGetNode("assemblyPrefix", out var prefixNode))
assemblyPrefix = prefixNode.AsString();
string? defaultWindowTitle = null;
if (mapping.TryGetNode("defaultWindowTitle", out var winTitleNode))
defaultWindowTitle = winTitleNode.AsString();
string? windowIconSet = null;
if (mapping.TryGetNode("windowIconSet", out var iconSetNode))
windowIconSet = iconSetNode.AsString();
string? splashLogo = null;
if (mapping.TryGetNode("splashLogo", out var splashNode))
splashLogo = splashNode.AsString();
bool autoConnect = true;
if (mapping.TryGetNode("autoConnect", out var autoConnectNode))
autoConnect = autoConnectNode.AsBool();
var clientAssemblies = ReadStringArray(mapping, "clientAssemblies");
return new ResourceManifestData(
modules,
assemblyPrefix,
defaultWindowTitle,
windowIconSet,
splashLogo,
autoConnect,
clientAssemblies
);
static string[]? ReadStringArray(YamlMappingNode mapping, string key)
{
if (!mapping.TryGetNode(key, out var node))
return null;
var sequence = (YamlSequenceNode)node;
var array = new string[sequence.Children.Count];
for (var i = 0; i < array.Length; i++)
{
array[i] = sequence[i].AsString();
}
return array;
}
}
internal bool StartupSystemSplash(
GameControllerOptions options,
Func<ILogHandler>? logHandlerFactory,
@@ -360,7 +432,7 @@ namespace Robust.Client
_parallelMgr.Initialize();
_prof.Initialize();
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
@@ -385,7 +457,7 @@ namespace Robust.Client
_modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler;
}
_resourceManifest = ResourceManifestData.LoadResourceManifest(_resourceCache);
_resourceManifest = LoadResourceManifest();
{
// Handle GameControllerOptions implicit CVar overrides.
@@ -632,6 +704,7 @@ namespace Robust.Client
logManager.GetSawmill("ogl.debug.other").Level = LogLevel.Warning;
logManager.GetSawmill("gdparse").Level = LogLevel.Error;
logManager.GetSawmill("discord").Level = LogLevel.Warning;
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
logManager.GetSawmill("szr").Level = LogLevel.Info;
logManager.GetSawmill("loc").Level = LogLevel.Warning;
@@ -713,6 +786,20 @@ namespace Robust.Client
_clydeAudio.Shutdown();
}
private sealed record ResourceManifestData(
string[] Modules,
string? AssemblyPrefix,
string? DefaultWindowTitle,
string? WindowIconSet,
string? SplashLogo,
bool AutoConnect,
string[]? ClientAssemblies
)
{
public static readonly ResourceManifestData Default =
new ResourceManifestData(Array.Empty<string>(), null, null, null, null, true, null);
}
public event Action<FrameEventArgs>? TickUpdateOverride;
}
}

View File

@@ -1,53 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects;
public sealed partial class ClientEntityManager
{
protected override NetEntity GenerateNetEntity() => new(NextNetworkId++ | NetEntity.ClientEntity);
/// <summary>
/// If the client fails to resolve a NetEntity then during component state handling or the likes we
/// flag that comp state as requiring re-running if that NetEntity comes in.
/// </summary>
/// <returns></returns>
internal readonly Dictionary<NetEntity, List<(Type type, EntityUid Owner)>> PendingNetEntityStates = new();
public override bool IsClientSide(EntityUid uid, MetaDataComponent? metadata = null)
{
// Can't log false because some content code relies on invalid UIDs.
if (!MetaQuery.Resolve(uid, ref metadata, false))
return false;
return metadata.NetEntity.IsClientSide();
}
public override EntityUid EnsureEntity<T>(NetEntity nEntity, EntityUid callerEntity)
{
if (!nEntity.Valid)
{
return EntityUid.Invalid;
}
if (NetEntityLookup.TryGetValue(nEntity, out var entity))
{
return entity.Item1;
}
// Flag the callerEntity to have their state potentially re-run later.
var pending = PendingNetEntityStates.GetOrNew(nEntity);
pending.Add((typeof(T), callerEntity));
return entity.Item1;
}
public override EntityCoordinates EnsureCoordinates<T>(NetCoordinates netCoordinates, EntityUid callerEntity)
{
var entity = EnsureEntity<T>(netCoordinates.NetEntity, callerEntity);
return new EntityCoordinates(entity, netCoordinates.Position);
}
}

View File

@@ -16,7 +16,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Manager for entities -- controls things like template loading and instantiation
/// </summary>
public sealed partial class ClientEntityManager : EntityManager, IClientEntityManagerInternal
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientNetManager _networkManager = default!;
@@ -25,6 +25,8 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
protected override int NextEntityUid { get; set; } = EntityUid.ClientUid + 1;
public override void Initialize()
{
SetupNetworking();
@@ -35,16 +37,13 @@ namespace Robust.Client.GameObjects
public override void FlushEntities()
{
// Server doesn't network deletions on client shutdown so we need to
// manually clear these out or risk stale data getting used.
PendingNetEntityStates.Clear();
using var _ = _gameTiming.StartStateApplicationArea();
base.FlushEntities();
}
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, out MetaDataComponent metadata)
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, EntityUid uid)
{
return base.CreateEntity(prototypeName, out metadata, out _);
return base.CreateEntity(prototypeName, uid);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)
@@ -65,12 +64,9 @@ namespace Robust.Client.GameObjects
base.DirtyEntity(uid, meta);
}
public override void QueueDeleteEntity(EntityUid? uid)
public override void QueueDeleteEntity(EntityUid uid)
{
if (uid == null)
return;
if (IsClientSide(uid.Value))
if (uid.IsClientSide())
{
base.QueueDeleteEntity(uid);
return;
@@ -81,29 +77,23 @@ namespace Robust.Client.GameObjects
// Client-side entity deletion is not supported and will cause errors.
if (_client.RunLevel == ClientRunLevel.Connected || _client.RunLevel == ClientRunLevel.InGame)
LogManager.RootSawmill.Error($"Predicting the queued deletion of a networked entity: {ToPrettyString(uid.Value)}. Trace: {Environment.StackTrace}");
LogManager.RootSawmill.Error($"Predicting the queued deletion of a networked entity: {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
}
/// <inheritdoc />
public override void Dirty(EntityUid uid, IComponent component, MetaDataComponent? meta = null)
{
Dirty(new Entity<IComponent>(uid, component), meta);
}
/// <inheritdoc />
public override void Dirty<T>(Entity<T> ent, MetaDataComponent? meta = null)
public override void Dirty(EntityUid uid, Component component, MetaDataComponent? meta = null)
{
// Client only dirties during prediction
if (_gameTiming.InPrediction)
base.Dirty(ent, meta);
base.Dirty(uid, component, meta);
}
public override EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metaDataComponent = null)
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
{
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
return base.ToPrettyString(uid) with { Session = _playerManager.LocalPlayer.Session };
return base.ToPrettyString(uid);
else
return base.ToPrettyString(uid);
}
public override void RaisePredictiveEvent<T>(T msg)
@@ -170,7 +160,7 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel? channel)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel)
{
throw new NotSupportedException();
}

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using Robust.Client.Animations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using static Robust.Client.Animations.AnimationPlaybackShared;
namespace Robust.Client.GameObjects
@@ -19,5 +21,42 @@ namespace Robust.Client.GameObjects
= new();
internal bool HasPlayingAnimation = false;
/// <summary>
/// Start playing an animation.
/// </summary>
/// <param name="animation">The animation to play.</param>
/// <param name="key">
/// The key for this animation play. This key can be used to stop playback short later.
/// </param>
[Obsolete("Use AnimationPlayerSystem.Play() instead")]
public void Play(Animation animation, string key)
{
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AnimationPlayerSystem>().AddComponent(this);
var playback = new AnimationPlayback(animation);
PlayingAnimations.Add(key, playback);
}
[Obsolete("Use AnimationPlayerSystem.HasRunningAnimation() instead")]
public bool HasRunningAnimation(string key)
{
return PlayingAnimations.ContainsKey(key);
}
[Obsolete("Use AnimationPlayerSystem.Stop() instead")]
public void Stop(string key)
{
PlayingAnimations.Remove(key);
}
[Obsolete("Temporary method until the event is replaced with eventbus")]
internal void AnimationComplete(string key)
{
AnimationCompleted?.Invoke(key);
}
[Obsolete("Use AnimationCompletedEvent instead")]
public event Action<string>? AnimationCompleted;
}
}

View File

@@ -0,0 +1,136 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent, ComponentReference(typeof(SharedEyeComponent))]
public sealed partial class EyeComponent : SharedEyeComponent
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables] internal Eye? _eye = default!;
// Horrible hack to get around ordering issues.
internal bool _setCurrentOnInitialize;
[DataField("drawFov")] internal bool _setDrawFovOnInitialize = true;
[DataField("zoom")] internal Vector2 _setZoomOnInitialize = Vector2.One;
/// <summary>
/// If not null, this entity is used to update the eye's position instead of just using the component's owner.
/// </summary>
/// <remarks>
/// This is useful for things like vehicles that effectively need to hijack the eye. This allows them to do
/// that without messing with the main viewport's eye. This is important as there are some overlays that are
/// only be drawn if that viewport's eye belongs to the currently controlled entity.
/// </remarks>
[ViewVariables]
public EntityUid? Target;
public IEye? Eye => _eye;
[ViewVariables(VVAccess.ReadWrite)]
public bool Current
{
get => _eyeManager.CurrentEye == _eye;
set
{
if (_eye == null)
{
_setCurrentOnInitialize = value;
return;
}
if (_eyeManager.CurrentEye == _eye == value)
return;
if (value)
{
_eyeManager.CurrentEye = _eye;
}
else
{
_eyeManager.ClearCurrentEye();
}
}
}
public override Vector2 Zoom
{
get => _eye?.Zoom ?? _setZoomOnInitialize;
set
{
if (_eye == null)
{
_setZoomOnInitialize = value;
}
else
{
_eye.Zoom = value;
}
}
}
public override Angle Rotation
{
get => _eye?.Rotation ?? Angle.Zero;
set
{
if (_eye != null)
_eye.Rotation = value;
}
}
public override Vector2 Offset
{
get => _eye?.Offset ?? default;
set
{
if (_eye != null)
_eye.Offset = value;
}
}
public override bool DrawFov
{
get => _eye?.DrawFov ?? _setDrawFovOnInitialize;
set
{
if (_eye == null)
{
_setDrawFovOnInitialize = value;
}
else
{
_eye.DrawFov = value;
}
}
}
[ViewVariables]
public MapCoordinates? Position => _eye?.Position;
/// <summary>
/// Updates the Eye of this entity with the transform position. This has to be called every frame to
/// keep the view following the entity.
/// </summary>
public void UpdateEyePosition()
{
if (_eye == null) return;
if (!_entityManager.TryGetComponent(Target, out TransformComponent? xform))
{
xform = _entityManager.GetComponent<TransformComponent>(Owner);
Target = null;
}
_eye.Position = xform.MapPosition;
}
}
}

View File

@@ -0,0 +1,85 @@
using Robust.Client.Graphics;
using Robust.Shared.Animations;
using Robust.Shared.ComponentTrees;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedPointLightComponent))]
public sealed partial class PointLightComponent : SharedPointLightComponent, IComponentTreeEntry<PointLightComponent>
{
public EntityUid? TreeUid { get; set; }
public DynamicTree<ComponentTreeEntry<PointLightComponent>>? Tree { get; set; }
public bool AddToTree => Enabled && !ContainerOccluded;
public bool TreeUpdateQueued { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
[Animatable]
public override Color Color
{
get => _color;
set => base.Color = value;
}
[Access(typeof(PointLightSystem))]
public bool ContainerOccluded;
/// <summary>
/// Determines if the light mask should automatically rotate with the entity. (like a flashlight)
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool MaskAutoRotate
{
get => _maskAutoRotate;
set => _maskAutoRotate = value;
}
/// <summary>
/// Local rotation of the light mask around the center origin
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[Animatable]
public Angle Rotation
{
get => _rotation;
set => _rotation = value;
}
/// <summary>
/// The resource path to the mask texture the light will use.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public string? MaskPath
{
get => _maskPath;
set
{
if (_maskPath?.Equals(value) != false) return;
_maskPath = value;
EntitySystem.Get<PointLightSystem>().UpdateMask(this);
}
}
/// <summary>
/// Set a mask texture that will be applied to the light while rendering.
/// The mask's red channel will be linearly multiplied.p
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public Texture? Mask { get; set; }
[DataField("autoRot")]
private bool _maskAutoRotate;
private Angle _rotation;
[DataField("mask")]
internal string? _maskPath;
}
}

View File

@@ -1,38 +0,0 @@
using Robust.Client.Graphics;
using Robust.Shared.ComponentTrees;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects;
[RegisterComponent]
public sealed partial class PointLightComponent : SharedPointLightComponent, IComponentTreeEntry<PointLightComponent>
{
#region Component Tree
/// <inheritdoc />
[ViewVariables]
public EntityUid? TreeUid { get; set; }
/// <inheritdoc />
[ViewVariables]
public DynamicTree<ComponentTreeEntry<PointLightComponent>>? Tree { get; set; }
/// <inheritdoc />
[ViewVariables]
public bool AddToTree => Enabled && !ContainerOccluded;
/// <inheritdoc />
[ViewVariables]
public bool TreeUpdateQueued { get; set; }
#endregion
/// <summary>
/// Set a mask texture that will be applied to the light while rendering.
/// The mask's red channel will be linearly multiplied.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
internal Texture? Mask;
}

View File

@@ -1,7 +1,5 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
@@ -26,7 +24,7 @@ namespace Robust.Client.GameObjects
int AnimationFrame { get; }
bool AutoAnimated { get; set; }
RsiDirection EffectiveDirection(Angle worldRotation);
RSI.State.Direction EffectiveDirection(Angle worldRotation);
/// <summary>
/// Layer size in pixels.

View File

@@ -12,8 +12,6 @@ using Robust.Shared;
using Robust.Shared.Animations;
using Robust.Shared.ComponentTrees;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -28,8 +26,8 @@ using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using static Robust.Client.ComponentTrees.SpriteTreeSystem;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using RSIDirection = Robust.Client.Graphics.RSI.State.Direction;
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
using Direction = Robust.Shared.Maths.Direction;
namespace Robust.Client.GameObjects
{
@@ -1349,11 +1347,11 @@ namespace Robust.Client.GameObjects
state = GetFallbackState(resourceCache);
}
return state.RsiDirections switch
return state.Directions switch
{
RsiDirectionType.Dir1 => 1,
RsiDirectionType.Dir4 => 4,
RsiDirectionType.Dir8 => 8,
RSI.State.DirectionType.Dir1 => 1,
RSI.State.DirectionType.Dir4 => 4,
RSI.State.DirectionType.Dir8 => 8,
_ => throw new ArgumentOutOfRangeException()
};
}
@@ -1391,7 +1389,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, entities.GetComponent<TransformComponent>(Owner).WorldRotation.ToRsiDirection(RsiDirectionType.Dir8),
Color, NoRotation, entities.GetComponent<TransformComponent>(Owner).WorldRotation.ToRsiDirection(RSI.State.DirectionType.Dir8),
DirectionOverride
);
@@ -1691,7 +1689,7 @@ namespace Robust.Client.GameObjects
int ISpriteLayer.AnimationFrame => AnimationFrame;
public RsiDirection EffectiveDirection(Angle worldRotation)
public RSIDirection EffectiveDirection(Angle worldRotation)
{
if (State == default)
{
@@ -1712,23 +1710,23 @@ namespace Robust.Client.GameObjects
return default;
}
public RsiDirection EffectiveDirection(RSI.State state, Angle worldRotation,
public RSIDirection EffectiveDirection(RSI.State state, Angle worldRotation,
Direction? overrideDirection)
{
if (state.RsiDirections == RsiDirectionType.Dir1)
if (state.Directions == RSI.State.DirectionType.Dir1)
{
return RsiDirection.South;
return RSIDirection.South;
}
else
{
RsiDirection dir;
RSIDirection dir;
if (overrideDirection != null)
{
dir = overrideDirection.Value.Convert(state.RsiDirections);
dir = overrideDirection.Value.Convert(state.Directions);
}
else
{
dir = worldRotation.ToRsiDirection(state.RsiDirections);
dir = worldRotation.ToRsiDirection(state.Directions);
}
return dir.OffsetRsiDir(DirOffset);
@@ -1884,20 +1882,20 @@ namespace Robust.Client.GameObjects
else if (_parent.SnapCardinals && (!_parent.GranularLayersRendering || RenderingStrategy == LayerRenderingStrategy.UseSpriteStrategy)
|| _parent.GranularLayersRendering && RenderingStrategy == LayerRenderingStrategy.SnapToCardinals)
{
DebugTools.Assert(_actualState == null || _actualState.RsiDirections == RsiDirectionType.Dir1);
DebugTools.Assert(_actualState == null || _actualState.Directions == RSI.State.DirectionType.Dir1);
size = new Vector2(longestSide, longestSide);
}
else
{
// Build the bounding box based on how many directions the sprite has
size = (_actualState?.RsiDirections) switch
size = (_actualState?.Directions) switch
{
// If we have four cardinal directions, take the longest side of our texture and square it, then turn that into our bounding box.
// This accounts for all possible rotations.
RsiDirectionType.Dir4 => new Vector2(longestSide, longestSide),
RSI.State.DirectionType.Dir4 => new Vector2(longestSide, longestSide),
// If we have eight directions, find the maximum length of the texture (accounting for rotation), then square it to make
RsiDirectionType.Dir8 => new Vector2(longestRotatedSide, longestRotatedSide),
RSI.State.DirectionType.Dir8 => new Vector2(longestRotatedSide, longestRotatedSide),
// If we have only one direction or an invalid RSI state, create a simple bounding box with the size of the texture.
_ => textureSize
@@ -1931,9 +1929,9 @@ namespace Robust.Client.GameObjects
/// Given the apparent rotation of an entity on screen (world + eye rotation), get layer's matrix for drawing &
/// relevant RSI direction.
/// </summary>
public void GetLayerDrawMatrix(RsiDirection dir, out Matrix3 layerDrawMatrix)
public void GetLayerDrawMatrix(RSIDirection dir, out Matrix3 layerDrawMatrix)
{
if (_parent.NoRotation || dir == RsiDirection.South)
if (_parent.NoRotation || dir == RSIDirection.South)
layerDrawMatrix = LocalMatrix;
else
{
@@ -1958,11 +1956,11 @@ namespace Robust.Client.GameObjects
/// Converts an angle (between 0 and 2pi) to an RSI direction. This will slightly bias the angle to avoid flickering for
/// 4-directional sprites.
/// </summary>
public static RsiDirection GetDirection(RsiDirectionType dirType, Angle angle)
public static RSIDirection GetDirection(RSI.State.DirectionType dirType, Angle angle)
{
if (dirType == RsiDirectionType.Dir1)
return RsiDirection.South;
else if (dirType == RsiDirectionType.Dir8)
if (dirType == RSI.State.DirectionType.Dir1)
return RSIDirection.South;
else if (dirType == RSI.State.DirectionType.Dir8)
return angle.GetDir().Convert(dirType);
// For 4-directional sprites, as entities are often moving & facing diagonally, we will slightly bias the
@@ -1975,10 +1973,10 @@ namespace Robust.Client.GameObjects
return ((int)Math.Round(modTheta / MathHelper.PiOver2) % 4) switch
{
0 => RsiDirection.South,
1 => RsiDirection.East,
2 => RsiDirection.North,
_ => RsiDirection.West,
0 => RSIDirection.South,
1 => RSIDirection.East,
2 => RSIDirection.North,
_ => RSIDirection.West,
};
}
@@ -1990,7 +1988,7 @@ namespace Robust.Client.GameObjects
if (!Visible || Blank)
return;
var dir = _actualState == null ? RsiDirection.South : GetDirection(_actualState.RsiDirections, angle);
var dir = _actualState == null ? RSIDirection.South : GetDirection(_actualState.Directions, angle);
// Set the drawing transform for this layer
GetLayerDrawMatrix(dir, out var layerMatrix);
@@ -2000,7 +1998,7 @@ namespace Robust.Client.GameObjects
// The direction used to draw the sprite can differ from the one that the angle would naively suggest,
// due to direction overrides or offsets.
if (overrideDirection != null && _actualState != null)
dir = overrideDirection.Value.Convert(_actualState.RsiDirections);
dir = overrideDirection.Value.Convert(_actualState.Directions);
dir = dir.OffsetRsiDir(DirOffset);
// Get the correct directional texture from the state, and draw it!
@@ -2023,7 +2021,7 @@ namespace Robust.Client.GameObjects
drawingHandle.UseShader(null);
}
private Texture GetRenderTexture(RSI.State? state, RsiDirection dir)
private Texture GetRenderTexture(RSI.State? state, RSIDirection dir)
{
if (state == null)
return Texture ?? _parent.resourceCache.GetFallback<TextureResource>().Texture;

View File

@@ -1,17 +1,28 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public sealed partial class ClientUserInterfaceComponent : SharedUserInterfaceComponent
{
[ViewVariables]
internal readonly Dictionary<Enum, PrototypeData> _interfaces = new();
[ViewVariables]
public readonly Dictionary<Enum, BoundUserInterface> OpenInterfaces = new();
}
/// <summary>
/// An abstract class to override to implement bound user interfaces.
/// </summary>
public abstract class BoundUserInterface : IDisposable
{
[Dependency] protected readonly IEntityManager EntMan = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
protected readonly SharedUserInterfaceSystem UiSystem;
protected readonly UserInterfaceSystem UiSystem = default!;
public readonly Enum UiKey;
public EntityUid Owner { get; }
@@ -24,7 +35,7 @@ namespace Robust.Shared.GameObjects
protected BoundUserInterface(EntityUid owner, Enum uiKey)
{
IoCManager.InjectDependencies(this);
UiSystem = EntMan.System<SharedUserInterfaceSystem>();
UiSystem = EntMan.System<UserInterfaceSystem>();
Owner = owner;
UiKey = uiKey;
@@ -57,7 +68,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
public void Close()
{
UiSystem.TryCloseUi(_playerManager.LocalSession, Owner, UiKey);
UiSystem.TryCloseUi(Owner, UiKey);
}
/// <summary>
@@ -68,11 +79,6 @@ namespace Robust.Shared.GameObjects
UiSystem.SendUiMessage(this, message);
}
public void SendPredictedMessage(BoundUserInterfaceMessage message)
{
UiSystem.SendPredictedUiMessage(this, message);
}
internal void InternalReceiveMessage(BoundUserInterfaceMessage message)
{
switch (message)

View File

@@ -1,19 +1,23 @@
using System;
using System.Collections.Generic;
using Robust.Client.Animations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using TerraFX.Interop.Windows;
namespace Robust.Client.GameObjects
{
public sealed class AnimationPlayerSystem : EntitySystem
public sealed class AnimationPlayerSystem : EntitySystem, IPostInjectInit
{
private readonly List<Entity<AnimationPlayerComponent>> _activeAnimations = new();
private readonly List<AnimationPlayerComponent> _activeAnimations = new();
private EntityQuery<MetaDataComponent> _metaQuery;
[Dependency] private readonly IComponentFactory _compFact = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private ISawmill _sawmill = default!;
public override void Initialize()
{
@@ -35,22 +39,22 @@ namespace Robust.Client.GameObjects
continue;
}
if (!Update(uid, anim.Comp, frameTime))
if (!Update(uid, anim, frameTime))
{
continue;
}
_activeAnimations.RemoveSwap(i);
i--;
anim.Comp.HasPlayingAnimation = false;
anim.HasPlayingAnimation = false;
}
}
internal void AddComponent(Entity<AnimationPlayerComponent> ent)
internal void AddComponent(AnimationPlayerComponent component)
{
if (ent.Comp.HasPlayingAnimation) return;
_activeAnimations.Add(ent);
ent.Comp.HasPlayingAnimation = true;
if (component.HasPlayingAnimation) return;
_activeAnimations.Add(component);
component.HasPlayingAnimation = true;
}
private bool Update(EntityUid uid, AnimationPlayerComponent component, float frameTime)
@@ -75,6 +79,7 @@ namespace Robust.Client.GameObjects
{
component.PlayingAnimations.Remove(key);
EntityManager.EventBus.RaiseLocalEvent(uid, new AnimationCompletedEvent {Uid = uid, Key = key}, true);
component.AnimationComplete(key);
}
return false;
@@ -85,29 +90,22 @@ namespace Robust.Client.GameObjects
/// </summary>
public void Play(EntityUid uid, Animation animation, string key)
{
var component = EnsureComp<AnimationPlayerComponent>(uid);
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
var component = EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
Play(component, animation, key);
}
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
public void Play(EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key)
{
component ??= EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
Play(component, animation, key);
}
/// <summary>
/// Start playing an animation.
/// </summary>
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
public void Play(AnimationPlayerComponent component, Animation animation, string key)
{
Play(new Entity<AnimationPlayerComponent>(component.Owner, component), animation, key);
}
public void Play(Entity<AnimationPlayerComponent> ent, Animation animation, string key)
{
AddComponent(ent);
AddComponent(component);
var playback = new AnimationPlaybackShared.AnimationPlayback(animation);
#if DEBUG
@@ -119,18 +117,18 @@ namespace Robust.Client.GameObjects
if (compTrack.ComponentType == null)
{
Log.Error("Attempted to play a component animation without any component specified.");
_sawmill.Error($"Attempted to play a component animation without any component specified.");
return;
}
if (!EntityManager.TryGetComponent(ent, compTrack.ComponentType, out var animatedComp))
if (!EntityManager.TryGetComponent(component.Owner, compTrack.ComponentType, out var animatedComp))
{
Log.Error(
$"Attempted to play a component animation, but the entity {ToPrettyString(ent)} does not have the component to be animated: {compTrack.ComponentType}.");
_sawmill.Error(
$"Attempted to play a component animation, but the entity {ToPrettyString(component.Owner)} does not have the component to be animated: {compTrack.ComponentType}.");
return;
}
if (IsClientSide(ent) || !animatedComp.NetSyncEnabled)
if (component.Owner.IsClientSide() || !animatedComp.NetSyncEnabled)
continue;
var reg = _compFact.GetRegistration(animatedComp);
@@ -138,18 +136,12 @@ namespace Robust.Client.GameObjects
// In principle there is nothing wrong with this, as long as the property of the component being
// animated is not part of the networked state and setting it does not dirty the component. Hence only a
// warning in debug mode.
if (reg.NetID != null && compTrack.Property != null)
{
if (animatedComp.GetType().GetProperty(compTrack.Property) is { } property &&
property.HasCustomAttribute<AutoNetworkedFieldAttribute>())
{
Log.Warning($"Playing a component animation on a networked component {reg.Name} belonging to {ToPrettyString(ent)}");
}
}
if (reg.NetID != null)
_sawmill.Warning($"Playing a component animation on a networked component {reg.Name} belonging to {ToPrettyString(component.Owner)}");
}
#endif
ent.Comp.PlayingAnimations.Add(key, playback);
component.PlayingAnimations.Add(key, playback);
}
public bool HasRunningAnimation(EntityUid uid, string key)
@@ -178,19 +170,20 @@ namespace Robust.Client.GameObjects
public void Stop(EntityUid uid, string key)
{
if (!TryComp<AnimationPlayerComponent>(uid, out var player))
return;
if (!TryComp<AnimationPlayerComponent>(uid, out var player)) return;
player.PlayingAnimations.Remove(key);
}
public void Stop(EntityUid uid, AnimationPlayerComponent? component, string key)
{
if (!Resolve(uid, ref component, false))
return;
if (!Resolve(uid, ref component, false)) return;
component.PlayingAnimations.Remove(key);
}
void IPostInjectInit.PostInject()
{
_sawmill = _logManager.GetSawmill("anim");
}
}
public sealed class AnimationCompletedEvent : EntityEventArgs

View File

@@ -17,6 +17,7 @@ using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Random;
using Robust.Shared.Replays;
using Robust.Shared.Threading;
@@ -80,13 +81,9 @@ public sealed class AudioSystem : SharedAudioSystem
#region Event Handlers
private void PlayAudioEntityHandler(PlayAudioEntityMessage ev)
{
var uid = GetEntity(ev.NetEntity);
var coords = GetCoordinates(ev.Coordinates);
var fallback = GetCoordinates(ev.FallbackCoordinates);
var stream = EntityManager.EntityExists(uid)
? (PlayingStream?) Play(ev.FileName, uid, fallback, ev.AudioParams, false)
: (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false);
var stream = EntityManager.EntityExists(ev.EntityUid)
? (PlayingStream?) Play(ev.FileName, ev.EntityUid, ev.FallbackCoordinates, ev.AudioParams, false)
: (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams, false);
if (stream != null)
stream.NetIdentifier = ev.Identifier;
@@ -101,10 +98,7 @@ public sealed class AudioSystem : SharedAudioSystem
private void PlayAudioPositionalHandler(PlayAudioPositionalMessage ev)
{
var coords = GetCoordinates(ev.Coordinates);
var fallback = GetCoordinates(ev.FallbackCoordinates);
var stream = (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false);
var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams, false);
if (stream != null)
stream.NetIdentifier = ev.Identifier;
}
@@ -389,8 +383,8 @@ public sealed class AudioSystem : SharedAudioSystem
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
{
FileName = filename,
NetEntity = GetNetEntity(entity),
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates) ?? default,
EntityUid = entity,
FallbackCoordinates = fallbackCoordinates ?? default,
AudioParams = audioParams ?? AudioParams.Default
});
}
@@ -443,8 +437,8 @@ public sealed class AudioSystem : SharedAudioSystem
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = GetNetCoordinates(coordinates),
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates),
Coordinates = coordinates,
FallbackCoordinates = fallbackCoordinates,
AudioParams = audioParams ?? AudioParams.Default
});
}

View File

@@ -1,4 +1,3 @@
using System;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
@@ -6,11 +5,13 @@ using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Serialization;
using Robust.Shared.Physics.Components;
using static Robust.Shared.Containers.ContainerManagerComponent;
namespace Robust.Client.GameObjects
@@ -22,20 +23,14 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[Dependency] private readonly PointLightSystem _lightSys = default!;
private EntityQuery<PointLightComponent> _pointLightQuery;
private EntityQuery<SpriteComponent> _spriteQuery;
private readonly HashSet<EntityUid> _updateQueue = new();
public readonly Dictionary<NetEntity, BaseContainer> ExpectedEntities = new();
public readonly Dictionary<EntityUid, IContainer> ExpectedEntities = new();
public override void Initialize()
{
base.Initialize();
_pointLightQuery = GetEntityQuery<PointLightComponent>();
_spriteQuery = GetEntityQuery<SpriteComponent>();
EntityManager.EntityInitialized += HandleEntityInitialized;
SubscribeLocalEvent<ContainerManagerComponent, ComponentHandleState>(HandleComponentState);
@@ -48,18 +43,20 @@ namespace Robust.Client.GameObjects
base.Shutdown();
}
protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing)
protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing)
{
var netEntity = GetNetEntity(missing);
DebugTools.Assert(ExpectedEntities.TryGetValue(netEntity, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(netEntity));
DebugTools.Assert(ExpectedEntities.TryGetValue(missing, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(missing));
}
private void HandleEntityInitialized(EntityUid uid)
{
if (!RemoveExpectedEntity(GetNetEntity(uid), out var container))
if (!RemoveExpectedEntity(uid, out var container))
return;
container.Insert(uid, EntityManager, transform: TransformQuery.GetComponent(uid), meta: MetaQuery.GetComponent(uid));
if (container.Deleted)
return;
container.Insert(uid);
}
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
@@ -67,24 +64,23 @@ namespace Robust.Client.GameObjects
if (args.Current is not ContainerManagerComponentState cast)
return;
var xform = TransformQuery.GetComponent(uid);
var metaQuery = GetEntityQuery<MetaDataComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(uid);
// Delete now-gone containers.
var toDelete = new ValueList<string>();
foreach (var (id, container) in component.Containers)
{
if (cast.Containers.ContainsKey(id))
{
DebugTools.Assert(cast.Containers[id].ContainerType == container.GetType().Name);
continue;
}
foreach (var entity in container.ContainedEntities.ToArray())
{
container.Remove(entity,
EntityManager,
TransformQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
xformQuery.GetComponent(entity),
metaQuery.GetComponent(entity),
force: true,
reparent: false);
@@ -102,32 +98,26 @@ namespace Robust.Client.GameObjects
// Add new containers and update existing contents.
foreach (var (id, data) in cast.Containers)
foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.Containers.Values)
{
if (!component.Containers.TryGetValue(id, out var container))
{
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
container.Init(id, uid, component);
container = ContainerFactory(component, containerType, id);
component.Containers.Add(id, container);
}
DebugTools.Assert(container.ID == id);
container.ShowContents = data.ShowContents;
container.OccludesLight = data.OccludesLight;
// sync show flag
container.ShowContents = showEnts;
container.OccludesLight = occludesLight;
// Remove gone entities.
var toRemove = new ValueList<EntityUid>();
DebugTools.Assert(!container.Contains(EntityUid.Invalid));
var stateNetEnts = data.ContainedEntities;
var stateEnts = GetEntityArray(stateNetEnts); // No need to ensure entities.
foreach (var entity in container.ContainedEntities)
{
if (!stateEnts.Contains(entity))
if (!entityUids.Contains(entity))
{
toRemove.Add(entity);
}
}
foreach (var entity in toRemove)
@@ -135,8 +125,8 @@ namespace Robust.Client.GameObjects
container.Remove(
entity,
EntityManager,
TransformQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
xformQuery.GetComponent(entity),
metaQuery.GetComponent(entity),
force: true,
reparent: false);
@@ -144,11 +134,13 @@ namespace Robust.Client.GameObjects
}
// Remove entities that were expected, but have been removed from the container.
var removedExpected = new ValueList<NetEntity>();
foreach (var netEntity in container.ExpectedEntities)
var removedExpected = new ValueList<EntityUid>();
foreach (var entityUid in container.ExpectedEntities)
{
if (!stateNetEnts.Contains(netEntity))
removedExpected.Add(netEntity);
if (!entityUids.Contains(entityUid))
{
removedExpected.Add(entityUid);
}
}
foreach (var entityUid in removedExpected)
@@ -157,20 +149,14 @@ namespace Robust.Client.GameObjects
}
// Add new entities.
for (var i = 0; i < stateNetEnts.Length; i++)
foreach (var entity in entityUids)
{
var entity = stateEnts[i];
var netEnt = stateNetEnts[i];
if (!entity.IsValid())
if (!EntityManager.TryGetComponent(entity, out MetaDataComponent? meta))
{
DebugTools.Assert(netEnt.IsValid());
AddExpectedEntity(netEnt, container);
AddExpectedEntity(entity, container);
continue;
}
var meta = MetaData(entity);
DebugTools.Assert(meta.NetEntity == netEnt);
// If an entity is currently in the shadow realm, it means we probably left PVS and are now getting
// back into range. We do not want to directly insert this entity, as IF the container and entity
// transform states did not get sent simultaneously, the entity's transform will be modified by the
@@ -180,18 +166,18 @@ namespace Robust.Client.GameObjects
// containers/players.
if ((meta.Flags & MetaDataFlags.Detached) != 0)
{
AddExpectedEntity(netEnt, container);
AddExpectedEntity(entity, container);
continue;
}
if (container.Contains(entity))
continue;
RemoveExpectedEntity(netEnt, out _);
RemoveExpectedEntity(entity, out _);
container.Insert(entity, EntityManager,
TransformQuery.GetComponent(entity),
xformQuery.GetComponent(entity),
xform,
MetaQuery.GetComponent(entity),
metaQuery.GetComponent(entity),
force: true);
DebugTools.Assert(container.Contains(entity));
@@ -212,7 +198,7 @@ namespace Robust.Client.GameObjects
if (message.OldParent != null && message.OldParent.Value.IsValid())
return;
if (!RemoveExpectedEntity(GetNetEntity(message.Entity), out var container))
if (!RemoveExpectedEntity(message.Entity, out var container))
return;
if (xform.ParentUid != container.Owner)
@@ -222,69 +208,84 @@ namespace Robust.Client.GameObjects
return;
}
if (container.Deleted)
return;
container.Insert(message.Entity, EntityManager);
}
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id)
{
#if DEBUG
var uid = GetEntity(netEntity);
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found.");
if (TryComp<MetaDataComponent>(uid, out var meta))
{
DebugTools.Assert((meta.Flags & ( MetaDataFlags.Detached | MetaDataFlags.InContainer) ) == MetaDataFlags.Detached,
$"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container.");
}
#endif
var newContainer = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
newContainer.ID = id;
newContainer.Manager = component;
return newContainer;
}
if (!ExpectedEntities.TryAdd(netEntity, container))
public void AddExpectedEntity(EntityUid uid, IContainer container)
{
DebugTools.Assert(!TryComp(uid, out MetaDataComponent? meta) ||
(meta.Flags & ( MetaDataFlags.Detached | MetaDataFlags.InContainer) ) == MetaDataFlags.Detached,
$"Adding entity {ToPrettyString(uid)} to list of expected entities for container {container.ID} in {ToPrettyString(container.Owner)}, despite it already being in a container.");
if (!ExpectedEntities.TryAdd(uid, container))
{
// It is possible that we were expecting this entity in one container, but it has now moved to another
// container, and this entity's state is just being applied before the old container is getting updated.
var oldContainer = ExpectedEntities[netEntity];
ExpectedEntities[netEntity] = container;
DebugTools.Assert(oldContainer.ExpectedEntities.Contains(netEntity),
$"Entity {netEntity} is expected, but not expected in the given container? Container: {oldContainer.ID} in {ToPrettyString(oldContainer.Owner)}");
oldContainer.ExpectedEntities.Remove(netEntity);
var oldContainer = ExpectedEntities[uid];
ExpectedEntities[uid] = container;
DebugTools.Assert(oldContainer.ExpectedEntities.Contains(uid),
$"Entity {ToPrettyString(uid)} is expected, but not expected in the given container? Container: {oldContainer.ID} in {ToPrettyString(oldContainer.Owner)}");
oldContainer.ExpectedEntities.Remove(uid);
}
DebugTools.Assert(!container.ExpectedEntities.Contains(netEntity),
$"Contained entity {netEntity} was not yet expected by the system, but was already expected by the container: {container.ID} in {ToPrettyString(container.Owner)}");
container.ExpectedEntities.Add(netEntity);
DebugTools.Assert(!container.ExpectedEntities.Contains(uid),
$"Contained entity {ToPrettyString(uid)} was not yet expected by the system, but was already expected by the container: {container.ID} in {ToPrettyString(container.Owner)}");
container.ExpectedEntities.Add(uid);
}
public bool RemoveExpectedEntity(NetEntity netEntity, [NotNullWhen(true)] out BaseContainer? container)
public bool RemoveExpectedEntity(EntityUid uid, [NotNullWhen(true)] out IContainer? container)
{
if (!ExpectedEntities.Remove(netEntity, out container))
if (!ExpectedEntities.Remove(uid, out container))
return false;
DebugTools.Assert(container.ExpectedEntities.Contains(netEntity),
$"While removing expected contained entity {ToPrettyString(netEntity)}, the entity was missing from the container expected set. Container: {container.ID} in {ToPrettyString(container.Owner)}");
container.ExpectedEntities.Remove(netEntity);
DebugTools.Assert(container.ExpectedEntities.Contains(uid),
$"While removing expected contained entity {ToPrettyString(uid)}, the entity was missing from the container expected set. Container: {container.ID} in {ToPrettyString(container.Owner)}");
container.ExpectedEntities.Remove(uid);
return true;
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
var pointQuery = EntityManager.GetEntityQuery<PointLightComponent>();
var spriteQuery = EntityManager.GetEntityQuery<SpriteComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
foreach (var toUpdate in _updateQueue)
{
if (Deleted(toUpdate))
continue;
UpdateEntityRecursively(toUpdate);
UpdateEntityRecursively(toUpdate, xformQuery, pointQuery, spriteQuery);
}
_updateQueue.Clear();
}
private void UpdateEntityRecursively(EntityUid entity)
private void UpdateEntityRecursively(
EntityUid entity,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery)
{
// Recursively go up parents and containers to see whether both sprites and lights need to be occluded
// Could maybe optimise this more by checking nearest parent that has sprite / light and whether it's container
// occluded but this probably isn't a big perf issue.
var xform = TransformQuery.GetComponent(entity);
var xform = xformQuery.GetComponent(entity);
var parent = xform.ParentUid;
var child = entity;
var spriteOccluded = false;
@@ -292,7 +293,7 @@ namespace Robust.Client.GameObjects
while (parent.IsValid() && (!spriteOccluded || !lightOccluded))
{
var parentXform = TransformQuery.GetComponent(parent);
var parentXform = xformQuery.GetComponent(parent);
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
{
spriteOccluded = spriteOccluded || !container.ShowContents;
@@ -307,21 +308,24 @@ namespace Robust.Client.GameObjects
// This is the CBT bit.
// The issue is we need to go through the children and re-check whether they are or are not contained.
// if they are contained then the occlusion values may need updating for all those children
UpdateEntity(entity, xform, spriteOccluded, lightOccluded);
UpdateEntity(entity, xform, xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
}
private void UpdateEntity(
EntityUid entity,
TransformComponent xform,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery,
bool spriteOccluded,
bool lightOccluded)
{
if (_spriteQuery.TryGetComponent(entity, out var sprite))
if (spriteQuery.TryGetComponent(entity, out var sprite))
{
sprite.ContainerOccluded = spriteOccluded;
}
if (_pointLightQuery.TryGetComponent(entity, out var light))
if (pointQuery.TryGetComponent(entity, out var light))
_lightSys.SetContainerOccluded(entity, lightOccluded, light);
var childEnumerator = xform.ChildEnumerator;
@@ -342,14 +346,14 @@ namespace Robust.Client.GameObjects
childLightOccluded = childLightOccluded || container.OccludesLight;
}
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), childSpriteOccluded, childLightOccluded);
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, childSpriteOccluded, childLightOccluded);
}
}
else
{
while (childEnumerator.MoveNext(out var child))
{
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), spriteOccluded, lightOccluded);
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
}
}
}

View File

@@ -1,8 +1,7 @@
using Robust.Client.Graphics;
using Robust.Client.Physics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Player;
namespace Robust.Client.GameObjects;
@@ -14,61 +13,48 @@ public sealed class EyeSystem : SharedEyeSystem
{
base.Initialize();
SubscribeLocalEvent<EyeComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<EyeComponent, LocalPlayerDetachedEvent>(OnEyeDetached);
SubscribeLocalEvent<EyeComponent, LocalPlayerAttachedEvent>(OnEyeAttached);
SubscribeLocalEvent<EyeComponent, AfterAutoHandleStateEvent>(OnEyeAutoState);
// Make sure this runs *after* entities have been moved by interpolation and movement.
UpdatesAfter.Add(typeof(TransformSystem));
UpdatesAfter.Add(typeof(PhysicsSystem));
}
private void OnEyeAutoState(EntityUid uid, EyeComponent component, ref AfterAutoHandleStateEvent args)
{
UpdateEye((uid, component));
}
private void OnEyeAttached(EntityUid uid, EyeComponent component, LocalPlayerAttachedEvent args)
{
UpdateEye((uid, component));
_eyeManager.CurrentEye = component.Eye;
var ev = new EyeAttachedEvent(uid, component);
RaiseLocalEvent(uid, ref ev, true);
}
private void OnEyeDetached(EntityUid uid, EyeComponent component, LocalPlayerDetachedEvent args)
{
_eyeManager.ClearCurrentEye();
SubscribeLocalEvent<EyeComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<EyeComponent, ComponentHandleState>(OnHandleState);
}
private void OnInit(EntityUid uid, EyeComponent component, ComponentInit args)
{
UpdateEye((uid, component));
}
/// <inheritdoc />
public override void FrameUpdate(float frameTime)
{
var query = AllEntityQuery<EyeComponent>();
while (query.MoveNext(out var uid, out var eyeComponent))
component._eye = new Eye
{
if (eyeComponent.Eye == null)
continue;
Position = Transform(uid).MapPosition,
Zoom = component._setZoomOnInitialize,
DrawFov = component._setDrawFovOnInitialize
};
if (!TryComp<TransformComponent>(eyeComponent.Target, out var xform))
if ((_eyeManager.CurrentEye == component._eye) != component._setCurrentOnInitialize)
{
if (component._setCurrentOnInitialize)
{
xform = Transform(uid);
eyeComponent.Target = null;
_eyeManager.ClearCurrentEye();
}
else
{
_eyeManager.CurrentEye = component._eye;
}
eyeComponent.Eye.Position = xform.MapPosition;
}
}
}
/// <summary>
/// Raised on an entity when it is attached to one with an <see cref="EyeComponent"/>
/// </summary>
[ByRefEvent]
public readonly record struct EyeAttachedEvent(EntityUid Entity, EyeComponent Component);
private void OnRemove(EntityUid uid, EyeComponent component, ComponentRemove args)
{
component.Current = false;
}
private void OnHandleState(EntityUid uid, EyeComponent component, ref ComponentHandleState args)
{
if (args.Current is not EyeComponentState state)
{
return;
}
component.DrawFov = state.DrawFov;
// TODO: Should be a way for content to override lerping and lerp the zoom
component.Zoom = state.Zoom;
component.Offset = state.Offset;
component.VisibilityMask = state.VisibilityMask;
}
}

View File

@@ -0,0 +1,43 @@
using System;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Physics;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
#nullable enable
namespace Robust.Client.GameObjects
{
/// <summary>
/// Updates the position of every Eye every frame, so that the camera follows the player around.
/// </summary>
[UsedImplicitly]
public sealed class EyeUpdateSystem : EntitySystem
{
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
// Make sure this runs *after* entities have been moved by interpolation and movement.
UpdatesAfter.Add(typeof(TransformSystem));
UpdatesAfter.Add(typeof(PhysicsSystem));
}
/// <inheritdoc />
public override void FrameUpdate(float frameTime)
{
foreach (var eyeComponent in EntityManager.EntityQuery<EyeComponent>(true))
{
eyeComponent.UpdateEyePosition();
}
}
}
}

View File

@@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
@@ -59,8 +58,6 @@ namespace Robust.Client.GameObjects
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private List<Entity<MapGridComponent>> _grids = new();
public GridChunkBoundsOverlay(IEntityManager entManager, IEyeManager eyeManager, IMapManager mapManager)
{
_entityManager = entManager;
@@ -74,15 +71,13 @@ namespace Robust.Client.GameObjects
var viewport = args.WorldBounds;
var worldHandle = args.WorldHandle;
_grids.Clear();
_mapManager.FindGridsIntersecting(currentMap, viewport, ref _grids);
foreach (var grid in _grids)
foreach (var grid in _mapManager.FindGridsIntersecting(currentMap, viewport))
{
var worldMatrix = _entityManager.GetComponent<TransformComponent>(grid).WorldMatrix;
var worldMatrix = _entityManager.GetComponent<TransformComponent>(grid.Owner).WorldMatrix;
worldHandle.SetTransform(worldMatrix);
var transform = new Transform(Vector2.Zero, Angle.Zero);
var chunkEnumerator = grid.Comp.GetMapChunks(viewport);
var chunkEnumerator = grid.GetMapChunks(viewport);
while (chunkEnumerator.MoveNext(out var chunk))
{

View File

@@ -3,13 +3,16 @@ using System.Numerics;
using Robust.Client.GameStates;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -49,7 +52,7 @@ namespace Robust.Client.GameObjects
/// <param name="message">Arguments for this event.</param>
/// <param name="replay">if true, current cmd state will not be checked or updated - use this for "replaying" an
/// old input that was saved or buffered until further processing could be done</param>
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, IFullInputCmdMessage message, bool replay = false)
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
{
#if DEBUG
@@ -75,27 +78,14 @@ namespace Robust.Client.GameObjects
continue;
// local handlers can block sending over the network.
if (handler.HandleCmdMessage(EntityManager, session, message))
if (handler.HandleCmdMessage(session, message))
{
return true;
}
}
// send it off to the server
var clientMsg = (ClientFullInputCmdMessage)message;
var fullMsg = new FullInputCmdMessage(
clientMsg.Tick,
clientMsg.SubTick,
(int)clientMsg.InputSequence,
clientMsg.InputFunctionId,
clientMsg.State,
GetNetCoordinates(clientMsg.Coordinates),
clientMsg.ScreenCoordinates)
{
Uid = GetNetEntity(clientMsg.Uid)
};
DispatchInputCommand(clientMsg, fullMsg);
DispatchInputCommand(message);
return false;
}
@@ -103,7 +93,7 @@ namespace Robust.Client.GameObjects
/// Handle a predicted input command.
/// </summary>
/// <param name="inputCmd">Input command to handle as predicted.</param>
public void PredictInputCommand(IFullInputCmdMessage inputCmd)
public void PredictInputCommand(FullInputCmdMessage inputCmd)
{
DebugTools.AssertNotNull(_playerManager.LocalPlayer);
@@ -113,22 +103,21 @@ namespace Robust.Client.GameObjects
var session = _playerManager.LocalPlayer!.Session;
foreach (var handler in BindRegistry.GetHandlers(keyFunc))
{
if (handler.HandleCmdMessage(EntityManager, session, inputCmd))
break;
if (handler.HandleCmdMessage(session, inputCmd)) break;
}
Predicted = false;
}
private void DispatchInputCommand(ClientFullInputCmdMessage clientMsg, FullInputCmdMessage message)
private void DispatchInputCommand(FullInputCmdMessage message)
{
_stateManager.InputCommandDispatched(clientMsg, message);
_stateManager.InputCommandDispatched(message);
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, message.InputSequence);
}
public override void Initialize()
{
SubscribeLocalEvent<LocalPlayerAttachedEvent>(OnAttachedEntityChanged);
SubscribeLocalEvent<PlayerAttachSysMessage>(OnAttachedEntityChanged);
_conHost.RegisterCommand("incmd",
"Inserts an input command into the simulation",
@@ -163,16 +152,16 @@ namespace Robust.Client.GameObjects
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
coords, new ScreenCoordinates(0, 0, default), EntityUid.Invalid);
HandleInputCommand(localPlayer.Session, keyFunction, message);
}
private void OnAttachedEntityChanged(LocalPlayerAttachedEvent message)
private void OnAttachedEntityChanged(PlayerAttachSysMessage message)
{
if (message.Entity != default) // attach
if (message.AttachedEntity != default) // attach
{
SetEntityContextActive(_inputManager, message.Entity);
SetEntityContextActive(_inputManager, message.AttachedEntity);
}
else // detach
{
@@ -224,4 +213,44 @@ namespace Robust.Client.GameObjects
_sawmillInputContext = _logManager.GetSawmill("input.context");
}
}
/// <summary>
/// Entity system message that is raised when the player changes attached entities.
/// </summary>
public sealed class PlayerAttachSysMessage : EntityEventArgs
{
/// <summary>
/// New entity the player is attached to.
/// </summary>
public EntityUid AttachedEntity { get; }
/// <summary>
/// Creates a new instance of <see cref="PlayerAttachSysMessage"/>.
/// </summary>
/// <param name="attachedEntity">New entity the player is attached to.</param>
public PlayerAttachSysMessage(EntityUid attachedEntity)
{
AttachedEntity = attachedEntity;
}
}
public sealed class PlayerAttachedEvent : EntityEventArgs
{
public PlayerAttachedEvent(EntityUid entity)
{
Entity = entity;
}
public EntityUid Entity { get; }
}
public sealed class PlayerDetachedEvent : EntityEventArgs
{
public PlayerDetachedEvent(EntityUid entity)
{
Entity = entity;
}
public EntityUid Entity { get; }
}
}

View File

@@ -1,9 +1,12 @@
using Robust.Client.Graphics;
using Robust.Client.Map;
using Robust.Client.Physics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Client.GameObjects
{
@@ -25,5 +28,10 @@ namespace Robust.Client.GameObjects
base.Shutdown();
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
}
}

View File

@@ -1,8 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using Robust.Client.ComponentTrees;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -17,108 +15,59 @@ namespace Robust.Client.GameObjects
{
base.Initialize();
SubscribeLocalEvent<PointLightComponent, ComponentInit>(HandleInit);
SubscribeLocalEvent<PointLightComponent, ComponentHandleState>(OnLightHandleState);
}
private void OnLightHandleState(EntityUid uid, PointLightComponent component, ref ComponentHandleState args)
{
if (args.Current is not PointLightComponentState state)
return;
component.Enabled = state.Enabled;
component.Offset = state.Offset;
component.Softness = state.Softness;
component.CastShadows = state.CastShadows;
component.Energy = state.Energy;
component.Radius = state.Radius;
component.Color = state.Color;
_lightTree.QueueTreeUpdate(uid, component);
}
public override SharedPointLightComponent EnsureLight(EntityUid uid)
{
return EnsureComp<PointLightComponent>(uid);
}
public override bool ResolveLight(EntityUid uid, [NotNullWhen(true)] ref SharedPointLightComponent? component)
{
if (component is not null)
return true;
TryComp<PointLightComponent>(uid, out var comp);
component = comp;
return component != null;
}
public override bool TryGetLight(EntityUid uid, [NotNullWhen(true)] out SharedPointLightComponent? component)
{
if (TryComp<PointLightComponent>(uid, out var comp))
{
component = comp;
return true;
}
component = null;
return false;
}
public override bool RemoveLightDeferred(EntityUid uid)
{
return RemCompDeferred<PointLightComponent>(uid);
}
private void HandleInit(EntityUid uid, PointLightComponent component, ComponentInit args)
{
SetMask(component.MaskPath, component);
UpdateMask(component);
}
public void SetMask(string? maskPath, PointLightComponent component)
internal void UpdateMask(PointLightComponent component)
{
if (maskPath is not null)
component.Mask = _resourceCache.GetResource<TextureResource>(maskPath);
if (component._maskPath is not null)
component.Mask = _resourceCache.GetResource<TextureResource>(component._maskPath);
else
component.Mask = null;
}
#region Setters
public void SetContainerOccluded(EntityUid uid, bool occluded, SharedPointLightComponent? comp = null)
public void SetContainerOccluded(EntityUid uid, bool occluded, PointLightComponent? comp = null)
{
if (!ResolveLight(uid, ref comp) || occluded == comp.ContainerOccluded || comp is not PointLightComponent clientComp)
if (!Resolve(uid, ref comp) || occluded == comp.ContainerOccluded)
return;
comp.ContainerOccluded = occluded;
Dirty(uid, comp);
if (comp.Enabled)
_lightTree.QueueTreeUpdate(uid, clientComp);
_lightTree.QueueTreeUpdate(uid, comp);
}
public override void SetEnabled(EntityUid uid, bool enabled, SharedPointLightComponent? comp = null)
{
if (!ResolveLight(uid, ref comp) || enabled == comp.Enabled || comp is not PointLightComponent clientComp)
if (!Resolve(uid, ref comp) || enabled == comp.Enabled)
return;
comp.Enabled = enabled;
comp._enabled = enabled;
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
Dirty(uid, comp);
if (!comp.ContainerOccluded)
_lightTree.QueueTreeUpdate(uid, clientComp);
var cast = (PointLightComponent)comp;
if (!cast.ContainerOccluded)
_lightTree.QueueTreeUpdate(uid, cast);
}
public override void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null)
{
if (!ResolveLight(uid, ref comp) || MathHelper.CloseToPercent(radius, comp.Radius) ||
comp is not PointLightComponent clientComp)
if (!Resolve(uid, ref comp) || MathHelper.CloseToPercent(radius, comp.Radius))
return;
comp.Radius = radius;
comp._radius = radius;
Dirty(uid, comp);
if (clientComp.TreeUid != null)
_lightTree.QueueTreeUpdate(uid, clientComp);
var cast = (PointLightComponent)comp;
if (cast.TreeUid != null)
_lightTree.QueueTreeUpdate(uid, cast);
}
#endregion
}

View File

@@ -4,7 +4,6 @@ using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations;

View File

@@ -1,16 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using JetBrains.Annotations;
using Robust.Client.ComponentTrees;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -18,7 +15,6 @@ using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Client.GameObjects.SpriteComponent;
namespace Robust.Client.GameObjects
@@ -187,48 +183,6 @@ namespace Robust.Client.GameObjects
{
_queuedFrameUpdate.Add(uid);
}
/// <summary>
/// Gets the specified frame for this sprite at the specified time.
/// </summary>
public Texture GetFrame(SpriteSpecifier spriteSpec, TimeSpan curTime)
{
Texture? sprite = null;
switch (spriteSpec)
{
case SpriteSpecifier.Rsi rsi:
var rsiActual = _resourceCache.GetResource<RSIResource>(rsi.RsiPath).RSI;
rsiActual.TryGetState(rsi.RsiState, out var state);
var frames = state!.GetFrames(RsiDirection.South);
var delays = state.GetDelays();
var totalDelay = delays.Sum();
var time = curTime.TotalSeconds % totalDelay;
var delaySum = 0f;
for (var i = 0; i < delays.Length; i++)
{
var delay = delays[i];
delaySum += delay;
if (time > delaySum)
continue;
sprite = frames[i];
break;
}
sprite ??= Frame0(spriteSpec);
break;
case SpriteSpecifier.Texture texture:
sprite = texture.GetTexture(_resourceCache);
break;
default:
throw new NotImplementedException();
}
return sprite;
}
}
/// <summary>

View File

@@ -1,41 +1,51 @@
using System.Numerics;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects;
public sealed partial class TransformSystem
{
public override void SetLocalPosition(EntityUid uid, Vector2 value, TransformComponent? xform = null)
public override void SetLocalPosition(TransformComponent xform, Vector2 value)
{
if (!XformQuery.Resolve(uid, ref xform))
return;
xform.PrevPosition = xform._localPosition;
xform.NextPosition = value;
ActivateLerp(uid, xform);
base.SetLocalPosition(uid, value, xform);
xform.LerpParent = xform.ParentUid;
base.SetLocalPosition(xform, value);
ActivateLerp(xform);
}
public override void SetLocalRotation(EntityUid uid, Angle value, TransformComponent? xform = null)
public override void SetLocalPositionNoLerp(TransformComponent xform, Vector2 value)
{
if (!XformQuery.Resolve(uid, ref xform))
return;
xform.NextRotation = value;
ActivateLerp(uid, xform);
base.SetLocalRotation(uid, value, xform);
xform.NextPosition = null;
xform.LerpParent = EntityUid.Invalid;
base.SetLocalPositionNoLerp(xform, value);
}
public override void SetLocalPositionRotation(EntityUid uid, Vector2 pos, Angle rot, TransformComponent? xform = null)
public override void SetLocalRotationNoLerp(TransformComponent xform, Angle angle)
{
if (!XformQuery.Resolve(uid, ref xform))
return;
xform.NextRotation = null;
xform.LerpParent = EntityUid.Invalid;
base.SetLocalRotationNoLerp(xform, angle);
}
public override void SetLocalRotation(TransformComponent xform, Angle angle)
{
xform.PrevRotation = xform._localRotation;
xform.NextRotation = angle;
xform.LerpParent = xform.ParentUid;
base.SetLocalRotation(xform, angle);
ActivateLerp(xform);
}
public override void SetLocalPositionRotation(TransformComponent xform, Vector2 pos, Angle rot)
{
xform.PrevPosition = xform._localPosition;
xform.NextPosition = pos;
xform.PrevRotation = xform._localRotation;
xform.NextRotation = rot;
ActivateLerp(uid, xform);
base.SetLocalPositionRotation(uid, pos, rot, xform);
xform.LerpParent = xform.ParentUid;
base.SetLocalPositionRotation(xform, pos, rot);
ActivateLerp(xform);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using JetBrains.Annotations;
@@ -24,15 +25,20 @@ namespace Robust.Client.GameObjects
private const float MinInterpolationDistance = 0.001f;
private const float MinInterpolationDistanceSquared = MinInterpolationDistance * MinInterpolationDistance;
private const double MinInterpolationAngle = Math.PI / 720;
// 45 degrees.
private const double MaxInterpolationAngle = Math.PI / 4;
[Dependency] private readonly IGameTiming _gameTiming = default!;
// Only keep track of transforms actively lerping.
// Much faster than iterating 3000+ transforms every frame.
[ViewVariables] private readonly List<Entity<TransformComponent>> _lerpingTransforms = new();
[ViewVariables] private readonly List<TransformComponent> _lerpingTransforms = new();
public void Reset()
{
foreach (var (_, xform) in _lerpingTransforms)
foreach (var xform in _lerpingTransforms)
{
xform.ActivelyLerping = false;
xform.NextPosition = null;
@@ -42,78 +48,21 @@ namespace Robust.Client.GameObjects
_lerpingTransforms.Clear();
}
public override void ActivateLerp(EntityUid uid, TransformComponent xform)
public override void ActivateLerp(TransformComponent xform)
{
// This lerping logic is pretty convoluted and generally assumes that the client does not mispredict.
// A more foolproof solution would be to just cache the coordinates at which any given entity was most
// recently rendered and using that as the lerp origin. However that'd require enumerating over all entities
// every tick which is pretty icky.
// The general considerations are:
// - If the client receives a server state for an entity moving from a->b and predicts nothing else, then it
// should show the entity lerping.
// - If the client predicts an entity will move while already lerping due to a state-application, it should
// clear the state's lerp, under the assumption that the client predicted the state and already rendered
// the entity in the state's final position.
// - If the client predicts that an entity moves, then we only lerp if this is the first time that the tick
// was predicted. I.e., we assume the entity was already rendered in the final position that was
// previously predicted.
// - If the client predicts that an entity should lerp twice in the same tick, then we need to combine them.
// I.e. moving from a->b then b->c, the client should lerp from a->c.
// If the client predicts an entity moves while already lerping, it should clear the
// predict a->b, lerp a->b
// predicted a->b, then predict b->c. Lerp b->c
// predicted a->b, then predict b->c. Lerp b->c
// predicted a->b, predicted b->c, then predict c->d. Lerp c->d
// server state a->b, then predicted b->c, lerp b->c
// server state a->b, then predicted b->c, then predict d, lerp b->c
if (_gameTiming.ApplyingState)
{
if (xform.ActivelyLerping)
{
// This should not happen, but can happen if some bad component state application code modifies an entity's coordinates.
Log.Error($"Entity {(ToPrettyString(uid))} tried to lerp twice while applying component states.");
return;
}
_lerpingTransforms.Add((uid, xform));
xform.ActivelyLerping = true;
xform.PredictedLerp = false;
xform.LerpParent = xform.ParentUid;
xform.PrevRotation = xform._localRotation;
xform.PrevPosition = xform._localPosition;
xform.LastLerp = _gameTiming.CurTick;
if (xform.ActivelyLerping)
return;
}
xform.LastLerp = _gameTiming.CurTick;
if (!_gameTiming.IsFirstTimePredicted)
{
xform.ActivelyLerping = false;
return;
}
xform.ActivelyLerping = true;
_lerpingTransforms.Add(xform);
}
if (!xform.ActivelyLerping)
{
_lerpingTransforms.Add((uid, xform));
xform.ActivelyLerping = true;
xform.PredictedLerp = true;
xform.PrevRotation = xform._localRotation;
xform.PrevPosition = xform._localPosition;
xform.LerpParent = xform.ParentUid;
return;
}
if (!xform.PredictedLerp || xform.LerpParent != xform.ParentUid)
{
// Existing lerp was not due to prediction, but due to state application. That lerp should already
// have been rendered, so we will start a new lerp from the current position.
xform.PrevRotation = xform._localRotation;
xform.PrevPosition = xform._localPosition;
xform.LerpParent = xform.ParentUid;
}
public override void DeactivateLerp(TransformComponent component)
{
// this should cause the lerp to do nothing
component.NextPosition = null;
component.NextRotation = null;
component.LerpParent = EntityUid.Invalid;
}
public override void FrameUpdate(float frameTime)
@@ -124,13 +73,12 @@ namespace Robust.Client.GameObjects
for (var i = 0; i < _lerpingTransforms.Count; i++)
{
var (uid, transform) = _lerpingTransforms[i];
var transform = _lerpingTransforms[i];
var found = false;
// Only lerp if parent didn't change.
// E.g. entering lockers would do it.
if (transform.ActivelyLerping
&& transform.LerpParent == transform.ParentUid
if (transform.LerpParent == transform.ParentUid
&& transform.ParentUid.IsValid()
&& !transform.Deleted)
{
@@ -142,7 +90,8 @@ namespace Robust.Client.GameObjects
if (distance is > MinInterpolationDistanceSquared and < MaxInterpolationDistanceSquared)
{
SetLocalPositionNoLerp(uid, Vector2.Lerp(lerpSource, lerpDest, step), transform);
transform.LocalPosition = Vector2.Lerp(lerpSource, lerpDest, step);
// Setting LocalPosition clears LerpPosition so fix that.
transform.NextPosition = lerpDest;
found = true;
}
@@ -152,9 +101,15 @@ namespace Robust.Client.GameObjects
{
var lerpDest = transform.NextRotation.Value;
var lerpSource = transform.PrevRotation;
SetLocalRotationNoLerp(uid, Angle.Lerp(lerpSource, lerpDest, step), transform);
transform.NextRotation = lerpDest;
found = true;
var distance = Math.Abs(Angle.ShortestDistance(lerpDest, lerpSource));
if (distance is > MinInterpolationAngle and < MaxInterpolationAngle)
{
transform.LocalRotation = Angle.Lerp(lerpSource, lerpDest, step);
// Setting LocalRotation clears LerpAngle so fix that.
transform.NextRotation = lerpDest;
found = true;
}
}
}

View File

@@ -1,12 +1,13 @@
using JetBrains.Annotations;
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using System;
using UserInterfaceComponent = Robust.Shared.GameObjects.UserInterfaceComponent;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
@@ -18,22 +19,41 @@ namespace Robust.Client.GameObjects
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentInit>(OnUserInterfaceInit);
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
}
private void OnUserInterfaceInit(EntityUid uid, ClientUserInterfaceComponent component, ComponentInit args)
{
component._interfaces.Clear();
foreach (var data in component._interfaceData)
{
component._interfaces[data.UiKey] = data;
}
}
private void OnUserInterfaceShutdown(EntityUid uid, ClientUserInterfaceComponent component, ComponentShutdown args)
{
foreach (var bui in component.OpenInterfaces.Values)
{
bui.Dispose();
}
}
private void MessageReceived(BoundUIWrapMessage ev)
{
var uid = GetEntity(ev.Entity);
if (!TryComp<UserInterfaceComponent>(uid, out var cmp))
var uid = ev.Entity;
if (!TryComp<ClientUserInterfaceComponent>(uid, out var cmp))
return;
var uiKey = ev.UiKey;
var message = ev.Message;
// This should probably not happen at this point, but better make extra sure!
if (_playerManager.LocalPlayer != null)
if(_playerManager.LocalPlayer != null)
message.Session = _playerManager.LocalPlayer.Session;
message.Entity = GetNetEntity(uid);
message.Entity = uid;
message.UiKey = uiKey;
// Raise as object so the correct type is used.
@@ -46,7 +66,7 @@ namespace Robust.Client.GameObjects
break;
case CloseBoundInterfaceMessage _:
TryCloseUi(message.Session, uid, uiKey, remoteCall: true, uiComp: cmp);
TryCloseUi(uid, uiKey, remoteCall: true, uiComp: cmp);
break;
default:
@@ -57,7 +77,7 @@ namespace Robust.Client.GameObjects
}
}
private bool TryOpenUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? uiComp = null)
private bool TryOpenUi(EntityUid uid, Enum uiKey, ClientUserInterfaceComponent? uiComp = null)
{
if (!Resolve(uid, ref uiComp))
return false;
@@ -65,7 +85,7 @@ namespace Robust.Client.GameObjects
if (uiComp.OpenInterfaces.ContainsKey(uiKey))
return false;
var data = uiComp.MappedInterfaceData[uiKey];
var data = uiComp._interfaces[uiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
@@ -76,13 +96,36 @@ namespace Robust.Client.GameObjects
uiComp.OpenInterfaces[uiKey] = boundInterface;
var playerSession = _playerManager.LocalPlayer?.Session;
if (playerSession != null)
{
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
if(playerSession != null)
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
}
return true;
}
internal bool TryCloseUi(EntityUid uid, Enum uiKey, bool remoteCall = false, ClientUserInterfaceComponent? uiComp = null)
{
if (!Resolve(uid, ref uiComp))
return false;
if (!uiComp.OpenInterfaces.TryGetValue(uiKey, out var boundUserInterface))
return false;
if (!remoteCall)
SendUiMessage(boundUserInterface, new CloseBoundInterfaceMessage());
uiComp.OpenInterfaces.Remove(uiKey);
boundUserInterface.Dispose();
var playerSession = _playerManager.LocalPlayer?.Session;
if(playerSession != null)
RaiseLocalEvent(uid, new BoundUIClosedEvent(uiKey, uid, playerSession), true);
return true;
}
internal void SendUiMessage(BoundUserInterface bui, BoundUserInterfaceMessage msg)
{
RaiseNetworkEvent(new BoundUIWrapMessage(bui.Owner, msg, bui.UiKey));
}
}
}

View File

@@ -6,7 +6,7 @@ namespace Robust.Client.GameObjects
{
// These methods are used by the Game State Manager.
EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata);
EntityUid CreateEntity(string? prototypeName, EntityUid uid = default);
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);

View File

@@ -1,6 +1,7 @@
using Robust.Client.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
@@ -14,7 +15,7 @@ public sealed class ClientDirtySystem : EntitySystem
{
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly IComponentFactory _compFact = default!;
// Entities that have removed networked components
// could pool the ushort sets, but predicted component changes are rare... soo...
internal readonly Dictionary<EntityUid, HashSet<ushort>> RemovedComponents = new();
@@ -39,11 +40,11 @@ public sealed class ClientDirtySystem : EntitySystem
private void OnTerminate(ref EntityTerminatingEvent ev)
{
if (!_timing.InPrediction || IsClientSide(ev.Entity, ev.Metadata))
if (!_timing.InPrediction || ev.Entity.IsClientSide())
return;
// Client-side entity deletion is not supported and will cause errors.
Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}");
Logger.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}");
}
private void OnCompRemoved(RemovedComponentEventArgs args)
@@ -51,9 +52,8 @@ public sealed class ClientDirtySystem : EntitySystem
if (args.Terminating)
return;
var uid = args.BaseArgs.Owner;
var comp = args.BaseArgs.Component;
if (!_timing.InPrediction || !comp.NetSyncEnabled || IsClientSide(uid, args.Meta))
if (!_timing.InPrediction || comp.Owner.IsClientSide() || !comp.NetSyncEnabled)
return;
// Was this component added during prediction? If yes, then there is no need to re-add it when resetting.
@@ -62,7 +62,7 @@ public sealed class ClientDirtySystem : EntitySystem
var netId = _compFact.GetRegistration(comp).NetID;
if (netId != null)
RemovedComponents.GetOrNew(uid).Add(netId.Value);
RemovedComponents.GetOrNew(comp.Owner).Add(netId.Value);
}
public void Reset()
@@ -73,7 +73,7 @@ public sealed class ClientDirtySystem : EntitySystem
private void OnEntityDirty(EntityUid e)
{
if (_timing.InPrediction && !IsClientSide(e))
if (_timing.InPrediction && !e.IsClientSide())
DirtyEntities.Add(e);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +1,50 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Robust.Client.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network.Messages;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.GameStates
{
/// <inheritdoc />
internal sealed class GameStateProcessor : IGameStateProcessor
internal sealed class GameStateProcessor : IGameStateProcessor, IPostInjectInit
{
[Dependency] private ILogManager _logMan = default!;
private readonly IClientGameTiming _timing;
private readonly IClientGameStateManager _state;
private readonly ISawmill _logger;
private readonly List<GameState> _stateBuffer = new();
private readonly Dictionary<GameTick, List<NetEntity>> _pvsDetachMessages = new();
private readonly Dictionary<GameTick, List<EntityUid>> _pvsDetachMessages = new();
private ISawmill _logger = default!;
private ISawmill _stateLogger = default!;
public GameState? LastFullState { get; private set; }
public bool WaitingForFull => LastFullStateRequested.HasValue;
public (GameTick Tick, DateTime Time)? LastFullStateRequested { get; private set; } = (GameTick.Zero, DateTime.MaxValue);
public GameTick? LastFullStateRequested
{
get => _lastFullStateRequested;
set
{
_lastFullStateRequested = value;
LastFullState = null;
}
}
public GameTick? _lastFullStateRequested = GameTick.Zero;
private int _bufferSize;
private int _maxBufferSize = 512;
public const int MinimumMaxBufferSize = 256;
/// <summary>
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
/// </summary>
internal readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _lastStateFullRep
internal readonly Dictionary<EntityUid, Dictionary<ushort, ComponentState>> _lastStateFullRep
= new();
/// <inheritdoc />
@@ -48,14 +60,7 @@ namespace Robust.Client.GameStates
public int BufferSize
{
get => _bufferSize;
set => _bufferSize = Math.Max(value, 0);
}
public int MaxBufferSize
{
get => _maxBufferSize;
// We place a lower bound on the maximum size to avoid spamming servers with full game state requests.
set => _maxBufferSize = Math.Max(value, MinimumMaxBufferSize);
set => _bufferSize = value < 0 ? 0 : value;
}
/// <inheritdoc />
@@ -65,12 +70,9 @@ namespace Robust.Client.GameStates
/// Constructs a new instance of <see cref="GameStateProcessor"/>.
/// </summary>
/// <param name="timing">Timing information of the current state.</param>
/// <param name="clientGameStateManager"></param>
public GameStateProcessor(IClientGameStateManager state, IClientGameTiming timing, ISawmill logger)
public GameStateProcessor(IClientGameTiming timing)
{
_timing = timing;
_state = state;
_logger = logger;
}
/// <inheritdoc />
@@ -80,7 +82,7 @@ namespace Robust.Client.GameStates
if (state.ToSequence <= _timing.LastRealTick)
{
if (Logging)
_logger.Debug($"Received Old GameState: lastRealTick={_timing.LastRealTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={state.PayloadSize}, buf={_stateBuffer.Count}");
_stateLogger.Debug($"Received Old GameState: lastRealTick={_timing.LastRealTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={state.PayloadSize}, buf={_stateBuffer.Count}");
return false;
}
@@ -92,7 +94,7 @@ namespace Robust.Client.GameStates
continue;
if (Logging)
_logger.Debug($"Received Dupe GameState: lastRealTick={_timing.LastRealTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={state.PayloadSize}, buf={_stateBuffer.Count}");
_stateLogger.Debug($"Received Dupe GameState: lastRealTick={_timing.LastRealTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={state.PayloadSize}, buf={_stateBuffer.Count}");
return false;
}
@@ -101,68 +103,34 @@ namespace Robust.Client.GameStates
if (!WaitingForFull)
{
// This is a good state that we will be using.
TryAdd(state);
_stateBuffer.Add(state);
if (Logging)
_logger.Debug($"Received New GameState: lastRealTick={_timing.LastRealTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={state.PayloadSize}, buf={_stateBuffer.Count}");
_stateLogger.Debug($"Received New GameState: lastRealTick={_timing.LastRealTick}, fSeq={state.FromSequence}, tSeq={state.ToSequence}, sz={state.PayloadSize}, buf={_stateBuffer.Count}");
return true;
}
if (LastFullState == null && state.FromSequence == GameTick.Zero)
if (LastFullState == null && state.FromSequence == GameTick.Zero && state.ToSequence >= LastFullStateRequested!.Value)
{
if (state.ToSequence >= LastFullStateRequested!.Value.Tick)
{
LastFullState = state;
_logger.Info($"Received Full GameState: to={state.ToSequence}, sz={state.PayloadSize}");
return true;
}
LastFullState = state;
_logger.Info($"Received a late full game state. Received: {state.ToSequence}. Requested: {LastFullStateRequested.Value.Tick}");
if (Logging)
_logger.Info($"Received Full GameState: to={state.ToSequence}, sz={state.PayloadSize}");
return true;
}
if (LastFullState != null && state.ToSequence <= LastFullState.ToSequence)
{
_logger.Info($"While waiting for full, received late GameState with lower to={state.ToSequence} than the last full state={LastFullState.ToSequence}");
if (Logging)
_logger.Info($"While waiting for full, received late GameState with lower to={state.ToSequence} than the last full state={LastFullState.ToSequence}");
return false;
}
TryAdd(state);
_stateBuffer.Add(state);
return true;
}
public void TryAdd(GameState state)
{
if (_stateBuffer.Count <= MaxBufferSize)
{
_stateBuffer.Add(state);
return;
}
// This can happen if a required state gets dropped somehow and the client keeps receiving future
// game states that they can't apply. I.e., GetApplicableStateCount() is zero, even though there are many
// states in the list.
//
// This can seemingly happen when the server sends ""reliable"" game states while the client is paused?
// For example, when debugging the client, while the server is running:
// - The client stops sending acks for states that the server sends out.
// - Thus the client will exceed the net.force_ack_threshold cvar
// - The server starts sending some packets ""reliably"" and just force updates the clients last ack.
//
// What should happen is that when the client resumes, it receives the reliably sent states and can just
// resume. However, even though the packets are sent ""reliably"", they just seem to get dropped.
// I don't quite understand how/why yet, but this ensures the client doesn't get stuck.
#if FULL_RELEASE
_logger.Warning(@$"Exceeded maximum state buffer size!
Tick: {_timing.CurTick}/{_timing.LastProcessedTick}/{_timing.LastRealTick}
Size: {_stateBuffer.Count}
Applicable states: {GetApplicableStateCount()}
Was waiting for full: {WaitingForFull} {LastFullStateRequested}
Had full state: {LastFullState != null}"
);
#endif
_state.RequestFullState();
}
/// <summary>
/// Attempts to get the current and next states to apply.
/// </summary>
@@ -183,7 +151,7 @@ Had full state: {LastFullState != null}"
"Tried to apply a non-extrapolated state that has too high of a FromSequence!");
if (Logging)
_logger.Debug($"Applying State: cTick={_timing.LastProcessedTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
_stateLogger.Debug($"Applying State: cTick={_timing.LastProcessedTick}, fSeq={curState.FromSequence}, tSeq={curState.ToSequence}, buf={_stateBuffer.Count}");
}
return applyNextState;
@@ -210,10 +178,10 @@ Had full state: {LastFullState != null}"
foreach (var entityState in state.EntityStates.Span)
{
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
if (!_lastStateFullRep.TryGetValue(entityState.Uid, out var compData))
{
compData = new Dictionary<ushort, ComponentState>();
_lastStateFullRep.Add(entityState.NetEntity, compData);
_lastStateFullRep.Add(entityState.Uid, compData);
}
foreach (var change in entityState.ComponentChanges.Span)
@@ -295,7 +263,7 @@ Had full state: {LastFullState != null}"
return false;
}
internal void AddLeavePvsMessage(List<NetEntity> entities, GameTick tick)
internal void AddLeavePvsMessage(List<EntityUid> entities, GameTick tick)
{
// Late message may still need to be processed,
DebugTools.Assert(entities.Count > 0);
@@ -304,8 +272,9 @@ Had full state: {LastFullState != null}"
public void ClearDetachQueue() => _pvsDetachMessages.Clear();
public void GetEntitiesToDetach(IList<(GameTick Tick, List<NetEntity> Entities)> result, GameTick toTick, int budget)
public List<(GameTick Tick, List<EntityUid> Entities)> GetEntitiesToDetach(GameTick toTick, int budget)
{
var result = new List<(GameTick Tick, List<EntityUid> Entities)>();
foreach (var (tick, entities) in _pvsDetachMessages)
{
if (tick > toTick)
@@ -324,6 +293,7 @@ Had full state: {LastFullState != null}"
entities.RemoveRange(index, budget);
break;
}
return result;
}
private bool TryGetDeltaState(out GameState? curState, out GameState? nextState)
@@ -373,35 +343,27 @@ Had full state: {LastFullState != null}"
{
_stateBuffer.Clear();
LastFullState = null;
LastFullStateRequested = (GameTick.Zero, DateTime.MaxValue);
LastFullStateRequested = GameTick.Zero;
}
public void OnFullStateRequested(GameTick tick)
public void RequestFullState()
{
_stateBuffer.Clear();
LastFullState = null;
LastFullStateRequested = (tick, DateTime.UtcNow);
LastFullStateRequested = _timing.LastRealTick;
}
public void OnFullStateReceived()
public void MergeImplicitData(Dictionary<EntityUid, Dictionary<ushort, ComponentState>> implicitData)
{
LastFullState = null;
LastFullStateRequested = null;
}
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, ComponentState>> implicitData)
{
foreach (var (netEntity, implicitEntState) in implicitData)
foreach (var (uid, implicitEntState) in implicitData)
{
var fullRep = _lastStateFullRep[netEntity];
var fullRep = _lastStateFullRep[uid];
foreach (var (netId, implicitCompState) in implicitEntState)
{
ref var serverState = ref CollectionsMarshal.GetValueRefOrAddDefault(fullRep, netId, out var exists);
if (!exists)
if (!fullRep.TryGetValue(netId, out var serverState))
{
serverState = implicitCompState;
fullRep.Add(netId, implicitCompState);
continue;
}
@@ -412,50 +374,37 @@ Had full state: {LastFullState != null}"
// state from the entity prototype.
if (implicitCompState is not IComponentDeltaState implicitDelta || !implicitDelta.FullState)
{
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {netEntity}");
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {uid}");
continue;
}
serverDelta.ApplyToFullState(implicitCompState);
serverState = implicitCompState;
fullRep[netId] = implicitCompState;
DebugTools.Assert(implicitCompState is IComponentDeltaState d && d.FullState);
}
}
}
public Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity netEntity)
public Dictionary<ushort, ComponentState> GetLastServerStates(EntityUid entity)
{
return _lastStateFullRep[netEntity];
return _lastStateFullRep[entity];
}
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
public Dictionary<EntityUid, Dictionary<ushort, ComponentState>> GetFullRep()
{
return _lastStateFullRep;
}
public bool TryGetLastServerStates(NetEntity entity,
public bool TryGetLastServerStates(EntityUid entity,
[NotNullWhen(true)] out Dictionary<ushort, ComponentState>? dictionary)
{
return _lastStateFullRep.TryGetValue(entity, out dictionary);
}
public bool IsQueuedForDetach(NetEntity entity)
public int CalculateBufferSize(GameTick fromTick)
{
// This isn't fast, but its just meant for use in tests & debug asserts.
foreach (var msg in _pvsDetachMessages.Values)
{
if (msg.Contains(entity))
return true;
}
return false;
}
public int GetApplicableStateCount(GameTick? fromTick = null)
{
fromTick ??= _timing.LastRealTick;
bool foundState;
var nextTick = fromTick.Value;
var nextTick = fromTick;
do
{
@@ -473,9 +422,13 @@ Had full state: {LastFullState != null}"
}
while (foundState);
return (int) (nextTick.Value - fromTick.Value.Value);
return (int) (nextTick.Value - fromTick.Value);
}
public int StateCount => _stateBuffer.Count;
void IPostInjectInit.PostInject()
{
_logger = _logMan.GetSawmill("net");
_stateLogger = _logMan.GetSawmill("net.state");
}
}
}

View File

@@ -32,15 +32,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Number of applicable game states currently in the state buffer.
/// </summary>
int GetApplicableStateCount();
[Obsolete("use GetApplicableStateCount()")]
int CurrentBufferSize => GetApplicableStateCount();
/// <summary>
/// Total number of game states currently in the state buffer.
/// </summary>
int StateCount { get; }
int CurrentBufferSize { get; }
/// <summary>
/// If the buffer size is this many states larger than the target buffer size,
@@ -83,7 +75,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Applies a given set of game states.
/// </summary>
IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState);
IEnumerable<EntityUid> ApplyGameState(GameState curState, GameState? nextState);
/// <summary>
/// Resets any entities that have changed while predicting future ticks.
@@ -94,12 +86,12 @@ namespace Robust.Client.GameStates
/// An input command has been dispatched.
/// </summary>
/// <param name="message">Message being dispatched.</param>
void InputCommandDispatched(ClientFullInputCmdMessage clientMsg, FullInputCmdMessage message);
void InputCommandDispatched(FullInputCmdMessage message);
/// <summary>
/// Requests a full state from the server. This should override even implicit entity data.
/// </summary>
void RequestFullState(NetEntity? missingEntity = null, GameTick? tick = null);
void RequestFullState(EntityUid? missingEntity = null);
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
@@ -113,7 +105,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Returns the full collection of cached game states that are used to reset predicted entities.
/// </summary>
Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep();
Dictionary<EntityUid, Dictionary<ushort, ComponentState>> GetFullRep();
/// <summary>
/// This will perform some setup in order to reset the game to an earlier state. To fully reset the state
@@ -152,12 +144,12 @@ namespace Robust.Client.GameStates
/// Queue a collection of entities that are to be detached to null-space & marked as PVS-detached.
/// This store and modify the list given to it.
/// </summary>
void QueuePvsDetach(List<NetEntity> entities, GameTick tick);
void QueuePvsDetach(List<EntityUid> entities, GameTick tick);
/// <summary>
/// Immediately detach several entities.
/// </summary>
void DetachImmediate(List<NetEntity> entities);
void DetachImmediate(List<EntityUid> entities);
/// <summary>
/// Clears the PVS detach queue.

View File

@@ -83,22 +83,22 @@ namespace Robust.Client.GameStates
/// The data to merge.
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
/// </param>
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, ComponentState>> data);
void MergeImplicitData(Dictionary<EntityUid, Dictionary<ushort, ComponentState>> data);
/// <summary>
/// Get the last state data from the server for an entity.
/// </summary>
/// <returns>Dictionary (net ID -> ComponentState)</returns>
Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity entity);
Dictionary<ushort, ComponentState> GetLastServerStates(EntityUid entity);
/// <summary>
/// Calculate the number of applicable states in the game state buffer from a given tick.
/// This includes only applicable states. If there is a gap, future buffers are not included.
/// </summary>
/// <param name="fromTick">The tick to calculate from.</param>
int GetApplicableStateCount(GameTick? fromTick);
int CalculateBufferSize(GameTick fromTick);
bool TryGetLastServerStates(NetEntity entity,
bool TryGetLastServerStates(EntityUid entity,
[NotNullWhen(true)] out Dictionary<ushort, ComponentState>? dictionary);
}
}

View File

@@ -35,7 +35,7 @@ namespace Robust.Client.GameStates
private readonly Font _font;
private readonly int _lineHeight;
private readonly Dictionary<NetEntity, NetEntData> _netEnts = new();
private readonly Dictionary<EntityUid, NetEntData> _netEnts = new();
public NetEntityOverlay()
{
@@ -77,12 +77,12 @@ namespace Robust.Client.GameStates
foreach (var entityState in gameState.EntityStates.Span)
{
if (!_netEnts.TryGetValue(entityState.NetEntity, out var netEnt))
if (!_netEnts.TryGetValue(entityState.Uid, out var netEnt))
{
if (_netEnts.Count >= _maxEnts)
continue;
_netEnts[entityState.NetEntity] = netEnt = new();
_netEnts[entityState.Uid] = netEnt = new();
}
if (!netEnt.InPVS && netEnt.LastUpdate < gameState.ToSequence)
@@ -119,13 +119,11 @@ namespace Robust.Client.GameStates
var screenHandle = args.ScreenHandle;
int i = 0;
foreach (var (nent, netEnt) in _netEnts)
foreach (var (uid, netEnt) in _netEnts)
{
var uid = _entityManager.GetEntity(nent);
if (!_entityManager.EntityExists(uid))
{
_netEnts.Remove(nent);
_netEnts.Remove(uid);
continue;
}

View File

@@ -26,7 +26,6 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
private const int HistorySize = 60 * 5; // number of ticks to keep in history.
private const int TargetPayloadBps = 56000 / 8; // Target Payload size in Bytes per second. A mind-numbing fifty-six thousand bits per second, who would ever need more?
@@ -68,13 +67,13 @@ namespace Robust.Client.GameStates
var lag = _netManager.ServerChannel!.Ping;
// calc interp info
var buffer = _gameStateManager.GetApplicableStateCount();
var buffer = _gameStateManager.CurrentBufferSize;
_totalHistoryPayload += sz;
_history.Add((toSeq, sz, lag, buffer));
// not watching an ent
if(!WatchEntId.IsValid() || _entManager.IsClientSide(WatchEntId))
if(!WatchEntId.IsValid() || WatchEntId.IsClientSide())
return;
string? entStateString = null;
@@ -87,9 +86,7 @@ namespace Robust.Client.GameStates
var sb = new StringBuilder();
foreach (var entState in entStates.Span)
{
var uid = _entManager.GetEntity(entState.NetEntity);
if (uid != WatchEntId)
if (entState.Uid != WatchEntId)
continue;
if (!entState.ComponentChanges.HasContents)
@@ -118,9 +115,7 @@ namespace Robust.Client.GameStates
foreach (var ent in args.Detached)
{
var uid = _entManager.GetEntity(ent);
if (uid != WatchEntId)
if (ent != WatchEntId)
continue;
conShell.WriteLine($"watchEnt: Left PVS at tick {args.AppliedState.ToSequence}, eid={WatchEntId}" + "\n");
@@ -131,9 +126,7 @@ namespace Robust.Client.GameStates
{
foreach (var entDelete in entDeletes.Span)
{
var uid = _entManager.GetEntity(entDelete);
if (uid == WatchEntId)
if (entDelete == WatchEntId)
entDelString = "\n Deleted";
}
}
@@ -268,7 +261,7 @@ namespace Robust.Client.GameStates
handle.DrawString(_font, new Vector2(LeftMargin + width, lastLagY), $"{lastLagMs.ToString()}ms");
// buffer text
handle.DrawString(_font, new Vector2(LeftMargin, height + LowerGraphOffset), $"{_gameStateManager.GetApplicableStateCount().ToString()} states");
handle.DrawString(_font, new Vector2(LeftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
}
protected override void DisposeBehavior()
@@ -301,33 +294,30 @@ namespace Robust.Client.GameStates
private sealed class NetWatchEntCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
public override string Command => "net_watchent";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntityUid? entity;
EntityUid eValue;
if (args.Length == 0)
{
entity = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
eValue = IoCManager.Resolve<IPlayerManager>().LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
}
else if (!NetEntity.TryParse(args[0], out var netEntity) || !_entManager.TryGetEntity(netEntity, out entity))
else if (!EntityUid.TryParse(args[0], out eValue))
{
shell.WriteError("Invalid argument: Needs to be 0 or an entityId.");
return;
}
if (!_overlayManager.TryGetOverlay(out NetGraphOverlay? overlay))
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if (!overlayMan.TryGetOverlay(out NetGraphOverlay? overlay))
{
overlay = new NetGraphOverlay();
_overlayManager.AddOverlay(overlay);
overlay = new();
overlayMan.AddOverlay(overlay);
}
overlay.WatchEntId = entity.Value;
overlay.WatchEntId = eValue;
}
}
}

View File

@@ -1,19 +1,19 @@
using System;
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Containers;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
namespace Robust.Client.GameStates
{
internal sealed class NetInterpOverlay : Overlay
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -24,11 +24,6 @@ namespace Robust.Client.GameStates
private readonly SharedContainerSystem _container;
private readonly SharedTransformSystem _xform;
/// <summary>
/// When an entity stops lerping the overlay will continue to draw a box around the entity for this amount of time.
/// </summary>
public static readonly TimeSpan Delay = TimeSpan.FromSeconds(2f);
public NetInterpOverlay(EntityLookupSystem lookup)
{
IoCManager.InjectDependencies(this);
@@ -45,8 +40,8 @@ namespace Robust.Client.GameStates
var worldHandle = (DrawingHandleWorld) handle;
var viewport = args.WorldAABB;
var query = _entityManager.AllEntityQueryEnumerator<TransformComponent>();
while (query.MoveNext(out var uid, out var transform))
var query = _entityManager.AllEntityQueryEnumerator<PhysicsComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var physics, out var transform))
{
// if not on the same map, continue
if (transform.MapID != _eyeManager.CurrentMap || _container.IsEntityInContainer(uid))
@@ -55,8 +50,8 @@ namespace Robust.Client.GameStates
if (transform.GridUid == uid)
continue;
var delta = (_timing.CurTick.Value - transform.LastLerp.Value) * _timing.TickPeriod;
if(!transform.ActivelyLerping && delta > Delay)
// This entity isn't lerping, no need to draw debug info for it
if(transform.NextPosition == null)
continue;
var aabb = _lookup.GetWorldAABB(uid);
@@ -66,9 +61,7 @@ namespace Robust.Client.GameStates
continue;
var (pos, rot) = _xform.GetWorldPositionRotation(transform, _entityManager.GetEntityQuery<TransformComponent>());
var boxOffset = transform.NextPosition != null
? transform.NextPosition.Value - transform.LocalPosition
: default;
var boxOffset = transform.NextPosition.Value - transform.LocalPosition;
var worldOffset = (rot - transform.LocalRotation).RotateVec(boxOffset);
var nextPos = pos + worldOffset;

View File

@@ -1,5 +1,4 @@
using JetBrains.Annotations;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;

View File

@@ -1,17 +1,17 @@
#nullable enable
using System.Numerics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Graphics
#nullable enable
namespace Robust.Client.Graphics
{
/// <inheritdoc />
[Virtual]
public class Eye : IEye
{
private Vector2 _scale = Vector2.One / 2f;
private Vector2 _scale = Vector2.One/2f;
private Angle _rotation = Angle.Zero;
private MapCoordinates _coords;
@@ -19,10 +19,6 @@ namespace Robust.Shared.Graphics
[ViewVariables(VVAccess.ReadWrite)]
public bool DrawFov { get; set; } = true;
/// <inheritdoc />
[ViewVariables]
public bool DrawLight { get; set; } = true;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public virtual MapCoordinates Position

View File

@@ -2,7 +2,6 @@ using System.Numerics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;

View File

@@ -1,6 +1,4 @@
using Robust.Shared.Graphics;
namespace Robust.Client.Graphics
namespace Robust.Client.Graphics
{
/// <summary>
/// A fixed eye is an eye which is fixed to one point, its position.

View File

@@ -1,9 +1,9 @@
using System.Numerics;
using System.Numerics;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Shared.Graphics
namespace Robust.Client.Graphics
{
/// <summary>
/// An Eye is a point through which the player can view the world.
@@ -17,11 +17,6 @@ namespace Robust.Shared.Graphics
/// </summary>
bool DrawFov { get; set; }
/// <summary>
/// Whether to draw lights for this eye.
/// </summary>
bool DrawLight { get; set; }
/// <summary>
/// Current position of the center of the eye in the game world.
/// </summary>

View File

@@ -1,6 +1,5 @@
using System.Numerics;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;

View File

@@ -2,8 +2,8 @@ using System;
using System.Collections.Generic;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
@@ -40,28 +40,22 @@ namespace Robust.Client.Graphics.Clyde
gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
var grids = new List<Entity<MapGridComponent>>();
_mapManager.FindGridsIntersecting(mapId, worldBounds, ref grids);
foreach (var mapGrid in grids)
foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
if (!_mapChunkData.ContainsKey(mapGrid))
if (!_mapChunkData.ContainsKey(mapGrid.Owner))
continue;
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid.Owner);
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
var enumerator = mapGrid.Comp.GetMapChunks(worldBounds);
var data = _mapChunkData[mapGrid];
var enumerator = mapGrid.GetMapChunks(worldBounds);
while (enumerator.MoveNext(out var chunk))
{
DebugTools.Assert(chunk.FilledTiles > 0);
if (!data.TryGetValue(chunk.Indices, out MapChunkData? datum))
data[chunk.Indices] = datum = _initChunkBuffers(mapGrid, chunk);
if (_isChunkDirty(mapGrid, chunk))
_updateChunkMesh(mapGrid, chunk);
if (datum.Dirty)
_updateChunkMesh(mapGrid, chunk, datum);
var datum = _mapChunkData[mapGrid.Owner][chunk.Indices];
DebugTools.Assert(datum.TileCount > 0);
if (datum.TileCount == 0)
continue;
@@ -73,36 +67,22 @@ namespace Robust.Client.Graphics.Clyde
CheckGlError();
}
}
CullEmptyChunks();
}
private void CullEmptyChunks()
private void _updateChunkMesh(MapGridComponent grid, MapChunk chunk)
{
foreach (var (grid, chunks) in _mapChunkData)
var data = _mapChunkData[grid.Owner];
if (!data.TryGetValue(chunk.Indices, out var datum))
{
var gridComp = _mapManager.GetGridComp(grid);
foreach (var (index, chunk) in chunks)
{
if (!chunk.Dirty || gridComp.Chunks.ContainsKey(index))
{
DebugTools.Assert(gridComp.Chunks[index].FilledTiles > 0);
continue;
}
DeleteChunk(chunk);
chunks.Remove(index);
}
datum = _initChunkBuffers(grid, chunk);
}
}
private void _updateChunkMesh(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
{
Span<ushort> indexBuffer = stackalloc ushort[_indicesPerChunk(chunk)];
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
var i = 0;
var cSz = grid.Comp.ChunkSize;
var cSz = grid.ChunkSize;
var cScaled = chunk.Indices * cSz;
for (ushort x = 0; x < cSz; x++)
{
@@ -149,7 +129,7 @@ namespace Robust.Client.Graphics.Clyde
datum.TileCount = i;
}
private unsafe MapChunkData _initChunkBuffers(Entity<MapGridComponent> grid, MapChunk chunk)
private unsafe MapChunkData _initChunkBuffers(MapGridComponent grid, MapChunk chunk)
{
var vao = GenVertexArray();
BindVertexArray(vao);
@@ -177,22 +157,41 @@ namespace Robust.Client.Graphics.Clyde
Dirty = true
};
_mapChunkData[grid.Owner].Add(chunk.Indices, datum);
return datum;
}
private void DeleteChunk(MapChunkData data)
private bool _isChunkDirty(MapGridComponent grid, MapChunk chunk)
{
DeleteVertexArray(data.VAO);
CheckGlError();
data.VBO.Delete();
data.EBO.Delete();
var data = _mapChunkData[grid.Owner];
return !data.TryGetValue(chunk.Indices, out var datum) || datum.Dirty;
}
public void _setChunkDirty(MapGridComponent grid, Vector2i chunk)
{
var data = _mapChunkData.GetOrNew(grid.Owner);
if (data.TryGetValue(chunk, out var datum))
{
datum.Dirty = true;
}
// Don't need to set it if we don't have an entry since lack of an entry is treated as dirty.
}
private void _updateOnGridModified(GridModifiedEvent args)
{
foreach (var (pos, _) in args.Modified)
{
var grid = args.Grid;
var chunk = grid.GridTileToChunkIndices(pos);
_setChunkDirty(grid, chunk);
}
}
private void _updateTileMapOnUpdate(ref TileChangedEvent args)
{
var gridData = _mapChunkData.GetOrNew(args.Entity);
if (gridData.TryGetValue(args.ChunkIndex, out var data))
data.Dirty = true;
var grid = _mapManager.GetGrid(args.NewTile.GridUid);
var chunk = grid.GridTileToChunkIndices(new Vector2i(args.NewTile.X, args.NewTile.Y));
_setChunkDirty(grid, chunk);
}
private void _updateOnGridCreated(GridStartupEvent ev)
@@ -208,7 +207,10 @@ namespace Robust.Client.Graphics.Clyde
var data = _mapChunkData[gridId];
foreach (var chunkDatum in data.Values)
{
DeleteChunk(chunkDatum);
DeleteVertexArray(chunkDatum.VAO);
CheckGlError();
chunkDatum.VBO.Delete();
chunkDatum.EBO.Delete();
}
_mapChunkData.Remove(gridId);

View File

@@ -9,7 +9,6 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
@@ -350,7 +349,7 @@ namespace Robust.Client.Graphics.Clyde
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
if (entry.Sprite.RaiseShaderEvent)
_entityManager.EventBus.RaiseLocalEvent(entry.Uid,
_entityManager.EventBus.RaiseLocalEvent(entry.Sprite.Owner,
new BeforePostShaderRenderEvent(entry.Sprite, viewport), false);
}
}
@@ -512,7 +511,7 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowFOV, worldAABB, worldBounds);
}
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
ApplyFovToBuffer(viewport, eye);
}

View File

@@ -3,7 +3,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using ES20 = OpenToolkit.Graphics.ES20;

View File

@@ -16,10 +16,8 @@ using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
using Robust.Shared.Physics;
using Robust.Client.ComponentTrees;
using Robust.Shared.Graphics;
using static Robust.Shared.GameObjects.OccluderComponent;
using Robust.Shared.Utility;
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
@@ -97,9 +95,6 @@ namespace Robust.Client.Graphics.Clyde
private (PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot)[] _lightsToRenderList = default!;
private LightCapacityComparer _lightCap = new();
private ShadowCapacityComparer _shadowCap = new ShadowCapacityComparer();
private unsafe void InitLighting()
{
@@ -335,18 +330,16 @@ namespace Robust.Client.Graphics.Clyde
private void DrawLightsAndFov(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
{
if (!_lightManager.Enabled || !eye.DrawLight)
if (!_lightManager.Enabled)
{
return;
}
var mapId = eye.Position.MapId;
if (mapId == MapId.Nullspace)
return;
// If this map has lighting disabled, return
var mapUid = _mapManager.GetMapEntityId(mapId);
if (!_entityManager.TryGetComponent<MapComponent>(mapUid, out var map) || !map.LightingEnabled)
if (!_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
{
return;
}
@@ -573,28 +566,6 @@ namespace Robust.Client.Graphics.Clyde
return true;
}
private sealed class LightCapacityComparer : IComparer<(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot)>
{
public int Compare(
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot) x,
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot) y)
{
if (x.light.CastShadows && !y.light.CastShadows) return 1;
if (!x.light.CastShadows && y.light.CastShadows) return -1;
return 0;
}
}
private sealed class ShadowCapacityComparer : IComparer<(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot)>
{
public int Compare(
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot) x,
(PointLightComponent light, Vector2 pos, float distanceSquared, Angle rot) y)
{
return x.distanceSquared.CompareTo(y.distanceSquared);
}
}
private (int count, Box2 expandedBounds) GetLightsToRender(
MapId map,
in Box2Rotated worldBounds,
@@ -620,10 +591,20 @@ namespace Robust.Client.Graphics.Clyde
// First, partition the array based on whether the lights are shadow casting or not
// (non shadow casting lights should be the first partition, shadow casting lights the second)
Array.Sort(_lightsToRenderList, 0, state.count, _lightCap);
Array.Sort(_lightsToRenderList, 0, state.count,
Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
{
if (x.light.CastShadows && !y.light.CastShadows) return 1;
else if (!x.light.CastShadows && y.light.CastShadows) return -1;
else return 0;
}));
// Next, sort just the shadow casting lights by distance.
Array.Sort(_lightsToRenderList, state.count - state.shadowCastingCount, state.shadowCastingCount, _shadowCap);
Array.Sort(_lightsToRenderList, state.count - state.shadowCastingCount, state.shadowCastingCount,
Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
{
return x.distanceSquared.CompareTo(y.distanceSquared);
}));
// Then effectively delete the furthest lights, by setting the end of the array to exclude N
// number of shadow casting lights (where N is the number above the max number per scene.)

View File

@@ -5,7 +5,6 @@ using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Graphics;
namespace Robust.Client.Graphics.Clyde
{

View File

@@ -3,7 +3,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;

View File

@@ -7,7 +7,6 @@ using System.Runtime.InteropServices;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;

View File

@@ -6,7 +6,6 @@ using System.Numerics;
using System.Text;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.ResourceManagement;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;

View File

@@ -1,3 +1,11 @@
using Robust.Client.ComponentTrees;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Threading;
using Robust.Shared.Utility;
using System;
using System.Buffers;
using System.Collections.Generic;
@@ -6,15 +14,6 @@ using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Threading.Tasks;
using Robust.Client.ComponentTrees;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Threading;
using Robust.Shared.Utility;
namespace Robust.Client.Graphics.Clyde;
@@ -260,7 +259,7 @@ internal partial class Clyde
if (cmp != 0)
return cmp;
return a.Uid.CompareTo(b.Uid);
return a.Sprite.Owner.CompareTo(b.Sprite.Owner);
}
}
}

View File

@@ -8,8 +8,6 @@ using System.Runtime.InteropServices;
using System.Threading;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
@@ -19,7 +17,6 @@ using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
using PIF = OpenToolkit.Graphics.OpenGL4.PixelInternalFormat;
using PF = OpenToolkit.Graphics.OpenGL4.PixelFormat;
using PT = OpenToolkit.Graphics.OpenGL4.PixelType;
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
namespace Robust.Client.Graphics.Clyde
{

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Numerics;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;

View File

@@ -12,15 +12,15 @@ using Robust.Client.UserInterface;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
using Robust.Shared.Timing;
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
using SixLabors.ImageSharp;
using Color = Robust.Shared.Maths.Color;
using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
namespace Robust.Client.Graphics.Clyde
{
@@ -173,6 +173,7 @@ namespace Robust.Client.Graphics.Clyde
_entityManager.EventBus.SubscribeEvent<TileChangedEvent>(EventSource.Local, this, _updateTileMapOnUpdate);
_entityManager.EventBus.SubscribeEvent<GridStartupEvent>(EventSource.Local, this, _updateOnGridCreated);
_entityManager.EventBus.SubscribeEvent<GridRemovalEvent>(EventSource.Local, this, _updateOnGridRemoved);
_entityManager.EventBus.SubscribeEvent<GridModifiedEvent>(EventSource.Local, this, _updateOnGridModified);
}
public void ShutdownGridEcsEvents()
@@ -180,6 +181,7 @@ namespace Robust.Client.Graphics.Clyde
_entityManager.EventBus.UnsubscribeEvent<TileChangedEvent>(EventSource.Local, this);
_entityManager.EventBus.UnsubscribeEvent<GridStartupEvent>(EventSource.Local, this);
_entityManager.EventBus.UnsubscribeEvent<GridRemovalEvent>(EventSource.Local, this);
_entityManager.EventBus.UnsubscribeEvent<GridModifiedEvent>(EventSource.Local, this);
}
private void GLInitBindings(bool gles)

View File

@@ -8,7 +8,6 @@ using Robust.Client.Audio;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;

View File

@@ -0,0 +1,9 @@
namespace Robust.Client.Graphics
{
internal enum ClydeStockTexture : byte
{
White,
Black,
Transparent
}
}

View File

@@ -3,7 +3,6 @@ using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics

View File

@@ -3,7 +3,6 @@ using System.Numerics;
using System.Text;
using Robust.Client.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
@@ -155,7 +154,7 @@ namespace Robust.Client.Graphics
Vector2 scale,
Angle? worldRot,
Angle eyeRotation = default,
Shared.Maths.Direction? overrideDirection = null,
Direction? overrideDirection = null,
SpriteComponent? sprite = null,
TransformComponent? xform = null,
SharedTransformSystem? xformSystem = null);

View File

@@ -1,5 +1,4 @@
using System.Numerics;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics

View File

@@ -1,6 +1,5 @@
using System;
using System.Numerics;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;

View File

@@ -4,7 +4,6 @@ using System.IO;
using System.Text;
using JetBrains.Annotations;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SharpFont;

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Threading.Tasks;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;

View File

@@ -1,7 +1,6 @@
using System;
using System.Numerics;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;

View File

@@ -38,7 +38,8 @@ namespace Robust.Client.Graphics
event Action<WindowDestroyedEventArgs> Destroyed;
/// <summary>
/// Raised when the window has been resized.
/// Raised when the window has been definitively closed.
/// This means the window must not be used anymore (it is disposed).
/// </summary>
event Action<WindowResizedEventArgs> Resized;
}

View File

@@ -1,4 +1,4 @@
using Robust.Shared.Maths;
using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{

View File

@@ -1,6 +1,5 @@
using System.IO;
using System.Text;
using Robust.Shared.Graphics;
namespace Robust.Client.Graphics
{

View File

@@ -37,7 +37,7 @@ namespace Robust.Client.Graphics
Vector2 scale,
Angle? worldRot,
Angle eyeRotation = default,
Shared.Maths.Direction? overrideDirection = null,
Direction? overrideDirection = null,
SpriteComponent? sprite = null,
TransformComponent? xform = null,
SharedTransformSystem? xformSystem = null);

Some files were not shown because too many files have changed in this diff Show More