Compare commits

...

151 Commits

Author SHA1 Message Date
Pieter-Jan Briers
1904207358 Version: 162.2.2 2024-03-10 20:50:29 +01:00
Pieter-Jan Briers
939840ddab Backport 859f150404
(cherry picked from commit 24d5c26fa6)
(cherry picked from commit 688efac67b634c613539b783a9fb6e679948cd53)
2024-03-10 20:50:29 +01:00
DrSmugleaf
a6c295b89c Version: 162.2.1 2023-09-28 17:55:17 -07:00
DrSmugleaf
165913a4de Add IComparable to ProtoId, EntProtoId and LocId (#4460) 2023-09-28 17:53:23 -07:00
metalgearsloth
675dfdaabd Fix scroll containers invalidating on first scroll (#4449) 2023-09-28 16:58:18 -07:00
Kara
fab172d6f6 Allow force submitting line edits (#4455) 2023-09-28 11:07:55 -07:00
metalgearsloth
e75c1659f6 Version: 162.2.0 2023-09-28 20:25:52 +10:00
DrSmugleaf
0c440a8fc9 Localize "View Variables" (#4454) 2023-09-28 20:22:54 +10:00
DrSmugleaf
0c2c8f352a Add LocId and LocIdSerializer (#4456) 2023-09-28 20:22:17 +10:00
DrSmugleaf
0a4a2b7a36 Add nullable conversion operators to ProtoId and EntProtoId (#4447) 2023-09-28 20:18:45 +10:00
metalgearsloth
b5b59c1d2f Use CollectionsMarshal in clientgamestatemanager (#4453) 2023-09-28 20:13:38 +10:00
metalgearsloth
f4f0967fdc Fix double contact deletion throwing (#4450) 2023-09-28 20:12:45 +10:00
metalgearsloth
3d69766112 Use CollectionsMarshal for mergeimplicitdata (#4451) 2023-09-28 20:12:22 +10:00
DrSmugleaf
d1eb3438d5 Add support for automatically networking entity lists and sets (#4446) 2023-09-25 18:02:53 +10:00
ElectroJr
8f6b189d29 Version: 162.1.1 2023-09-19 17:59:16 -04:00
Leon Friedrich
ef8b278b47 Fix HideSpawnMenu (#4437) 2023-09-20 07:57:50 +10:00
metalgearsloth
c53ce2c907 Version: 162.1.0 2023-09-19 23:19:39 +10:00
Leon Friedrich
f063aa3ea1 Use CollectionsMarshal in RobustTree (#4429) 2023-09-19 23:17:27 +10:00
Leon Friedrich
30f63254ef Mark ProtoId<T> as serializable (#4430) 2023-09-19 23:13:14 +10:00
Leon Friedrich
30a5b6152c Slightly improve AddToChunkSetRecursively() (#4432) 2023-09-19 23:13:00 +10:00
Leon Friedrich
910a7f8bff Fix visibility layers not updating on children (#4431) 2023-09-19 23:12:52 +10:00
Leon Friedrich
526a88293e Use CollectionsMarshal in AddComponentInternal() (#4435) 2023-09-19 23:09:41 +10:00
metalgearsloth
22cd840b83 Revert "Add force ack threshold (#4423)" (#4436) 2023-09-19 18:36:41 +10:00
metalgearsloth
415c518bc7 Version: 162.0.0 2023-09-19 17:40:46 +10:00
Leon Friedrich
2417dbb0e0 Use CollectionsMarshal in DynamicTree (#4433) 2023-09-19 15:58:02 +10:00
Leon Friedrich
005673a957 Add force ack threshold (#4423) 2023-09-19 15:16:01 +10:00
Leon Friedrich
942db3120c Make entity system proxy methods use Metadata & Transform queries (#4434) 2023-09-19 15:11:21 +10:00
Leon Friedrich
c0a5fab19e Add missing EntitySystem - EntityManager proxy method (#4427) 2023-09-18 12:17:46 +10:00
Leon Friedrich
d16c62b132 Use CollectionsMarshal in PVS (#4428) 2023-09-18 12:17:32 +10:00
Leon Friedrich
7da22557fe Add entity categories (#4356) 2023-09-18 12:14:26 +10:00
metalgearsloth
92f47c0f20 Version: 161.1.0 2023-09-18 11:48:03 +10:00
Leon Friedrich
9576d0739f Add more DebugTools assert variants (#4425) 2023-09-18 11:18:35 +10:00
Leon Friedrich
10f25faabf Try fix oldest ack issues (#4426) 2023-09-18 11:17:49 +10:00
Leon Friedrich
74831a177e Don't attempt to insert entities into deleted containers (#4424) 2023-09-18 10:57:14 +10:00
ElectroJr
88d3168913 Version: 161.0.0 2023-09-17 13:56:34 -04:00
Leon Friedrich
902519093c Add PVS debug command (#4422) 2023-09-18 03:49:57 +10:00
Leon Friedrich
366266a8ae Fix light animations not working (#4413) 2023-09-18 03:49:47 +10:00
Leon Friedrich
a22cce7783 Fix metadata assert (#4419) 2023-09-17 17:18:44 +10:00
metalgearsloth
e5e738b8cd Maybe fix heisentest (#4418) 2023-09-17 16:13:37 +10:00
metalgearsloth
b8f6e83473 Use stackalloc Span<Vector2> in ComputeHull (#4417) 2023-09-17 12:41:53 +10:00
Artur
c5fb186c57 Add missing InvariantCulture to AngleTypeParser (#4411) 2023-09-17 11:32:01 +10:00
Leon Friedrich
131d7f5422 Add string formatting for client-side NetEntity ids (#4415) 2023-09-17 11:29:54 +10:00
Leon Friedrich
217996f1ed Use ToPrettyString() in state error logs. (#4416) 2023-09-17 11:29:02 +10:00
DrSmugleaf
fc718d68a5 Fix IClydeWindow resized xmldoc (#4414) 2023-09-17 11:28:43 +10:00
Leon Friedrich
d7d9578803 Mark EntPrototId as NetSerializable (#4412) 2023-09-17 11:25:40 +10:00
metalgearsloth
9bbeb54569 Version: 160.1.0 2023-09-17 11:20:03 +10:00
metalgearsloth
1ea7071ffb Backport some arch comp net changes (#4408)
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2023-09-17 11:03:11 +10:00
ElectroJr
196028b619 Version: 160.0.2 2023-09-16 14:50:34 -04:00
ElectroJr
c102da052f Make VV fields private 2023-09-16 14:47:27 -04:00
metalgearsloth
5d46cdcfa4 Add transform parent + container VVs (#4407) 2023-09-16 17:08:08 +10:00
metalgearsloth
cd646d3b07 Version: 160.0.0 2023-09-16 15:26:56 +10:00
Leon Friedrich
922165fa19 Include sensors in default entity lookups (#4406) 2023-09-16 15:22:54 +10:00
metalgearsloth
4879252e99 Remove comprefs entirely (#4367) 2023-09-15 21:52:47 +10:00
DrSmugleaf
0e21f5727a Version: 159.1.0 2023-09-15 03:41:17 -07:00
Leon Friedrich
3ce8a00389 Store metadata component in NetEntity lookup dictionary (#4400)
Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
2023-09-15 02:01:44 -07:00
Leon Friedrich
9a283fe541 PVS NetEntity related changes (#4399) 2023-09-15 01:47:35 -07:00
Leon Friedrich
f3e3e64db3 Re-add entity check to fixture equality (#4397) 2023-09-15 18:44:24 +10:00
Leon Friedrich
4a4a135089 Make ToPrettyString() take in nullable EntityUids (#4396) 2023-09-15 01:43:39 -07:00
metalgearsloth
e323a67806 Remove 1 pvs getcomp (#4398) 2023-09-15 15:28:16 +10:00
Leon Friedrich
3a328ffdd5 More NetEntity VV fixes (#4395) 2023-09-15 13:37:18 +10:00
Leon Friedrich
bc5107e297 Misc NetEntity fixes for VV (#4390) 2023-09-15 10:11:28 +10:00
metalgearsloth
2abf33c9be Queue LightTree update on light states (#4385)
Might fix some more issues.
2023-09-15 06:03:25 +12:00
metalgearsloth
71c46828c2 Minor re-apply net state change (#4392)
Slightly faster than checking deleted first, at least for now.
2023-09-15 05:56:51 +12:00
DrSmugleaf
814d6fe2d0 Add ProtoId, EntProtoId and serializers (#4387) 2023-09-12 23:19:40 -07:00
DrSmugleaf
77b98b8308 Add ProtoId, EntProtoId and serializers (#4386) 2023-09-13 14:32:01 +10:00
metalgearsloth
34b0a7fc6d Fix tooltip bounds (#4384) 2023-09-13 13:23:34 +10:00
DrSmugleaf
1b6123c79f Make data field tags optional (#4382) 2023-09-13 12:58:46 +10:00
DrSmugleaf
1476f9d462 Make QueueDel work with nullable entity uids (#4381) 2023-09-13 12:58:03 +10:00
Leon Friedrich
d62efe7301 Add UnknownEntityDeleteTest (#4380) 2023-09-13 12:57:50 +10:00
metalgearsloth
6af0c88f27 Fix IEye not updating always (#4368) 2023-09-13 11:51:11 +10:00
metalgearsloth
5f05b0aa2a Version: 159.0.3 2023-09-13 11:43:34 +10:00
metalgearsloth
e6c335b6cd Fix nent deleted entity handling (#4379) 2023-09-13 11:22:21 +10:00
metalgearsloth
5cd8e8276e Version: 159.0.2 2023-09-13 00:33:43 +10:00
metalgearsloth
22aeec45f9 Minor resolve light update (#4366) 2023-09-13 00:32:02 +10:00
metalgearsloth
aed53fb63d Fix lights not updating (#4377) 2023-09-13 00:31:45 +10:00
metalgearsloth
5ebe97aec1 Version: 159.0.1 2023-09-12 13:30:52 +10:00
metalgearsloth
3ff374a4af Remove unnecessary meta getcomp (#4376) 2023-09-12 13:30:15 +10:00
metalgearsloth
c5bcf853ac Some netstate fixes (#4375) 2023-09-12 13:05:20 +10:00
metalgearsloth
0624ac36cd Remove PhysicsComponent ref from fixtures (#4374) 2023-09-12 12:49:33 +10:00
metalgearsloth
dd906e9b01 Version: 159.0.0 2023-09-11 21:26:25 +10:00
DrSmugleaf
7f99b44e5c Remove inactive reviewers from CODEOWNERS (#4365) 2023-09-11 20:51:55 +10:00
metalgearsloth
268eb862ea Refactor UI a bit more to shared (#4343) 2023-09-11 20:21:23 +10:00
Leon Friedrich
467f518421 Make entity-deletion take in nullables (#4363) 2023-09-11 20:19:04 +10:00
DrSmugleaf
4666a87aa5 Add obsoletion warnings to serialization source generated methods, xmldocs (#4364) 2023-09-11 20:15:07 +10:00
metalgearsloth
25007a743f Remove lights component reference (#4316) 2023-09-11 19:17:28 +10:00
metalgearsloth
8ce3a03136 Version: 158.0.0 2023-09-11 17:41:27 +10:00
metalgearsloth
c4d6690a71 Remove SharedEyeComponent (#4309) 2023-09-11 16:15:08 +10:00
Kara
5486bc7686 Add tile overlay edge priority (#4341) 2023-09-11 15:30:49 +10:00
ElectroJr
cdf44ef3d9 Version: 157.1.0 2023-09-11 01:27:55 -04:00
metalgearsloth
49ec5b9ca3 Use RichTextLabel for tooltips (#4331) 2023-09-11 13:55:13 +10:00
ElectroJr
8b53b89423 Version: 157.0.0 2023-09-10 19:46:20 -04:00
metalgearsloth
3fd731d917 Network entity ids (#4252) 2023-09-11 09:42:55 +10:00
metalgearsloth
cb1d4ae843 Version: 156.0.0 2023-09-10 21:48:19 +10:00
metalgearsloth
039b70f502 Revert "Remove IContainer and move some functions to the system (#4351)" (#4361) 2023-09-10 21:46:17 +10:00
metalgearsloth
7892cc895f Tooltip QoL (#4330) 2023-09-10 20:23:52 +10:00
ElectroJr
77108284b8 Version: 155.0.0 2023-09-09 22:26:10 -04:00
Leon Friedrich
5e21dbdd7f Remove IContainer and move some functions to the system (#4351) 2023-09-10 12:17:00 +10:00
ShadowCommander
8274623edb Add a command to hide replay UI (#4355) 2023-09-10 10:49:34 +10:00
Leon Friedrich
e923d69083 Miscellaneous replay related changes (#4354) 2023-09-10 10:48:42 +10:00
PrPleGoo
6e8ab5ce78 Ignore deleted components while raising events. (#4311) 2023-09-10 10:48:00 +10:00
metalgearsloth
f905ea631b Raise MapInitEvent on components added after spawn (#4290) 2023-09-10 10:47:18 +10:00
Pieter-Jan Briers
be54c41891 Fix localization file error formatting. 2023-09-09 20:10:55 +02:00
DrSmugleaf
33184ecfa5 Version: 154.2.0 2023-09-08 14:44:37 -07:00
Morb
11cf0c1703 Fix turn into Invalid direction (#4350) 2023-09-08 11:14:14 +10:00
DrSmugleaf
528544b7a2 Remove redundant new() constraint from EntitySystem.AddComp (#4353) 2023-09-07 11:10:35 +10:00
chromiumboy
8571d7e7b5 Added method to search nested containers for a specified component (#4337) 2023-09-06 10:24:14 +10:00
DrSmugleaf
0f06423b7a Remove RobustAutoGenerated from partials generated by serialization (#4338) 2023-09-06 10:23:28 +10:00
PrPleGoo
eb9e0ffefc Advertise to multiple hubs simultaneously (#4285) 2023-09-06 00:25:11 +02:00
metalgearsloth
903619ecef Version: 154.1.0 2023-09-05 00:14:15 +10:00
DrSmugleaf
879c6ea538 Make joint initialization only log under IsFirstTimePredicted (#4346) 2023-09-02 21:45:21 -07:00
metalgearsloth
5478545aeb Mark Dirty(comp) as obsolete (#4344) 2023-09-03 06:56:04 +10:00
Wrexbe (Josh)
650929dcbb Add Timespan helpers (#4342) 2023-08-31 11:51:06 -07:00
DrSmugleaf
a289659b49 Version: 154.0.0 2023-08-30 21:22:39 -07:00
DrSmugleaf
85d15c21e1 Move IPlayerData interface to shared (#4339) 2023-08-30 21:17:32 -07:00
DrSmugleaf
bcd1566440 Respect ignored prototypes even if the kind name is registered (#4340) 2023-08-30 21:01:47 -07:00
Julian Giebel
749ac2c364 Fix some multiline edit issues (#4332) 2023-08-30 09:36:36 +10:00
Pieter-Jan Briers
5eed3bc281 Make toolshed stuff oneOff IoC injections.
Removes a ton of IoC injector delegates.
2023-08-29 21:56:57 +02:00
Pieter-Jan Briers
d78f378493 More event sources 2023-08-29 21:43:02 +02:00
DrSmugleaf
eef44c15cf Version: 153.0.0 2023-08-28 16:00:34 -07:00
DrSmugleaf
3d1b2418f9 Remove redundant DebugTools.AssertNotNull(netId) in ClientGameStateManager (#4333) 2023-08-28 15:57:46 -07:00
DrSmugleaf
6b49a86ee5 Make EntityManager.AddComponent with a comp instance set the owner if its default, add system proxy (#4328) 2023-08-28 15:31:17 -07:00
metalgearsloth
cd13cd3cd8 Delete EntityDeletedMessage (#4329) 2023-08-29 05:21:29 +10:00
metalgearsloth
2b8d8d6636 Remove UI comprefs (#4320) 2023-08-28 03:49:57 +10:00
Pieter-Jan Briers
409fe1a125 Some warning fixes 2023-08-27 15:35:15 +02:00
Pieter-Jan Briers
ab5db4641c Update Lidgren to v0.2.6 2023-08-27 13:18:19 +02:00
metalgearsloth
064e8ee365 Minor CompAdd stuff (#4327) 2023-08-27 12:54:09 +02:00
metalgearsloth
02dcff7eae Remove CollisionWake comp removal sub (#4326) 2023-08-27 15:47:46 +10:00
metalgearsloth
e1e5f8de54 Fix master build (#4325) 2023-08-27 15:33:15 +10:00
Leon Friedrich
d5ba822a79 Remove redundant prototype resolving (#4322) 2023-08-27 15:24:25 +10:00
Leon Friedrich
f448c6b8fa Add RecursiveMoveBenchmark (#4323) 2023-08-27 15:24:04 +10:00
Pieter-Jan Briers
5e1d80be35 Attempts to fix replay recording performance issues.
Replays now use a dedicated thread (rather than thread pool) for write operations.

Moved batch operations to this thread as well. They were previously happening during PVS. Looking at some trace files these compression ops can easily take 5+ ms in some cases, so moving them somewhere else is appreciated.

Added EventSource instrumentation for PVS and replay recording.
2023-08-27 02:15:15 +02:00
DrSmugleaf
01546f32da Version: 152.0.0 2023-08-26 15:31:30 -07:00
DrSmugleaf
aeeaaaefc5 Fix not running hooks when copying non-byref data definition fields without a custom serializer (#4324) 2023-08-26 15:20:46 -07:00
Leon Friedrich
b6c8060af1 Add new PVS test (#4312) 2023-08-26 22:23:32 +10:00
DrSmugleaf
99685838da Fix entity spawn tests having instance per test lifecycle with non static setup and tear downs (#4321) 2023-08-26 22:16:47 +10:00
ike709
8917b29255 Convert Tile.TypeId to an int (#4307)
Co-authored-by: ike709 <ike709@github.com>
Co-authored-by: DrSmugleaf <drsmugleaf@gmail.com>
2023-08-26 22:16:14 +10:00
DrSmugleaf
f0c4d7c5eb Update CI to use setup-dotnet 3.2.0 and checkout 3.6.0 (#4319) 2023-08-25 15:45:19 -07:00
Pieter-Jan Briers
6a00c62d3c Allow content to implement own logic for BUI range checks. (#4301)
They were currently inconsistent with interaction logic in SS14. Please fix and thank.
2023-08-26 09:29:43 +12:00
metalgearsloth
fc3116fca5 Remove ComponentDeleted C# event (#4317)
No one's used it for 12 years probably no reason to obs first.
2023-08-26 09:24:33 +12:00
metalgearsloth
98c1397b3a Remove EntityStarted C# event (#4318) 2023-08-25 19:57:43 +02:00
Leon Friedrich
2464bb6c2f Remove and obsolete ComponentExt functions (#4313) 2023-08-25 23:20:39 +10:00
Leon Friedrich
709142acee Fix prototype manager not being initialized in tests (#4294) 2023-08-24 19:02:02 +02:00
Kevin Zheng
af4e3e5e1c Remove personally-identifiable file paths from client logs (#4267) 2023-08-24 18:56:48 +02:00
Kevin Zheng
d51a18c6ea Fix build with USE_SYSTEM_SQLITE (#4266) 2023-08-24 18:55:54 +02:00
metalgearsloth
d5c3d4c0c9 Add system to CompNetworkGenerator (#4310)
Robust doesn't global using this but content does so any automatic comp states on engine don't work.
2023-08-24 04:26:30 -07:00
metalgearsloth
a4474d8df8 Remove IContainerManager (#4308) 2023-08-24 16:39:35 +10:00
DrSmugleaf
d66f7c7c06 Disable obsoletion and inherited member hidden warnings in serialization source generated code (#4302) 2023-08-24 13:04:32 +10:00
DrSmugleaf
b6879869d6 Add support for long values in CVars (#4299) 2023-08-24 00:57:09 +02:00
Errant
815b8e0c48 removed warning for glibc (#4296) 2023-08-24 00:56:38 +02:00
Arimah Greene
ef4e3baa7f Fix Timer drift (#4300) 2023-08-24 00:54:30 +02:00
Moony
270ddb5a53 Update CODEOWNERS 2023-08-23 16:23:38 -05:00
379 changed files with 8617 additions and 4589 deletions

19
.github/CODEOWNERS vendored
View File

@@ -1,18 +1,9 @@
# Last match in file takes precedence.
# Ping for all PRs
* @Acruid @PJB3005 @ZoldorfTheWizard
* @PJB3005 @DrSmugleaf
/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

View File

@@ -7,12 +7,12 @@ jobs:
docfx:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x

View File

@@ -15,12 +15,12 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Install dependencies

View File

@@ -35,12 +35,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x

View File

@@ -16,12 +16,12 @@ jobs:
$ver = [regex]::Match($env:GITHUB_REF, "refs/tags/v?(.+)").Groups[1].Value
echo ("::set-output name=version::{0}" -f $ver)
- uses: actions/checkout@v2
- uses: actions/checkout@v3.6.0
with:
submodules: true
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x

View File

@@ -13,13 +13,13 @@ jobs:
steps:
- name: Check out content
uses: actions/checkout@v2
uses: actions/checkout@v3.6.0
with:
repository: space-wizards/space-station-14
submodules: recursive
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3.2.0
with:
dotnet-version: 7.0.x
- name: Disable submodule autoupdate

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,6 +54,331 @@ END TEMPLATE-->
*None yet*
## 162.2.2
## 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.0
### Breaking changes
* Revert container changes from 155.0.0.
## 155.0.0
### Breaking changes
* MapInitEvent now gets raised for components that get added to entities that have already been map-initialized.
### New features
* VirtualWritableDirProvider now supports file renaming/moving.
* Added a new command for toggling the replay UI (`replay_toggleui`).
### Bugfixes
* Fixed formatting of localization file errors.
* Directed event subscriptions will no longer error if the corresponding component is queued for deletion.
## 154.2.0
### New features
* Added support for advertising to multiple hubs simultaneously.
* Added new functions to ContainerSystem that recursively look for a component on a contained entity's parents.
### Bugfixes
* Fix Direction.TurnCw/TurnCcw to South returning Invalid.
## 154.1.0
### New features
* Add MathHelper.Max for TimeSpans.
### Bugfixes
* Make joint initialisation only log under IsFirstTimePredicted on client.
### Other
* Mark the proxy Dirty(component) as obsolete in line with EntityManager (Dirty(EntityUid, Component) should be used in its place).
## 154.0.0
### Breaking changes
* Change ignored prototypes to skip prototypes even if the prototype type is found.
* Moved IPlayerData interface to shared.
### New features
* Added a multiline text submit keybind function.
### Bugfixes
* Fixed multiline edits scrollbar margins.
### Internal
* Added more event sources.
* Made Toolshed types oneOff IoC injections.
## 153.0.0
### Breaking changes
* Removed SharedUserInterfaceComponent component references.
* Removed EntityDeletedMessage.
### Other
* Performance improvements for replay recording.
* Lidgren has been updated to [v0.2.6](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.2.6/RELEASE-NOTES.md).
* Make EntityManager.AddComponent with a component instance set the owner if its default, add system proxy for it.
### Internal
* Added some `EventSource` providers for PVS and replay recording: `Robust.Pvs` and `Robust.ReplayRecording`.
* Added RecursiveMoveBenchmark.
* Removed redundant prototype resolving.
* Removed CollisionWake component removal subscription.
* Removed redundant DebugTools.AssertNotNull(netId) in ClientGameStateManager
## 152.0.0
### Breaking changes
* `Robust.Server.GameObjects.BoundUserInterface.InteractionRangeSqrd` is now a get-only property. Modify `InteractionRange` instead if you want to change it on active UIs.
* Remove IContainerManager.
* Remove and obsolete ComponentExt methods.
* Remove EntityStarted and ComponentDeleted C# events.
* Convert Tile.TypeId to an int. Old maps that were saved with TypeId being an ushort will still be properly deserialized.
### New features
* `BoundUserInterfaceCheckRangeEvent` can be used to implement custom logic for BUI range checks.
* Add support for long values in CVars.
* Allow user code to implement own logic for bound user interface range checks.
### Bugfixes
* Fix timers counting down slower than real time and drifting.
* Add missing System using statement to generated component states.
* Fix build with USE_SYSTEM_SQLITE.
* Fix prototype manager not being initialized in robust server simulation tests.
* Fix not running serialization hooks when copying non-byref data definition fields without a custom type serializer.
### Other
* Remove warning for glibc 2.37.
* Remove personally-identifiable file paths from client logs.
### Internal
* Disable obsoletion and inherited member hidden warnings in serialization source generated code.
* Update CI workflows to use setup-dotnet 3.2.0 and checkout 3.6.0.
* Fix entity spawn tests having instance per test lifecycle with a non static OneTimeTearDown method.
* Add new PVS test to check that there is no issue with entity states referencing other entities that the client is not yet aware of.
## 151.0.0

View File

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

View File

@@ -0,0 +1,17 @@
# 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

@@ -0,0 +1,8 @@
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,5 +1,6 @@
## 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
@@ -8,4 +9,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

@@ -0,0 +1,171 @@
using System;
using System.Linq;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Server.Containers;
using Robust.Server.GameStates;
using Robust.Shared;
using Robust.Shared.Analyzers;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Transform;
/// <summary>
/// This benchmark tests various transform/move related functions with an entity that has many children.
/// </summary>
[Virtual, MemoryDiagnoser]
public class RecursiveMoveBenchmark
{
private ISimulation _simulation = default!;
private IEntityManager _entMan = default!;
private SharedTransformSystem _transform = default!;
private ContainerSystem _container = default!;
private PvsSystem _pvs = default!;
private EntityCoordinates _mapCoords;
private EntityCoordinates _gridCoords;
private EntityUid _ent;
private EntityUid _child;
private TransformComponent _childXform = default!;
private EntityQuery<TransformComponent> _query;
[GlobalSetup]
public void GlobalSetup()
{
_simulation = RobustServerSimulation
.NewSimulation()
.InitializeInstance();
if (!_simulation.Resolve<IConfigurationManager>().GetCVar(CVars.NetPVS))
throw new InvalidOperationException("PVS must be enabled");
_entMan = _simulation.Resolve<IEntityManager>();
_transform = _entMan.System<SharedTransformSystem>();
_container = _entMan.System<ContainerSystem>();
_pvs = _entMan.System<PvsSystem>();
_query = _entMan.GetEntityQuery<TransformComponent>();
// Create map & grid
var mapMan = _simulation.Resolve<IMapManager>();
var mapSys = _entMan.System<SharedMapSystem>();
var mapId = mapMan.CreateMap();
var map = mapMan.GetMapEntityId(mapId);
var gridComp = mapMan.CreateGrid(mapId);
var grid = gridComp.Owner;
_gridCoords = new EntityCoordinates(grid, .5f, .5f);
_mapCoords = new EntityCoordinates(map, 100, 100);
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));
// Next, we will spawn our test entity. This entity will have a complex transform/container hierarchy.
// This is intended to be representative of a typical SS14 player entity, with organs. clothing, and a full backpack.
_ent = _entMan.Spawn();
// Quick check that SetCoordinates actually changes the parent as expected
// I.e., ensure that grid-traversal code doesn't just dump the entity on the map.
_transform.SetCoordinates(_ent, _gridCoords);
if (_query.GetComponent(_ent).ParentUid != _gridCoords.EntityId)
throw new Exception("Grid traversal error.");
_transform.SetCoordinates(_ent, _mapCoords);
if (_query.GetComponent(_ent).ParentUid != _mapCoords.EntityId)
throw new Exception("Grid traversal error.");
// Add 5 direct children in slots to represent clothing.
for (var i = 0; i < 5; i++)
{
var id = $"inventory{i}";
_container.EnsureContainer<ContainerSlot>(_ent, id);
if (!_entMan.TrySpawnInContainer(null, _ent, id, out _))
throw new Exception($"Failed to setup entity");
}
// body parts
_container.EnsureContainer<Container>(_ent, "body");
for (var i = 0; i < 5; i++)
{
// Simple organ
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out _))
throw new Exception($"Failed to setup entity");
// body part that has another body part / limb
if (!_entMan.TrySpawnInContainer(null, _ent, "body", out var limb))
throw new Exception($"Failed to setup entity");
_container.EnsureContainer<ContainerSlot>(limb.Value, "limb");
if (!_entMan.TrySpawnInContainer(null, limb.Value, "limb", out _))
throw new Exception($"Failed to setup entity");
}
// Backpack
_container.EnsureContainer<ContainerSlot>(_ent, "inventory-backpack");
if (!_entMan.TrySpawnInContainer(null, _ent, "inventory-backpack", out var backpack))
throw new Exception($"Failed to setup entity");
// Misc backpack contents.
var backpackStorage = _container.EnsureContainer<Container>(backpack.Value, "storage");
for (var i = 0; i < 10; i++)
{
if (!_entMan.TrySpawnInContainer(null, backpack.Value, "storage", out _))
throw new Exception($"Failed to setup entity");
}
// Emergency box inside of the backpack
var box = backpackStorage.ContainedEntities.First();
var boxContainer = _container.EnsureContainer<Container>(box, "storage");
for (var i = 0; i < 10; i++)
{
if (!_entMan.TrySpawnInContainer(null, box, "storage", out _))
throw new Exception($"Failed to setup entity");
}
// Deepest child.
_child = boxContainer.ContainedEntities.First();
_childXform = _query.GetComponent(_child);
_pvs.ProcessCollections();
}
/// <summary>
/// This implicitly measures move events, including PVS and entity lookups. Though given that most of the entities
/// are in containers, this will bias the entity lookup aspect.
/// </summary>
[Benchmark]
public void MoveEntity()
{
_transform.SetCoordinates(_ent, _gridCoords);
_transform.SetCoordinates(_ent, _mapCoords);
}
/// <summary>
/// Like <see cref="MoveEntity"/>, but also processes queued PVS chunk updates.
/// </summary>
[Benchmark]
public void MoveAndUpdateChunks()
{
_transform.SetCoordinates(_ent, _gridCoords);
_pvs.ProcessCollections();
_transform.SetCoordinates(_ent, _mapCoords);
_pvs.ProcessCollections();
}
[Benchmark]
public Vector2 GetWorldPos()
{
return _transform.GetWorldPosition(_childXform);
}
[Benchmark]
public EntityUid GetRootUid()
{
var xform = _childXform;
while (xform.ParentUid.IsValid())
{
xform = _query.GetComponent(xform.ParentUid);
}
return xform.ParentUid;
}
}

View File

@@ -2,11 +2,13 @@ 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

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

View File

@@ -78,14 +78,7 @@ 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)
{
@@ -296,6 +289,7 @@ 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";
@@ -310,7 +304,7 @@ namespace Robust.Client.Console.Commands
string indices = args[1];
if (!EntityUid.TryParse(args[0], out var gridUid))
if (!NetEntity.TryParse(args[0], out var gridNet))
{
shell.WriteError($"{args[0]} is not a valid entity UID.");
return;
@@ -322,7 +316,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (_map.TryGetGrid(gridUid, out var grid))
if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid))
{
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
@@ -430,6 +424,7 @@ 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";
@@ -442,7 +437,8 @@ namespace Robust.Client.Console.Commands
return;
}
if (!EntityUid.TryParse(args[0], out var gridUid))
if (!NetEntity.TryParse(args[0], out var gridUidNet) ||
!_entManager.TryGetEntity(gridUidNet, out var gridUid))
{
shell.WriteLine($"{args[0]} is not a valid entity UID.");
return;

View File

@@ -167,7 +167,6 @@ namespace Robust.Client
_reflectionManager.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDefaultPrototypes();
_prototypeManager.ResolveResults();
_userInterfaceManager.Initialize();
_eyeManager.Initialize();
_entityManager.Initialize();

View File

@@ -0,0 +1,53 @@
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

@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Replays;
@@ -18,7 +17,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Manager for entities -- controls things like template loading and instantiation
/// </summary>
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
public sealed partial class ClientEntityManager : EntityManager, IClientEntityManagerInternal
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientNetManager _networkManager = default!;
@@ -27,8 +26,6 @@ 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();
@@ -39,13 +36,16 @@ 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, EntityUid uid)
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName)
{
return base.CreateEntity(prototypeName, uid);
return base.CreateEntity(prototypeName);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)
@@ -66,9 +66,12 @@ namespace Robust.Client.GameObjects
base.DirtyEntity(uid, meta);
}
public override void QueueDeleteEntity(EntityUid uid)
public override void QueueDeleteEntity(EntityUid? uid)
{
if (uid.IsClientSide())
if (uid == null)
return;
if (IsClientSide(uid.Value))
{
base.QueueDeleteEntity(uid);
return;
@@ -79,7 +82,7 @@ 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)}. Trace: {Environment.StackTrace}");
LogManager.RootSawmill.Error($"Predicting the queued deletion of a networked entity: {ToPrettyString(uid.Value)}. Trace: {Environment.StackTrace}");
}
/// <inheritdoc />
@@ -90,12 +93,16 @@ namespace Robust.Client.GameObjects
base.Dirty(uid, component, meta);
}
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
[return: NotNullIfNotNull("uid")]
public override EntityStringRepresentation? ToPrettyString(EntityUid? uid)
{
if (uid == null)
return null;
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
return base.ToPrettyString(uid) with { Session = _playerManager.LocalPlayer.Session };
else
return base.ToPrettyString(uid);
return base.ToPrettyString(uid).Value with { Session = _playerManager.LocalPlayer.Session };
return base.ToPrettyString(uid);
}
public override void RaisePredictiveEvent<T>(T msg)

View File

@@ -1,136 +0,0 @@
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

@@ -1,85 +0,0 @@
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

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

View File

@@ -12,6 +12,8 @@ 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;
@@ -26,8 +28,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
{
@@ -1347,11 +1349,11 @@ namespace Robust.Client.GameObjects
state = GetFallbackState(resourceCache);
}
return state.Directions switch
return state.RsiDirections switch
{
RSI.State.DirectionType.Dir1 => 1,
RSI.State.DirectionType.Dir4 => 4,
RSI.State.DirectionType.Dir8 => 8,
RsiDirectionType.Dir1 => 1,
RsiDirectionType.Dir4 => 4,
RsiDirectionType.Dir8 => 8,
_ => throw new ArgumentOutOfRangeException()
};
}
@@ -1389,7 +1391,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(RSI.State.DirectionType.Dir8),
Color, NoRotation, entities.GetComponent<TransformComponent>(Owner).WorldRotation.ToRsiDirection(RsiDirectionType.Dir8),
DirectionOverride
);
@@ -1689,7 +1691,7 @@ namespace Robust.Client.GameObjects
int ISpriteLayer.AnimationFrame => AnimationFrame;
public RSIDirection EffectiveDirection(Angle worldRotation)
public RsiDirection EffectiveDirection(Angle worldRotation)
{
if (State == default)
{
@@ -1710,23 +1712,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.Directions == RSI.State.DirectionType.Dir1)
if (state.RsiDirections == RsiDirectionType.Dir1)
{
return RSIDirection.South;
return RsiDirection.South;
}
else
{
RSIDirection dir;
RsiDirection dir;
if (overrideDirection != null)
{
dir = overrideDirection.Value.Convert(state.Directions);
dir = overrideDirection.Value.Convert(state.RsiDirections);
}
else
{
dir = worldRotation.ToRsiDirection(state.Directions);
dir = worldRotation.ToRsiDirection(state.RsiDirections);
}
return dir.OffsetRsiDir(DirOffset);
@@ -1882,20 +1884,20 @@ namespace Robust.Client.GameObjects
else if (_parent.SnapCardinals && (!_parent.GranularLayersRendering || RenderingStrategy == LayerRenderingStrategy.UseSpriteStrategy)
|| _parent.GranularLayersRendering && RenderingStrategy == LayerRenderingStrategy.SnapToCardinals)
{
DebugTools.Assert(_actualState == null || _actualState.Directions == RSI.State.DirectionType.Dir1);
DebugTools.Assert(_actualState == null || _actualState.RsiDirections == RsiDirectionType.Dir1);
size = new Vector2(longestSide, longestSide);
}
else
{
// Build the bounding box based on how many directions the sprite has
size = (_actualState?.Directions) switch
size = (_actualState?.RsiDirections) 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.
RSI.State.DirectionType.Dir4 => new Vector2(longestSide, longestSide),
RsiDirectionType.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
RSI.State.DirectionType.Dir8 => new Vector2(longestRotatedSide, longestRotatedSide),
RsiDirectionType.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
@@ -1929,9 +1931,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
{
@@ -1956,11 +1958,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(RSI.State.DirectionType dirType, Angle angle)
public static RsiDirection GetDirection(RsiDirectionType dirType, Angle angle)
{
if (dirType == RSI.State.DirectionType.Dir1)
return RSIDirection.South;
else if (dirType == RSI.State.DirectionType.Dir8)
if (dirType == RsiDirectionType.Dir1)
return RsiDirection.South;
else if (dirType == RsiDirectionType.Dir8)
return angle.GetDir().Convert(dirType);
// For 4-directional sprites, as entities are often moving & facing diagonally, we will slightly bias the
@@ -1973,10 +1975,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,
};
}
@@ -1988,7 +1990,7 @@ namespace Robust.Client.GameObjects
if (!Visible || Blank)
return;
var dir = _actualState == null ? RSIDirection.South : GetDirection(_actualState.Directions, angle);
var dir = _actualState == null ? RsiDirection.South : GetDirection(_actualState.RsiDirections, angle);
// Set the drawing transform for this layer
GetLayerDrawMatrix(dir, out var layerMatrix);
@@ -1998,7 +2000,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.Directions);
dir = overrideDirection.Value.Convert(_actualState.RsiDirections);
dir = dir.OffsetRsiDir(DirOffset);
// Get the correct directional texture from the state, and draw it!
@@ -2021,7 +2023,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

@@ -128,7 +128,7 @@ namespace Robust.Client.GameObjects
return;
}
if (component.Owner.IsClientSide() || !animatedComp.NetSyncEnabled)
if (IsClientSide(component.Owner) || !animatedComp.NetSyncEnabled)
continue;
var reg = _compFact.GetRegistration(animatedComp);

View File

@@ -81,9 +81,13 @@ public sealed class AudioSystem : SharedAudioSystem
#region Event Handlers
private void PlayAudioEntityHandler(PlayAudioEntityMessage ev)
{
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);
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);
if (stream != null)
stream.NetIdentifier = ev.Identifier;
@@ -98,7 +102,10 @@ public sealed class AudioSystem : SharedAudioSystem
private void PlayAudioPositionalHandler(PlayAudioPositionalMessage ev)
{
var stream = (PlayingStream?) Play(ev.FileName, ev.Coordinates, ev.FallbackCoordinates, ev.AudioParams, false);
var coords = GetCoordinates(ev.Coordinates);
var fallback = GetCoordinates(ev.FallbackCoordinates);
var stream = (PlayingStream?) Play(ev.FileName, coords, fallback, ev.AudioParams, false);
if (stream != null)
stream.NetIdentifier = ev.Identifier;
}
@@ -383,8 +390,8 @@ public sealed class AudioSystem : SharedAudioSystem
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
{
FileName = filename,
EntityUid = entity,
FallbackCoordinates = fallbackCoordinates ?? default,
NetEntity = GetNetEntity(entity),
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates) ?? default,
AudioParams = audioParams ?? AudioParams.Default
});
}
@@ -437,8 +444,8 @@ public sealed class AudioSystem : SharedAudioSystem
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = coordinates,
FallbackCoordinates = fallbackCoordinates,
Coordinates = GetNetCoordinates(coordinates),
FallbackCoordinates = GetNetCoordinates(fallbackCoordinates),
AudioParams = audioParams ?? AudioParams.Default
});
}

View File

@@ -1,3 +1,4 @@
using System;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
@@ -5,13 +6,11 @@ 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.Physics.Components;
using Robust.Shared.Serialization;
using static Robust.Shared.Containers.ContainerManagerComponent;
namespace Robust.Client.GameObjects
@@ -23,14 +22,20 @@ 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<EntityUid, IContainer> ExpectedEntities = new();
public readonly Dictionary<NetEntity, BaseContainer> ExpectedEntities = new();
public override void Initialize()
{
base.Initialize();
_pointLightQuery = GetEntityQuery<PointLightComponent>();
_spriteQuery = GetEntityQuery<SpriteComponent>();
EntityManager.EntityInitialized += HandleEntityInitialized;
SubscribeLocalEvent<ContainerManagerComponent, ComponentHandleState>(HandleComponentState);
@@ -43,20 +48,18 @@ namespace Robust.Client.GameObjects
base.Shutdown();
}
protected override void ValidateMissingEntity(EntityUid uid, IContainer cont, EntityUid missing)
protected override void ValidateMissingEntity(EntityUid uid, BaseContainer cont, EntityUid missing)
{
DebugTools.Assert(ExpectedEntities.TryGetValue(missing, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(missing));
var netEntity = GetNetEntity(missing);
DebugTools.Assert(ExpectedEntities.TryGetValue(netEntity, out var expectedContainer) && expectedContainer == cont && cont.ExpectedEntities.Contains(netEntity));
}
private void HandleEntityInitialized(EntityUid uid)
{
if (!RemoveExpectedEntity(uid, out var container))
if (!RemoveExpectedEntity(GetNetEntity(uid), out var container))
return;
if (container.Deleted)
return;
container.Insert(uid);
container.Insert(uid, EntityManager, transform: TransformQuery.GetComponent(uid), meta: MetaQuery.GetComponent(uid));
}
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
@@ -64,23 +67,24 @@ namespace Robust.Client.GameObjects
if (args.Current is not ContainerManagerComponentState cast)
return;
var metaQuery = GetEntityQuery<MetaDataComponent>();
var xformQuery = GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(uid);
var xform = TransformQuery.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,
xformQuery.GetComponent(entity),
metaQuery.GetComponent(entity),
TransformQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
force: true,
reparent: false);
@@ -98,26 +102,32 @@ namespace Robust.Client.GameObjects
// Add new containers and update existing contents.
foreach (var (containerType, id, showEnts, occludesLight, entityUids) in cast.Containers.Values)
foreach (var (id, data) in cast.Containers)
{
if (!component.Containers.TryGetValue(id, out var container))
{
container = ContainerFactory(component, containerType, id);
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
container.Init(id, uid, component);
component.Containers.Add(id, container);
}
// sync show flag
container.ShowContents = showEnts;
container.OccludesLight = occludesLight;
DebugTools.Assert(container.ID == id);
container.ShowContents = data.ShowContents;
container.OccludesLight = data.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 (!entityUids.Contains(entity))
{
if (!stateEnts.Contains(entity))
toRemove.Add(entity);
}
}
foreach (var entity in toRemove)
@@ -125,8 +135,8 @@ namespace Robust.Client.GameObjects
container.Remove(
entity,
EntityManager,
xformQuery.GetComponent(entity),
metaQuery.GetComponent(entity),
TransformQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
force: true,
reparent: false);
@@ -134,13 +144,11 @@ namespace Robust.Client.GameObjects
}
// Remove entities that were expected, but have been removed from the container.
var removedExpected = new ValueList<EntityUid>();
foreach (var entityUid in container.ExpectedEntities)
var removedExpected = new ValueList<NetEntity>();
foreach (var netEntity in container.ExpectedEntities)
{
if (!entityUids.Contains(entityUid))
{
removedExpected.Add(entityUid);
}
if (!stateNetEnts.Contains(netEntity))
removedExpected.Add(netEntity);
}
foreach (var entityUid in removedExpected)
@@ -149,14 +157,20 @@ namespace Robust.Client.GameObjects
}
// Add new entities.
foreach (var entity in entityUids)
for (var i = 0; i < stateNetEnts.Length; i++)
{
if (!EntityManager.TryGetComponent(entity, out MetaDataComponent? meta))
var entity = stateEnts[i];
var netEnt = stateNetEnts[i];
if (!entity.IsValid())
{
AddExpectedEntity(entity, container);
DebugTools.Assert(netEnt.IsValid());
AddExpectedEntity(netEnt, 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
@@ -166,18 +180,18 @@ namespace Robust.Client.GameObjects
// containers/players.
if ((meta.Flags & MetaDataFlags.Detached) != 0)
{
AddExpectedEntity(entity, container);
AddExpectedEntity(netEnt, container);
continue;
}
if (container.Contains(entity))
continue;
RemoveExpectedEntity(entity, out _);
RemoveExpectedEntity(netEnt, out _);
container.Insert(entity, EntityManager,
xformQuery.GetComponent(entity),
TransformQuery.GetComponent(entity),
xform,
metaQuery.GetComponent(entity),
MetaQuery.GetComponent(entity),
force: true);
DebugTools.Assert(container.Contains(entity));
@@ -198,7 +212,7 @@ namespace Robust.Client.GameObjects
if (message.OldParent != null && message.OldParent.Value.IsValid())
return;
if (!RemoveExpectedEntity(message.Entity, out var container))
if (!RemoveExpectedEntity(GetNetEntity(message.Entity), out var container))
return;
if (xform.ParentUid != container.Owner)
@@ -208,84 +222,69 @@ namespace Robust.Client.GameObjects
return;
}
if (container.Deleted)
return;
container.Insert(message.Entity, EntityManager);
}
private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id)
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
{
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 DEBUG
var uid = GetEntity(netEntity);
var newContainer = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
newContainer.ID = id;
newContainer.Manager = component;
return newContainer;
}
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
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))
if (!ExpectedEntities.TryAdd(netEntity, 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[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);
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);
}
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);
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);
}
public bool RemoveExpectedEntity(EntityUid uid, [NotNullWhen(true)] out IContainer? container)
public bool RemoveExpectedEntity(NetEntity netEntity, [NotNullWhen(true)] out BaseContainer? container)
{
if (!ExpectedEntities.Remove(uid, out container))
if (!ExpectedEntities.Remove(netEntity, out container))
return false;
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);
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);
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, xformQuery, pointQuery, spriteQuery);
UpdateEntityRecursively(toUpdate);
}
_updateQueue.Clear();
}
private void UpdateEntityRecursively(
EntityUid entity,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery)
private void UpdateEntityRecursively(EntityUid entity)
{
// 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 = xformQuery.GetComponent(entity);
var xform = TransformQuery.GetComponent(entity);
var parent = xform.ParentUid;
var child = entity;
var spriteOccluded = false;
@@ -293,7 +292,7 @@ namespace Robust.Client.GameObjects
while (parent.IsValid() && (!spriteOccluded || !lightOccluded))
{
var parentXform = xformQuery.GetComponent(parent);
var parentXform = TransformQuery.GetComponent(parent);
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
{
spriteOccluded = spriteOccluded || !container.ShowContents;
@@ -308,24 +307,21 @@ 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, xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
UpdateEntity(entity, xform, 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 (pointQuery.TryGetComponent(entity, out var light))
if (_pointLightQuery.TryGetComponent(entity, out var light))
_lightSys.SetContainerOccluded(entity, lightOccluded, light);
var childEnumerator = xform.ChildEnumerator;
@@ -346,14 +342,14 @@ namespace Robust.Client.GameObjects
childLightOccluded = childLightOccluded || container.OccludesLight;
}
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, childSpriteOccluded, childLightOccluded);
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), childSpriteOccluded, childLightOccluded);
}
}
else
{
while (childEnumerator.MoveNext(out var child))
{
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), spriteOccluded, lightOccluded);
}
}
}

View File

@@ -1,6 +1,8 @@
using Robust.Client.Graphics;
using Robust.Client.Physics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
namespace Robust.Client.GameObjects;
@@ -13,8 +15,35 @@ public sealed class EyeSystem : SharedEyeSystem
{
base.Initialize();
SubscribeLocalEvent<EyeComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<EyeComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<EyeComponent, ComponentHandleState>(OnHandleState);
SubscribeLocalEvent<EyeComponent, PlayerDetachedEvent>(OnEyeDetached);
SubscribeLocalEvent<EyeComponent, PlayerAttachedEvent>(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(component);
}
private void OnEyeAttached(EntityUid uid, EyeComponent component, PlayerAttachedEvent args)
{
// TODO: This probably shouldn't be nullable bruv.
if (component._eye != null)
{
_eyeManager.CurrentEye = component._eye;
}
var ev = new EyeAttachedEvent(uid, component);
RaiseLocalEvent(uid, ref ev, true);
}
private void OnEyeDetached(EntityUid uid, EyeComponent component, PlayerDetachedEvent args)
{
_eyeManager.ClearCurrentEye();
}
private void OnInit(EntityUid uid, EyeComponent component, ComponentInit args)
@@ -22,39 +51,35 @@ public sealed class EyeSystem : SharedEyeSystem
component._eye = new Eye
{
Position = Transform(uid).MapPosition,
Zoom = component._setZoomOnInitialize,
DrawFov = component._setDrawFovOnInitialize
Zoom = component.Zoom,
DrawFov = component.DrawFov,
Rotation = component.Rotation,
};
if ((_eyeManager.CurrentEye == component._eye) != component._setCurrentOnInitialize)
{
if (component._setCurrentOnInitialize)
{
_eyeManager.ClearCurrentEye();
}
else
{
_eyeManager.CurrentEye = component._eye;
}
}
}
private void OnRemove(EntityUid uid, EyeComponent component, ComponentRemove args)
/// <inheritdoc />
public override void FrameUpdate(float frameTime)
{
component.Current = false;
}
var query = AllEntityQuery<EyeComponent>();
private void OnHandleState(EntityUid uid, EyeComponent component, ref ComponentHandleState args)
{
if (args.Current is not EyeComponentState state)
while (query.MoveNext(out var uid, out var eyeComponent))
{
return;
}
if (eyeComponent._eye == null)
continue;
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;
if (!TryComp<TransformComponent>(eyeComponent.Target, out var xform))
{
xform = Transform(uid);
eyeComponent.Target = null;
}
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);

View File

@@ -1,43 +0,0 @@
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

@@ -52,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, FullInputCmdMessage message, bool replay = false)
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, IFullInputCmdMessage message, bool replay = false)
{
#if DEBUG
@@ -78,14 +78,27 @@ namespace Robust.Client.GameObjects
continue;
// local handlers can block sending over the network.
if (handler.HandleCmdMessage(session, message))
if (handler.HandleCmdMessage(EntityManager, session, message))
{
return true;
}
}
// send it off to the server
DispatchInputCommand(message);
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);
return false;
}
@@ -93,7 +106,7 @@ namespace Robust.Client.GameObjects
/// Handle a predicted input command.
/// </summary>
/// <param name="inputCmd">Input command to handle as predicted.</param>
public void PredictInputCommand(FullInputCmdMessage inputCmd)
public void PredictInputCommand(IFullInputCmdMessage inputCmd)
{
DebugTools.AssertNotNull(_playerManager.LocalPlayer);
@@ -103,15 +116,16 @@ namespace Robust.Client.GameObjects
var session = _playerManager.LocalPlayer!.Session;
foreach (var handler in BindRegistry.GetHandlers(keyFunc))
{
if (handler.HandleCmdMessage(session, inputCmd)) break;
if (handler.HandleCmdMessage(EntityManager, session, inputCmd))
break;
}
Predicted = false;
}
private void DispatchInputCommand(FullInputCmdMessage message)
private void DispatchInputCommand(ClientFullInputCmdMessage clientMsg, FullInputCmdMessage message)
{
_stateManager.InputCommandDispatched(message);
_stateManager.InputCommandDispatched(clientMsg, message);
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, message.InputSequence);
}
@@ -152,7 +166,7 @@ namespace Robust.Client.GameObjects
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
coords, new ScreenCoordinates(0, 0, default), EntityUid.Invalid);
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
HandleInputCommand(localPlayer.Session, keyFunction, message);
}
@@ -206,8 +220,10 @@ namespace Robust.Client.GameObjects
SetEntityContextActive(_inputManager, controlled);
}
void IPostInjectInit.PostInject()
protected override void PostInject()
{
base.PostInject();
_sawmillInputContext = _logManager.GetSawmill("input.context");
}
}

View File

@@ -1,6 +1,8 @@
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;
@@ -15,59 +17,108 @@ 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)
{
UpdateMask(component);
SetMask(component.MaskPath, component);
}
internal void UpdateMask(PointLightComponent component)
public void SetMask(string? maskPath, PointLightComponent component)
{
if (component._maskPath is not null)
component.Mask = _resourceCache.GetResource<TextureResource>(component._maskPath);
if (maskPath is not null)
component.Mask = _resourceCache.GetResource<TextureResource>(maskPath);
else
component.Mask = null;
}
#region Setters
public void SetContainerOccluded(EntityUid uid, bool occluded, PointLightComponent? comp = null)
public void SetContainerOccluded(EntityUid uid, bool occluded, SharedPointLightComponent? comp = null)
{
if (!Resolve(uid, ref comp) || occluded == comp.ContainerOccluded)
if (!ResolveLight(uid, ref comp) || occluded == comp.ContainerOccluded || comp is not PointLightComponent clientComp)
return;
comp.ContainerOccluded = occluded;
Dirty(uid, comp);
if (comp.Enabled)
_lightTree.QueueTreeUpdate(uid, comp);
_lightTree.QueueTreeUpdate(uid, clientComp);
}
public override void SetEnabled(EntityUid uid, bool enabled, SharedPointLightComponent? comp = null)
{
if (!Resolve(uid, ref comp) || enabled == comp.Enabled)
if (!ResolveLight(uid, ref comp) || enabled == comp.Enabled || comp is not PointLightComponent clientComp)
return;
comp._enabled = enabled;
comp.Enabled = enabled;
RaiseLocalEvent(uid, new PointLightToggleEvent(comp.Enabled));
Dirty(uid, comp);
var cast = (PointLightComponent)comp;
if (!cast.ContainerOccluded)
_lightTree.QueueTreeUpdate(uid, cast);
if (!comp.ContainerOccluded)
_lightTree.QueueTreeUpdate(uid, clientComp);
}
public override void SetRadius(EntityUid uid, float radius, SharedPointLightComponent? comp = null)
{
if (!Resolve(uid, ref comp) || MathHelper.CloseToPercent(radius, comp.Radius))
if (!ResolveLight(uid, ref comp) || MathHelper.CloseToPercent(radius, comp.Radius) ||
comp is not PointLightComponent clientComp)
return;
comp._radius = radius;
comp.Radius = radius;
Dirty(uid, comp);
var cast = (PointLightComponent)comp;
if (cast.TreeUid != null)
_lightTree.QueueTreeUpdate(uid, cast);
if (clientComp.TreeUid != null)
_lightTree.QueueTreeUpdate(uid, clientComp);
}
#endregion
}

View File

@@ -4,6 +4,7 @@ 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,13 +1,12 @@
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!;
@@ -19,41 +18,22 @@ 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 = ev.Entity;
if (!TryComp<ClientUserInterfaceComponent>(uid, out var cmp))
var uid = GetEntity(ev.Entity);
if (!TryComp<UserInterfaceComponent>(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 = uid;
message.Entity = GetNetEntity(uid);
message.UiKey = uiKey;
// Raise as object so the correct type is used.
@@ -66,7 +46,7 @@ namespace Robust.Client.GameObjects
break;
case CloseBoundInterfaceMessage _:
TryCloseUi(uid, uiKey, remoteCall: true, uiComp: cmp);
TryCloseUi(message.Session, uid, uiKey, remoteCall: true, uiComp: cmp);
break;
default:
@@ -77,7 +57,7 @@ namespace Robust.Client.GameObjects
}
}
private bool TryOpenUi(EntityUid uid, Enum uiKey, ClientUserInterfaceComponent? uiComp = null)
private bool TryOpenUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? uiComp = null)
{
if (!Resolve(uid, ref uiComp))
return false;
@@ -85,7 +65,7 @@ namespace Robust.Client.GameObjects
if (uiComp.OpenInterfaces.ContainsKey(uiKey))
return false;
var data = uiComp._interfaces[uiKey];
var data = uiComp.MappedInterfaceData[uiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
@@ -96,36 +76,13 @@ namespace Robust.Client.GameObjects
uiComp.OpenInterfaces[uiKey] = boundInterface;
var playerSession = _playerManager.LocalPlayer?.Session;
if(playerSession != null)
if (playerSession != null)
{
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
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, EntityUid uid = default);
EntityUid CreateEntity(string? prototypeName);
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);

View File

@@ -1,7 +1,6 @@
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;
@@ -15,7 +14,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();
@@ -40,11 +39,11 @@ public sealed class ClientDirtySystem : EntitySystem
private void OnTerminate(ref EntityTerminatingEvent ev)
{
if (!_timing.InPrediction || ev.Entity.IsClientSide())
if (!_timing.InPrediction || IsClientSide(ev.Entity))
return;
// Client-side entity deletion is not supported and will cause errors.
Logger.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}");
Log.Error($"Predicting the deletion of a networked entity: {ToPrettyString(ev.Entity)}. Trace: {Environment.StackTrace}");
}
private void OnCompRemoved(RemovedComponentEventArgs args)
@@ -52,8 +51,9 @@ public sealed class ClientDirtySystem : EntitySystem
if (args.Terminating)
return;
var uid = args.BaseArgs.Owner;
var comp = args.BaseArgs.Component;
if (!_timing.InPrediction || comp.Owner.IsClientSide() || !comp.NetSyncEnabled)
if (!_timing.InPrediction || !comp.NetSyncEnabled || IsClientSide(uid, args.Meta))
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(comp.Owner).Add(netId.Value);
RemovedComponents.GetOrNew(uid).Add(netId.Value);
}
public void Reset()
@@ -73,7 +73,7 @@ public sealed class ClientDirtySystem : EntitySystem
private void OnEntityDirty(EntityUid e)
{
if (_timing.InPrediction && !e.IsClientSide())
if (_timing.InPrediction && !IsClientSide(e))
DirtyEntities.Add(e);
}
}

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Input;
@@ -47,8 +48,10 @@ namespace Robust.Client.GameStates
= new();
// Game state dictionaries that get used every tick.
private readonly Dictionary<EntityUid, (bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState?nextState)> _toApply = new();
private readonly Dictionary<EntityUid, EntityState> _toCreate = new();
private readonly Dictionary<EntityUid, (NetEntity NetEntity, MetaDataComponent Meta, bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState? nextState)> _toApply = new();
private readonly Dictionary<NetEntity, EntityState> _toCreate = new();
private readonly Dictionary<ushort, (IComponent Component, ComponentState? curState, ComponentState? nextState)> _compStateWork = new();
private readonly Dictionary<EntityUid, HashSet<Type>> _pendingReapplyNetStates = new();
private uint _metaCompNetId;
@@ -157,7 +160,7 @@ namespace Robust.Client.GameStates
}
}
public void InputCommandDispatched(FullInputCmdMessage message)
public void InputCommandDispatched(ClientFullInputCmdMessage clientMessage, FullInputCmdMessage message)
{
if (!IsPredictionEnabled)
{
@@ -201,7 +204,7 @@ namespace Robust.Client.GameStates
public void UpdateFullRep(GameState state, bool cloneDelta = false)
=> _processor.UpdateFullRep(state, cloneDelta);
public Dictionary<EntityUid, Dictionary<ushort, ComponentState>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
=> _processor.GetFullRep();
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
@@ -210,7 +213,7 @@ namespace Robust.Client.GameStates
PvsLeave?.Invoke(message);
}
public void QueuePvsDetach(List<EntityUid> entities, GameTick tick)
public void QueuePvsDetach(List<NetEntity> entities, GameTick tick)
{
_processor.AddLeavePvsMessage(entities, tick);
if (_replayRecording.IsRecording)
@@ -298,7 +301,7 @@ namespace Robust.Client.GameStates
_processor.UpdateFullRep(curState);
}
IEnumerable<EntityUid> createdEntities;
IEnumerable<NetEntity> createdEntities;
using (_prof.Group("ApplyGameState"))
{
if (_timing.LastProcessedTick < targetProcessedTick && nextState != null)
@@ -323,7 +326,7 @@ namespace Robust.Client.GameStates
catch (MissingMetadataException e)
{
// Something has gone wrong. Probably a missing meta-data component. Perhaps a full server state will fix it.
RequestFullState(e.Uid);
RequestFullState(e.NetEntity);
throw;
}
#endif
@@ -392,10 +395,10 @@ namespace Robust.Client.GameStates
}
}
public void RequestFullState(EntityUid? missingEntity = null)
public void RequestFullState(NetEntity? missingEntity = null)
{
_sawmill.Info("Requesting full server state");
_network.ClientSendMessage(new MsgStateRequestFull { Tick = _timing.LastRealTick , MissingEntity = missingEntity ?? EntityUid.Invalid });
_network.ClientSendMessage(new MsgStateRequestFull { Tick = _timing.LastRealTick , MissingEntity = missingEntity ?? NetEntity.Invalid });
_processor.RequestFullState();
}
@@ -472,7 +475,7 @@ namespace Robust.Client.GameStates
var countReset = 0;
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
var query = _entityManager.GetEntityQuery<MetaDataComponent>();
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
RemQueue<Component> toRemove = new();
// This is terrible, and I hate it.
@@ -486,7 +489,8 @@ namespace Robust.Client.GameStates
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($"Entity {entity} was made dirty.");
if (!_processor.TryGetLastServerStates(entity, out var last))
if (!metaQuery.TryGetComponent(entity, out var meta) ||
!_processor.TryGetLastServerStates(meta.NetEntity, out var last))
{
// Entity was probably deleted on the server so do nothing.
continue;
@@ -494,13 +498,8 @@ namespace Robust.Client.GameStates
countReset += 1;
var netComps = _entityManager.GetNetComponentsOrNull(entity);
if (netComps == null)
continue;
foreach (var (netId, comp) in netComps.Value)
foreach (var (netId, comp) in meta.NetComponents)
{
DebugTools.AssertNotNull(netId);
if (!comp.NetSyncEnabled)
continue;
@@ -547,13 +546,13 @@ namespace Robust.Client.GameStates
{
foreach (var netId in netIds)
{
if (_entities.HasComponent(entity, netId))
if (meta.NetComponents.ContainsKey(netId))
continue;
if (!last.TryGetValue(netId, out var state))
continue;
var comp = _entityManager.AddComponent(entity, netId);
var comp = _entityManager.AddComponent(entity, netId, meta);
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was removed: {comp.GetType()}");
@@ -565,7 +564,6 @@ namespace Robust.Client.GameStates
}
}
var meta = query.GetComponent(entity);
DebugTools.Assert(meta.EntityLastModifiedTick > _timing.LastRealTick);
meta.EntityLastModifiedTick = _timing.LastRealTick;
}
@@ -589,17 +587,18 @@ namespace Robust.Client.GameStates
/// initial server state for any newly created entity. It does this by simply using the standard <see
/// cref="IEntityManager.GetComponentState(IEventBus, IComponent)"/>.
/// </remarks>
private void MergeImplicitData(IEnumerable<EntityUid> createdEntities)
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
{
var outputData = new Dictionary<EntityUid, Dictionary<ushort, ComponentState>>();
var outputData = new Dictionary<NetEntity, Dictionary<ushort, ComponentState>>();
var bus = _entityManager.EventBus;
foreach (var createdEntity in createdEntities)
foreach (var netEntity in createdEntities)
{
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
var compData = new Dictionary<ushort, ComponentState>();
outputData.Add(createdEntity, compData);
outputData.Add(netEntity, compData);
foreach (var (netId, component) in _entityManager.GetNetComponents(createdEntity))
foreach (var (netId, component) in meta.NetComponents)
{
if (!component.NetSyncEnabled)
continue;
@@ -618,7 +617,7 @@ namespace Robust.Client.GameStates
_network.ClientSendMessage(new MsgStateAck() { Sequence = sequence });
}
public IEnumerable<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
public IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState)
{
using var _ = _timing.StartStateApplicationArea();
@@ -638,7 +637,7 @@ namespace Robust.Client.GameStates
_config.TickProcessMessages();
}
(IEnumerable<EntityUid> Created, List<EntityUid> Detached) output;
(IEnumerable<NetEntity> Created, List<NetEntity> Detached) output;
using (_prof.Group("Entity"))
{
output = ApplyEntityStates(curState, nextState);
@@ -657,7 +656,7 @@ namespace Robust.Client.GameStates
return output.Created;
}
private (IEnumerable<EntityUid> Created, List<EntityUid> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
private (IEnumerable<NetEntity> Created, List<NetEntity> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
{
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
@@ -666,6 +665,7 @@ namespace Robust.Client.GameStates
var enteringPvs = 0;
_toApply.Clear();
_toCreate.Clear();
_pendingReapplyNetStates.Clear();
var curSpan = curState.EntityStates.Span;
// Create new entities
@@ -676,21 +676,42 @@ namespace Robust.Client.GameStates
foreach (var es in curSpan)
{
if (metas.HasComponent(es.Uid))
if (_entityManager.TryGetEntity(es.NetEntity, out var nUid))
{
DebugTools.Assert(_entityManager.EntityExists(nUid));
continue;
}
count++;
var uid = es.Uid;
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
if (metaState == null)
throw new MissingMetadataException(uid);
_entities.CreateEntity(metaState.PrototypeId, uid);
_toCreate.Add(uid, es);
_toApply.Add(uid, (false, GameTick.Zero, es, null));
throw new MissingMetadataException(es.NetEntity);
var uid = _entities.CreateEntity(metaState.PrototypeId);
_toCreate.Add(es.NetEntity, es);
var newMeta = metas.GetComponent(uid);
_toApply.Add(uid, (es.NetEntity, newMeta, false, GameTick.Zero, es, null));
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entityManager.ClearNetEntity(newMeta.NetEntity);
_entityManager.SetNetEntity(uid, es.NetEntity, newMeta);
newMeta.LastStateApplied = curState.ToSequence;
// Check if there's any component states awaiting this entity.
if (_entityManager.PendingNetEntityStates.TryGetValue(es.NetEntity, out var value))
{
foreach (var (type, owner) in value)
{
var pending = _pendingReapplyNetStates.GetOrNew(owner);
pending.Add(type);
}
_entityManager.PendingNetEntityStates.Remove(es.NetEntity);
}
}
_prof.WriteValue("Count", ProfData.Int32(count));
@@ -698,10 +719,13 @@ namespace Robust.Client.GameStates
foreach (var es in curSpan)
{
if (!metas.TryGetComponent(es.Uid, out var meta) || _toCreate.ContainsKey(es.Uid))
{
if (_toCreate.ContainsKey(es.NetEntity))
continue;
var uid = _entityManager.GetEntity(es.NetEntity);
if (!metas.TryGetComponent(uid, out var meta))
continue;
}
bool isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
if (isEnteringPvs)
@@ -715,7 +739,7 @@ namespace Robust.Client.GameStates
continue;
}
_toApply.Add(es.Uid, (isEnteringPvs, meta.LastStateApplied, es, null));
_toApply.Add(uid, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
meta.LastStateApplied = curState.ToSequence;
}
@@ -729,22 +753,38 @@ namespace Robust.Client.GameStates
{
foreach (var es in nextState.EntityStates.Span)
{
var uid = es.Uid;
if (!metas.TryGetComponent(uid, out var meta))
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
continue;
// Does the next state actually have any future information about this entity that could be used for interpolation?
if (es.EntityLastModified != nextState.ToSequence)
continue;
if (_toApply.TryGetValue(uid, out var state))
_toApply[uid] = (state.EnteringPvs, state.LastApplied, state.curState, es);
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid.Value, out var exists);
if (exists)
state = (es.NetEntity, meta, state.EnteringPvs, state.LastApplied, state.curState, es);
else
_toApply[uid] = (false, GameTick.Zero, null, es);
state = (es.NetEntity, meta, false, GameTick.Zero, null, es);
}
}
// Check pending states and see if we need to force any entities to re-run component states.
foreach (var uid in _pendingReapplyNetStates.Keys)
{
// Original entity referencing the NetEntity may have been deleted.
if (!metas.TryGetComponent(uid, out var meta))
continue;
// State already being re-applied so don't bulldoze it.
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid, out var exists);
if (exists)
continue;
state = (meta.NetEntity, meta, false, GameTick.Zero, null, null);
}
var queuedBroadphaseUpdates = new List<(EntityUid, TransformComponent)>(enteringPvs);
// Apply entity states.
@@ -752,7 +792,7 @@ namespace Robust.Client.GameStates
{
foreach (var (entity, data) in _toApply)
{
HandleEntityState(entity, _entities.EventBus, data.curState,
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
if (!data.EnteringPvs)
@@ -767,6 +807,7 @@ namespace Robust.Client.GameStates
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
queuedBroadphaseUpdates.Add((entity, xform));
}
_prof.WriteValue("Count", ProfData.Int32(_toApply.Count));
}
@@ -830,37 +871,37 @@ namespace Robust.Client.GameStates
// Construct hashset for set.Contains() checks.
var entityStates = state.EntityStates.Span;
var stateEnts = new HashSet<EntityUid>(entityStates.Length);
var stateEnts = new HashSet<NetEntity>();
foreach (var entState in entityStates)
{
stateEnts.Add(entState.Uid);
stateEnts.Add(entState.NetEntity);
}
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
var currentEnts = _entities.GetEntities();
var toDelete = new List<EntityUid>(Math.Max(64, _entities.EntityCount - stateEnts.Count));
foreach (var ent in currentEnts)
// Client side entities won't need the transform, but that should always be a tiny minority of entities
var metaQuery = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
while (metaQuery.MoveNext(out var ent, out var metadata, out var xform))
{
if (ent.IsClientSide())
var netEnt = metadata.NetEntity;
if (metadata.NetEntity.IsClientSide())
{
if (deleteClientEntities)
toDelete.Add(ent);
continue;
}
if (stateEnts.Contains(ent) && metas.TryGetComponent(ent, out var meta))
if (stateEnts.Contains(netEnt))
{
if (resetAllEntities || meta.LastStateApplied > state.ToSequence)
meta.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it?
if (resetAllEntities || metadata.LastStateApplied > state.ToSequence)
metadata.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it?
continue;
}
if (!xforms.TryGetComponent(ent, out var xform))
continue;
// This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to
// null. First we will detach the parent in order to reduce the number of broadphase/lookup updates.
xformSys.DetachParentToNull(ent, xform);
@@ -873,7 +914,7 @@ namespace Robust.Client.GameStates
if (deleteClientChildren
&& !deleteClientEntities // don't add duplicates
&& child.Value.IsClientSide())
&& _entities.IsClientSide(child.Value))
{
toDelete.Add(child.Value);
}
@@ -888,7 +929,7 @@ namespace Robust.Client.GameStates
}
private void ProcessDeletions(
ReadOnlySpan<EntityUid> delSpan,
ReadOnlySpan<NetEntity> delSpan,
EntityQuery<TransformComponent> xforms,
EntityQuery<MetaDataComponent> metas,
SharedTransformSystem xformSys)
@@ -905,13 +946,19 @@ namespace Robust.Client.GameStates
using var _ = _prof.Group("Deletion");
foreach (var id in delSpan)
foreach (var netEntity in delSpan)
{
// Don't worry about this for later.
_entityManager.PendingNetEntityStates.Remove(netEntity);
if (!_entityManager.TryGetEntity(netEntity, out var id))
continue;
if (!xforms.TryGetComponent(id, out var xform))
continue; // Already deleted? or never sent to us?
// First, a single recursive map change
xformSys.DetachParentToNull(id, xform);
xformSys.DetachParentToNull(id.Value, xform);
// Then detach all children.
var childEnumerator = xform.ChildEnumerator;
@@ -921,12 +968,12 @@ namespace Robust.Client.GameStates
}
// Finally, delete the entity.
_entities.DeleteEntity(id);
_entities.DeleteEntity(id.Value);
}
_prof.WriteValue("Count", ProfData.Int32(delSpan.Length));
}
public void DetachImmediate(List<EntityUid> entities)
public void DetachImmediate(List<NetEntity> entities)
{
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
@@ -936,7 +983,7 @@ namespace Robust.Client.GameStates
Detach(GameTick.MaxValue, null, entities, metas, xforms, xformSys, containerSys, lookupSys);
}
private List<EntityUid> ProcessPvsDeparture(
private List<NetEntity> ProcessPvsDeparture(
GameTick toTick,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms,
@@ -945,7 +992,7 @@ namespace Robust.Client.GameStates
EntityLookupSystem lookupSys)
{
var toDetach = _processor.GetEntitiesToDetach(toTick, _pvsDetachBudget);
var detached = new List<EntityUid>();
var detached = new List<NetEntity>();
if (toDetach.Count == 0)
return detached;
@@ -967,16 +1014,18 @@ namespace Robust.Client.GameStates
private void Detach(GameTick maxTick,
GameTick? lastStateApplied,
List<EntityUid> entities,
List<NetEntity> entities,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms,
SharedTransformSystem xformSys,
ContainerSystem containerSys,
EntityLookupSystem lookupSys,
List<EntityUid>? detached = null)
List<NetEntity>? detached = null)
{
foreach (var ent in entities)
foreach (var netEntity in entities)
{
var ent = _entityManager.GetEntity(netEntity);
if (!metas.TryGetComponent(ent, out var meta))
continue;
@@ -1001,7 +1050,7 @@ namespace Robust.Client.GameStates
// In some cursed scenarios an entity inside of a container can leave PVS without the container itself leaving PVS.
// In those situations, we need to add the entity back to the list of expected entities after detaching.
IContainer? container = null;
BaseContainer? container = null;
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
@@ -1015,35 +1064,38 @@ namespace Robust.Client.GameStates
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
if (container != null)
containerSys.AddExpectedEntity(ent, container);
containerSys.AddExpectedEntity(netEntity, container);
}
detached?.Add(ent);
detached?.Add(netEntity);
}
}
private void InitializeAndStart(Dictionary<EntityUid, EntityState> toCreate)
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
{
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
#if EXCEPTION_TOLERANCE
HashSet<EntityUid> brokenEnts = new HashSet<EntityUid>();
var brokenEnts = new List<EntityUid>();
#endif
using (_prof.Group("Initialize Entity"))
{
foreach (var entity in toCreate.Keys)
foreach (var netEntity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
#endif
_entities.InitializeEntity(entity);
_entities.InitializeEntity(entity, metaQuery.GetComponent(entity));
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: ent={_entityManager.ToPrettyString(entity)}");
_sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(entity);
toCreate.Remove(netEntity);
}
#endif
}
@@ -1051,8 +1103,9 @@ namespace Robust.Client.GameStates
using (_prof.Group("Start Entity"))
{
foreach (var entity in toCreate.Keys)
foreach (var netEntity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
@@ -1065,7 +1118,7 @@ namespace Robust.Client.GameStates
_sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(entity);
toCreate.Remove(netEntity);
}
#endif
}
@@ -1079,17 +1132,16 @@ namespace Robust.Client.GameStates
#endif
}
private void HandleEntityState(EntityUid uid, IEventBus bus, EntityState? curState,
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
{
var size = (curState?.ComponentChanges.Span.Length ?? 0) + (nextState?.ComponentChanges.Span.Length ?? 0);
var compStateWork = new Dictionary<ushort, (IComponent Component, ComponentState? curState, ComponentState? nextState)>(size);
_compStateWork.Clear();
// First remove any deleted components
if (curState?.NetComponents != null)
{
RemQueue<Component> toRemove = new();
foreach (var (id, comp) in _entities.GetNetComponents(uid))
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
toRemove.Add(comp);
@@ -1097,7 +1149,7 @@ namespace Robust.Client.GameStates
foreach (var comp in toRemove)
{
_entities.RemoveComponent(uid, comp);
_entities.RemoveComponent(uid, comp, meta);
}
}
@@ -1108,34 +1160,32 @@ namespace Robust.Client.GameStates
//
// as to why we need to reset: because in the process of detaching to null-space, we will have dirtied
// the entity. most notably, all entities will have been ejected from their containers.
foreach (var (id, state) in _processor.GetLastServerStates(uid))
foreach (var (id, state) in _processor.GetLastServerStates(netEntity))
{
if (!_entityManager.TryGetComponent(uid, id, out var comp))
if (!meta.NetComponents.TryGetValue(id, out var comp))
{
comp = _compFactory.GetComponent(id);
var newComp = (Component)comp;
newComp.Owner = uid;
_entityManager.AddComponent(uid, newComp, true);
comp = (Component) _compFactory.GetComponent(id);
comp.Owner = uid;
_entityManager.AddComponent(uid, comp, true, metadata: meta);
}
compStateWork[id] = (comp, state, null);
_compStateWork[id] = (comp, state, null);
}
}
else if (curState != null)
{
foreach (var compChange in curState.ComponentChanges.Span)
{
if (!_entityManager.TryGetComponent(uid, compChange.NetID, out var comp))
if (!meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
{
comp = _compFactory.GetComponent(compChange.NetID);
var newComp = (Component)comp;
newComp.Owner = uid;
_entityManager.AddComponent(uid, newComp, true);
comp = (Component) _compFactory.GetComponent(compChange.NetID);
comp.Owner = uid;
_entityManager.AddComponent(uid, comp, true, metadata:meta);
}
else if (compChange.LastModifiedTick <= lastApplied && lastApplied != GameTick.Zero)
continue;
compStateWork[compChange.NetID] = (comp, compChange.State, null);
_compStateWork[compChange.NetID] = (comp, compChange.State, null);
}
}
@@ -1146,21 +1196,53 @@ namespace Robust.Client.GameStates
if (compState.LastModifiedTick != toTick + 1)
continue;
if (!_entityManager.TryGetComponent(uid, compState.NetID, out var comp))
if (!meta.NetComponents.TryGetValue(compState.NetID, out var comp))
{
// The component can be null here due to interp, because the NEXT state will have a new
// component, but the component does not yet exist.
continue;
}
if (compStateWork.TryGetValue(compState.NetID, out var state))
compStateWork[compState.NetID] = (comp, state.curState, compState.State);
ref var state =
ref CollectionsMarshal.GetValueRefOrAddDefault(_compStateWork, compState.NetID, out var exists);
if (exists)
state = (comp, state.curState, compState.State);
else
compStateWork[compState.NetID] = (comp, null, compState.State);
state = (comp, null, compState.State);
}
}
foreach (var (comp, cur, next) in compStateWork.Values)
// If we have a NetEntity we reference come in then apply their state.
if (_pendingReapplyNetStates.TryGetValue(uid, out var reapplyTypes))
{
var lastState = _processor.GetLastServerStates(netEntity);
foreach (var type in reapplyTypes)
{
var compRef = _compFactory.GetRegistration(type);
var netId = compRef.NetID;
if (netId == null)
continue;
if (!meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
!lastState.TryGetValue(netId.Value, out var lastCompState))
{
continue;
}
ref var compState =
ref CollectionsMarshal.GetValueRefOrAddDefault(_compStateWork, netId.Value, out var exists);
if (exists)
continue;
compState = (comp, lastCompState, null);
}
}
foreach (var (comp, cur, next) in _compStateWork.Values)
{
try
{
@@ -1170,10 +1252,10 @@ namespace Robust.Client.GameStates
catch (Exception e)
{
#if EXCEPTION_TOLERANCE
_sawmill.Error($"Failed to apply comp state: entity={comp.Owner}, comp={comp.GetType()}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
#else
_sawmill.Error($"Failed to apply comp state: entity={uid}, comp={comp.GetType()}");
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
throw;
#endif
}
@@ -1181,6 +1263,7 @@ namespace Robust.Client.GameStates
}
#region Debug Commands
private bool TryParseUid(IConsoleShell shell, string[] args, out EntityUid uid, [NotNullWhen(true)] out MetaDataComponent? meta)
{
if (args.Length != 1)
@@ -1239,7 +1322,7 @@ namespace Robust.Client.GameStates
var xform = _entities.GetComponent<TransformComponent>(uid);
if (xform.ParentUid.IsValid())
{
IContainer? container = null;
BaseContainer? container = null;
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
_entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0)
@@ -1250,7 +1333,7 @@ namespace Robust.Client.GameStates
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
if (container != null)
containerSys.AddExpectedEntity(uid, container);
containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container);
}
}
@@ -1267,18 +1350,21 @@ namespace Robust.Client.GameStates
// If this is not a client-side entity, it also needs to be removed from the full-server state dictionary to
// avoid errors. This has to be done recursively for all children.
void _recursiveRemoveState(TransformComponent xform, EntityQuery<TransformComponent> query)
void _recursiveRemoveState(NetEntity netEntity, TransformComponent xform, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> xformQuery)
{
_processor._lastStateFullRep.Remove(xform.Owner);
_processor._lastStateFullRep.Remove(netEntity);
foreach (var child in xform.ChildEntities)
{
if (query.TryGetComponent(child, out var childXform))
_recursiveRemoveState(childXform, query);
if (xformQuery.TryGetComponent(child, out var childXform) &&
metaQuery.TryGetComponent(child, out var childMeta))
{
_recursiveRemoveState(childMeta.NetEntity, childXform, metaQuery, xformQuery);
}
}
}
if (!uid.IsClientSide() && _entities.TryGetComponent(uid, out TransformComponent? xform))
_recursiveRemoveState(xform, _entities.GetEntityQuery<TransformComponent>());
if (!_entities.IsClientSide(uid) && _entities.TryGetComponent(uid, out TransformComponent? xform))
_recursiveRemoveState(meta.NetEntity, xform, _entities.GetEntityQuery<MetaDataComponent>(), _entities.GetEntityQuery<TransformComponent>());
// Set ApplyingState to true to avoid logging errors about predicting the deletion of networked entities.
using (_timing.StartStateApplicationArea())
@@ -1312,17 +1398,16 @@ namespace Robust.Client.GameStates
meta.Flags &= ~MetaDataFlags.Detached;
if (!_processor.TryGetLastServerStates(uid, out var lastState))
if (!_processor.TryGetLastServerStates(meta.NetEntity, out var lastState))
return;
foreach (var (id, state) in lastState)
{
if (!_entityManager.TryGetComponent(uid, id, out var comp))
if (!meta.NetComponents.TryGetValue(id, out var comp))
{
comp = _compFactory.GetComponent(id);
var newComp = (Component)comp;
newComp.Owner = uid;
_entityManager.AddComponent(uid, newComp, true);
comp = (Component) _compFactory.GetComponent(id);
comp.Owner = uid;
_entityManager.AddComponent(uid, comp, true, meta);
}
var handleState = new ComponentHandleState(state, null);
@@ -1331,7 +1416,7 @@ namespace Robust.Client.GameStates
// ensure we don't have any extra components
RemQueue<Component> toRemove = new();
foreach (var (id, comp) in _entities.GetNetComponents(uid))
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !lastState.ContainsKey(id))
toRemove.Add(comp);
@@ -1353,9 +1438,9 @@ namespace Robust.Client.GameStates
public sealed class GameStateAppliedArgs : EventArgs
{
public GameState AppliedState { get; }
public readonly List<EntityUid> Detached;
public readonly List<NetEntity> Detached;
public GameStateAppliedArgs(GameState appliedState, List<EntityUid> detached)
public GameStateAppliedArgs(GameState appliedState, List<NetEntity> detached)
{
AppliedState = appliedState;
Detached = detached;
@@ -1364,12 +1449,12 @@ namespace Robust.Client.GameStates
public sealed class MissingMetadataException : Exception
{
public readonly EntityUid Uid;
public readonly NetEntity NetEntity;
public MissingMetadataException(EntityUid uid)
: base($"Server state is missing the metadata component for a new entity: {uid}.")
public MissingMetadataException(NetEntity netEntity)
: base($"Server state is missing the metadata component for a new entity: {netEntity}.")
{
Uid = uid;
NetEntity = netEntity;
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Robust.Client.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
@@ -20,7 +21,7 @@ namespace Robust.Client.GameStates
private readonly List<GameState> _stateBuffer = new();
private readonly Dictionary<GameTick, List<EntityUid>> _pvsDetachMessages = new();
private readonly Dictionary<GameTick, List<NetEntity>> _pvsDetachMessages = new();
private ISawmill _logger = default!;
private ISawmill _stateLogger = default!;
@@ -44,7 +45,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
/// </summary>
internal readonly Dictionary<EntityUid, Dictionary<ushort, ComponentState>> _lastStateFullRep
internal readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _lastStateFullRep
= new();
/// <inheritdoc />
@@ -178,10 +179,10 @@ namespace Robust.Client.GameStates
foreach (var entityState in state.EntityStates.Span)
{
if (!_lastStateFullRep.TryGetValue(entityState.Uid, out var compData))
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
{
compData = new Dictionary<ushort, ComponentState>();
_lastStateFullRep.Add(entityState.Uid, compData);
_lastStateFullRep.Add(entityState.NetEntity, compData);
}
foreach (var change in entityState.ComponentChanges.Span)
@@ -263,7 +264,7 @@ namespace Robust.Client.GameStates
return false;
}
internal void AddLeavePvsMessage(List<EntityUid> entities, GameTick tick)
internal void AddLeavePvsMessage(List<NetEntity> entities, GameTick tick)
{
// Late message may still need to be processed,
DebugTools.Assert(entities.Count > 0);
@@ -272,9 +273,9 @@ namespace Robust.Client.GameStates
public void ClearDetachQueue() => _pvsDetachMessages.Clear();
public List<(GameTick Tick, List<EntityUid> Entities)> GetEntitiesToDetach(GameTick toTick, int budget)
public List<(GameTick Tick, List<NetEntity> Entities)> GetEntitiesToDetach(GameTick toTick, int budget)
{
var result = new List<(GameTick Tick, List<EntityUid> Entities)>();
var result = new List<(GameTick Tick, List<NetEntity> Entities)>();
foreach (var (tick, entities) in _pvsDetachMessages)
{
if (tick > toTick)
@@ -353,17 +354,19 @@ namespace Robust.Client.GameStates
LastFullStateRequested = _timing.LastRealTick;
}
public void MergeImplicitData(Dictionary<EntityUid, Dictionary<ushort, ComponentState>> implicitData)
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, ComponentState>> implicitData)
{
foreach (var (uid, implicitEntState) in implicitData)
foreach (var (netEntity, implicitEntState) in implicitData)
{
var fullRep = _lastStateFullRep[uid];
var fullRep = _lastStateFullRep[netEntity];
foreach (var (netId, implicitCompState) in implicitEntState)
{
if (!fullRep.TryGetValue(netId, out var serverState))
ref var serverState = ref CollectionsMarshal.GetValueRefOrAddDefault(fullRep, netId, out var exists);
if (!exists)
{
fullRep.Add(netId, implicitCompState);
serverState = implicitCompState;
continue;
}
@@ -374,28 +377,28 @@ namespace Robust.Client.GameStates
// 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 {uid}");
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {netEntity}");
continue;
}
serverDelta.ApplyToFullState(implicitCompState);
fullRep[netId] = implicitCompState;
serverState = implicitCompState;
DebugTools.Assert(implicitCompState is IComponentDeltaState d && d.FullState);
}
}
}
public Dictionary<ushort, ComponentState> GetLastServerStates(EntityUid entity)
public Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity netEntity)
{
return _lastStateFullRep[entity];
return _lastStateFullRep[netEntity];
}
public Dictionary<EntityUid, Dictionary<ushort, ComponentState>> GetFullRep()
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
{
return _lastStateFullRep;
}
public bool TryGetLastServerStates(EntityUid entity,
public bool TryGetLastServerStates(NetEntity entity,
[NotNullWhen(true)] out Dictionary<ushort, ComponentState>? dictionary)
{
return _lastStateFullRep.TryGetValue(entity, out dictionary);

View File

@@ -75,7 +75,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Applies a given set of game states.
/// </summary>
IEnumerable<EntityUid> ApplyGameState(GameState curState, GameState? nextState);
IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState);
/// <summary>
/// Resets any entities that have changed while predicting future ticks.
@@ -86,12 +86,12 @@ namespace Robust.Client.GameStates
/// An input command has been dispatched.
/// </summary>
/// <param name="message">Message being dispatched.</param>
void InputCommandDispatched(FullInputCmdMessage message);
void InputCommandDispatched(ClientFullInputCmdMessage clientMsg, FullInputCmdMessage message);
/// <summary>
/// Requests a full state from the server. This should override even implicit entity data.
/// </summary>
void RequestFullState(EntityUid? missingEntity = null);
void RequestFullState(NetEntity? missingEntity = null);
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
@@ -105,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<EntityUid, Dictionary<ushort, ComponentState>> GetFullRep();
Dictionary<NetEntity, 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
@@ -144,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<EntityUid> entities, GameTick tick);
void QueuePvsDetach(List<NetEntity> entities, GameTick tick);
/// <summary>
/// Immediately detach several entities.
/// </summary>
void DetachImmediate(List<EntityUid> entities);
void DetachImmediate(List<NetEntity> entities);
/// <summary>
/// Clears the PVS detach queue.

View File

@@ -83,13 +83,13 @@ namespace Robust.Client.GameStates
/// The data to merge.
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
/// </param>
void MergeImplicitData(Dictionary<EntityUid, Dictionary<ushort, ComponentState>> data);
void MergeImplicitData(Dictionary<NetEntity, 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(EntityUid entity);
Dictionary<ushort, ComponentState> GetLastServerStates(NetEntity entity);
/// <summary>
/// Calculate the number of applicable states in the game state buffer from a given tick.
@@ -98,7 +98,7 @@ namespace Robust.Client.GameStates
/// <param name="fromTick">The tick to calculate from.</param>
int CalculateBufferSize(GameTick fromTick);
bool TryGetLastServerStates(EntityUid entity,
bool TryGetLastServerStates(NetEntity 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<EntityUid, NetEntData> _netEnts = new();
private readonly Dictionary<NetEntity, NetEntData> _netEnts = new();
public NetEntityOverlay()
{
@@ -77,12 +77,12 @@ namespace Robust.Client.GameStates
foreach (var entityState in gameState.EntityStates.Span)
{
if (!_netEnts.TryGetValue(entityState.Uid, out var netEnt))
if (!_netEnts.TryGetValue(entityState.NetEntity, out var netEnt))
{
if (_netEnts.Count >= _maxEnts)
continue;
_netEnts[entityState.Uid] = netEnt = new();
_netEnts[entityState.NetEntity] = netEnt = new();
}
if (!netEnt.InPVS && netEnt.LastUpdate < gameState.ToSequence)
@@ -119,11 +119,13 @@ namespace Robust.Client.GameStates
var screenHandle = args.ScreenHandle;
int i = 0;
foreach (var (uid, netEnt) in _netEnts)
foreach (var (nent, netEnt) in _netEnts)
{
var uid = _entityManager.GetEntity(nent);
if (!_entityManager.EntityExists(uid))
{
_netEnts.Remove(uid);
_netEnts.Remove(nent);
continue;
}

View File

@@ -26,6 +26,7 @@ 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?
@@ -73,7 +74,7 @@ namespace Robust.Client.GameStates
_history.Add((toSeq, sz, lag, buffer));
// not watching an ent
if(!WatchEntId.IsValid() || WatchEntId.IsClientSide())
if(!WatchEntId.IsValid() || _entManager.IsClientSide(WatchEntId))
return;
string? entStateString = null;
@@ -86,7 +87,9 @@ namespace Robust.Client.GameStates
var sb = new StringBuilder();
foreach (var entState in entStates.Span)
{
if (entState.Uid != WatchEntId)
var uid = _entManager.GetEntity(entState.NetEntity);
if (uid != WatchEntId)
continue;
if (!entState.ComponentChanges.HasContents)
@@ -115,7 +118,9 @@ namespace Robust.Client.GameStates
foreach (var ent in args.Detached)
{
if (ent != WatchEntId)
var uid = _entManager.GetEntity(ent);
if (uid != WatchEntId)
continue;
conShell.WriteLine($"watchEnt: Left PVS at tick {args.AppliedState.ToSequence}, eid={WatchEntId}" + "\n");
@@ -126,7 +131,9 @@ namespace Robust.Client.GameStates
{
foreach (var entDelete in entDeletes.Span)
{
if (entDelete == WatchEntId)
var uid = _entManager.GetEntity(entDelete);
if (uid == WatchEntId)
entDelString = "\n Deleted";
}
}
@@ -294,30 +301,33 @@ 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 eValue;
EntityUid? entity;
if (args.Length == 0)
{
eValue = IoCManager.Resolve<IPlayerManager>().LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
entity = _playerManager.LocalPlayer?.ControlledEntity ?? EntityUid.Invalid;
}
else if (!EntityUid.TryParse(args[0], out eValue))
else if (!NetEntity.TryParse(args[0], out var netEntity) || !_entManager.TryGetEntity(netEntity, out entity))
{
shell.WriteError("Invalid argument: Needs to be 0 or an entityId.");
return;
}
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if (!overlayMan.TryGetOverlay(out NetGraphOverlay? overlay))
if (!_overlayManager.TryGetOverlay(out NetGraphOverlay? overlay))
{
overlay = new();
overlayMan.AddOverlay(overlay);
overlay = new NetGraphOverlay();
_overlayManager.AddOverlay(overlay);
}
overlay.WatchEntId = eValue;
overlay.WatchEntId = entity.Value;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ 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;

View File

@@ -9,6 +9,7 @@ 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;

View File

@@ -3,6 +3,7 @@ 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,8 +16,10 @@ 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

View File

@@ -5,6 +5,7 @@ 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,6 +3,7 @@ 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,6 +7,7 @@ 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,6 +6,7 @@ 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

@@ -14,6 +14,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
using System.Threading.Tasks;
using Robust.Shared.Graphics;
namespace Robust.Client.Graphics.Clyde;

View File

@@ -8,6 +8,8 @@ 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;
@@ -17,6 +19,7 @@ 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,6 +3,7 @@ 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,6 +12,7 @@ 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;
@@ -21,6 +22,7 @@ using Robust.Shared.Timing;
using SixLabors.ImageSharp;
using Color = Robust.Shared.Maths.Color;
using DependencyAttribute = Robust.Shared.IoC.DependencyAttribute;
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
namespace Robust.Client.Graphics.Clyde
{

View File

@@ -8,6 +8,7 @@ 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

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

View File

@@ -3,6 +3,7 @@ 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,6 +3,7 @@ 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
@@ -154,7 +155,7 @@ namespace Robust.Client.Graphics
Vector2 scale,
Angle? worldRot,
Angle eyeRotation = default,
Direction? overrideDirection = null,
Shared.Maths.Direction? overrideDirection = null,
SpriteComponent? sprite = null,
TransformComponent? xform = null,
SharedTransformSystem? xformSystem = null);

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ 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,6 +3,7 @@ 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,6 +3,7 @@ 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,6 +1,7 @@
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,8 +38,7 @@ namespace Robust.Client.Graphics
event Action<WindowDestroyedEventArgs> Destroyed;
/// <summary>
/// Raised when the window has been definitively closed.
/// This means the window must not be used anymore (it is disposed).
/// Raised when the window has been resized.
/// </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,5 +1,6 @@
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,
Direction? overrideDirection = null,
Shared.Maths.Direction? overrideDirection = null,
SpriteComponent? sprite = null,
TransformComponent? xform = null,
SharedTransformSystem? xformSystem = null);

View File

@@ -1,3 +1,5 @@
using Robust.Shared.Graphics;
namespace Robust.Client.Graphics
{
/// <summary>

View File

@@ -1,12 +1,14 @@
namespace Robust.Client.Graphics
using Robust.Shared.Graphics.RSI;
namespace Robust.Client.Graphics
{
public interface IRsiStateLike : IDirectionalTextureProvider
{
RSI.State.DirectionType Directions { get; }
RsiDirectionType RsiDirections { get; }
bool IsAnimated { get; }
int AnimationFrameCount { get; }
float GetDelay(int frame);
Texture GetFrame(RSI.State.Direction dir, int frame);
Texture GetFrame(RsiDirection dir, int frame);
}
}

View File

@@ -3,6 +3,7 @@ using JetBrains.Annotations;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using System;
using Robust.Shared.Graphics;
namespace Robust.Client.Graphics
{

View File

@@ -1,6 +1,8 @@
using System;
using System.Linq;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -25,7 +27,7 @@ namespace Robust.Client.Graphics
// 2D array for the texture to use for each animation frame at each direction.
public readonly Texture[][] Icons;
internal State(Vector2i size, RSI rsi, StateId stateId, DirectionType direction, float[] delays, Texture[][] icons)
internal State(Vector2i size, RSI rsi, StateId stateId, RsiDirectionType rsiDirection, float[] delays, Texture[][] icons)
{
DebugTools.Assert(size.X > 0);
DebugTools.Assert(size.Y > 0);
@@ -34,7 +36,7 @@ namespace Robust.Client.Graphics
Size = size;
RSI = rsi;
StateId = stateId;
Directions = direction;
RsiDirections = rsiDirection;
Delays = delays;
TotalDelay = delays.Sum();
Icons = icons;
@@ -63,7 +65,7 @@ namespace Robust.Client.Graphics
/// <summary>
/// How many directions this state has.
/// </summary>
public DirectionType Directions { get; }
public RsiDirectionType RsiDirections { get; }
/// <summary>
/// The first frame of the "south" direction.
@@ -90,14 +92,14 @@ namespace Robust.Client.Graphics
int IRsiStateLike.AnimationFrameCount => DelayCount;
public Texture GetFrame(Direction direction, int frame)
public Texture GetFrame(RsiDirection rsiDirection, int frame)
{
return Icons[(int) direction][frame];
return Icons[(int) rsiDirection][frame];
}
public Texture[] GetFrames(Direction direction)
public Texture[] GetFrames(RsiDirection rsiDirection)
{
return Icons[(int) direction];
return Icons[(int) rsiDirection];
}
/// <summary>
@@ -124,52 +126,12 @@ namespace Robust.Client.Graphics
Texture IDirectionalTextureProvider.TextureFor(Shared.Maths.Direction dir)
{
if (Directions == DirectionType.Dir1)
if (RsiDirections == RsiDirectionType.Dir1)
{
return Frame0;
}
return GetFrame(dir.Convert(Directions), 0);
}
/// <summary>
/// Specifies which types of directions an RSI state has.
/// </summary>
public enum DirectionType : byte
{
/// <summary>
/// A single direction, namely South.
/// </summary>
Dir1,
/// <summary>
/// 4 cardinal directions.
/// </summary>
Dir4,
/// <summary>
/// 4 cardinal + 4 diagonal directions.
/// </summary>
Dir8,
}
/// <summary>
/// Specifies a direction in an RSI state.
/// </summary>
/// <remarks>
/// Value of the enum here matches the index used to store it in the icons array. If this ever changes, then
/// <see cref="GameObjects.SpriteComponent.Layer._rsiDirectionMatrices"/> also needs to be updated.
/// </remarks>
public enum Direction : byte
{
South = 0,
North = 1,
East = 2,
West = 3,
SouthEast = 4,
SouthWest = 5,
NorthEast = 6,
NorthWest = 7,
return GetFrame(dir.Convert(RsiDirections), 0);
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Numerics;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;

View File

@@ -1,233 +1,112 @@
using System;
using System.IO;
using JetBrains.Annotations;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using YamlDotNet.RepresentationModel;
using Color = Robust.Shared.Maths.Color;
namespace Robust.Client.Graphics
namespace Robust.Client.Graphics;
/// <summary>
/// Contains a texture used for drawing things.
/// </summary>
[PublicAPI]
public abstract class Texture : IRsiStateLike
{
/// <summary>
/// Contains a texture used for drawing things.
/// The width of the texture, in pixels.
/// </summary>
[PublicAPI]
public abstract class Texture : IRsiStateLike
public int Width => Size.X;
/// <summary>
/// The height of the texture, in pixels.
/// </summary>
public int Height => Size.Y;
/// <summary>
/// The size of the texture, in pixels.
/// </summary>
public Vector2i Size { get; /*protected set;*/ }
public Color this[int x, int y] => this.GetPixel(x, y);
protected Texture(Vector2i size)
{
/// <summary>
/// The width of the texture, in pixels.
/// </summary>
public int Width => Size.X;
Size = size;
}
/// <summary>
/// The height of the texture, in pixels.
/// </summary>
public int Height => Size.Y;
Texture IDirectionalTextureProvider.Default => this;
/// <summary>
/// The size of the texture, in pixels.
/// </summary>
public Vector2i Size { get; /*protected set;*/ }
Texture IDirectionalTextureProvider.TextureFor(Direction dir)
{
return this;
}
public Color this[int x, int y] => this.GetPixel(x, y);
RsiDirectionType IRsiStateLike.RsiDirections => RsiDirectionType.Dir1;
bool IRsiStateLike.IsAnimated => false;
int IRsiStateLike.AnimationFrameCount => 0;
protected Texture(Vector2i size)
{
Size = size;
}
float IRsiStateLike.GetDelay(int frame)
{
if (frame != 0)
throw new IndexOutOfRangeException();
public static Texture Transparent =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.Transparent);
return 0;
}
public static Texture White =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.White);
Texture IRsiStateLike.GetFrame(RsiDirection dir, int frame)
{
if (frame != 0)
throw new IndexOutOfRangeException();
public static Texture Black =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.Black);
return this;
}
/// <summary>
/// Loads a new texture an existing image.
/// </summary>
/// <param name="image">The image to load.</param>
/// <param name="name">The "name" of this texture. This can be referred to later to aid debugging.</param>
/// <param name="loadParameters">
/// Parameters that influence the loading of textures.
/// Defaults to <see cref="TextureLoadParameters.Default"/> if <c>null</c>.
/// </param>
/// <typeparam name="T">The type of pixels of the image. At the moment, images must be <see cref="Rgba32"/>.</typeparam>
public static Texture LoadFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParameters = null) where T : unmanaged, IPixel<T>
{
var manager = IoCManager.Resolve<IClyde>();
return manager.LoadTextureFromImage(image, name, loadParameters);
}
public abstract Color GetPixel(int x, int y);
/// <summary>
/// Loads an image from a stream containing PNG data.
/// </summary>
/// <param name="stream">The stream to load the image from.</param>
/// <param name="name">The "name" of this texture. This can be referred to later to aid debugging.</param>
/// <param name="loadParameters">
/// Parameters that influence the loading of textures.
/// Defaults to <see cref="TextureLoadParameters.Default"/> if <c>null</c>.
/// </param>
public static Texture LoadFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParameters = null)
{
var manager = IoCManager.Resolve<IClyde>();
return manager.LoadTextureFromPNGStream(stream, name, loadParameters);
}
public static Texture Transparent =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.Transparent);
Texture IDirectionalTextureProvider.Default => this;
public static Texture White =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.White);
Texture IDirectionalTextureProvider.TextureFor(Direction dir)
{
return this;
}
public static Texture Black =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.Black);
RSI.State.DirectionType IRsiStateLike.Directions => RSI.State.DirectionType.Dir1;
bool IRsiStateLike.IsAnimated => false;
int IRsiStateLike.AnimationFrameCount => 0;
float IRsiStateLike.GetDelay(int frame)
{
if (frame != 0)
throw new IndexOutOfRangeException();
return 0;
}
Texture IRsiStateLike.GetFrame(RSI.State.Direction dir, int frame)
{
if (frame != 0)
throw new IndexOutOfRangeException();
return this;
}
public abstract Color GetPixel(int x, int y);
/// <summary>
/// Loads a new texture an existing image.
/// </summary>
/// <param name="image">The image to load.</param>
/// <param name="name">The "name" of this texture. This can be referred to later to aid debugging.</param>
/// <param name="loadParameters">
/// Parameters that influence the loading of textures.
/// Defaults to <see cref="Robust.Client.Graphics.TextureLoadParameters.Default"/> if <c>null</c>.
/// </param>
/// <typeparam name="T">The type of pixels of the image. At the moment, images must be <see cref="Rgba32"/>.</typeparam>
public static Texture LoadFromImage<T>(Image<T> image, string? name = null,
TextureLoadParameters? loadParameters = null) where T : unmanaged, IPixel<T>
{
var manager = IoCManager.Resolve<IClyde>();
return manager.LoadTextureFromImage(image, name, loadParameters);
}
/// <summary>
/// Flags for loading of textures.
/// Loads an image from a stream containing PNG data.
/// </summary>
[PublicAPI]
public struct TextureLoadParameters
/// <param name="stream">The stream to load the image from.</param>
/// <param name="name">The "name" of this texture. This can be referred to later to aid debugging.</param>
/// <param name="loadParameters">
/// Parameters that influence the loading of textures.
/// Defaults to <see cref="Robust.Client.Graphics.TextureLoadParameters.Default"/> if <c>null</c>.
/// </param>
public static Texture LoadFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParameters = null)
{
/// <summary>
/// The default sampling parameters for the texture.
/// </summary>
public TextureSampleParameters SampleParameters { get; set; }
/// <summary>
/// If true, the image data will be treated as sRGB.
/// </summary>
public bool Srgb { get; set; }
public static TextureLoadParameters FromYaml(YamlMappingNode yaml)
{
var loadParams = Default;
if (yaml.TryGetNode("sample", out YamlMappingNode? sampleNode))
{
loadParams.SampleParameters = TextureSampleParameters.FromYaml(sampleNode);
}
if (yaml.TryGetNode("srgb", out var srgb))
{
loadParams.Srgb = srgb.AsBool();
}
return loadParams;
}
public static readonly TextureLoadParameters Default = new()
{
SampleParameters = TextureSampleParameters.Default,
Srgb = true
};
}
/// <summary>
/// Sample flags for textures.
/// These are separate from <see cref="TextureLoadParameters"/>,
/// because it is possible to create "proxies" to existing textures
/// with different sampling parameters than the base texture.
/// </summary>
[PublicAPI]
public struct TextureSampleParameters
{
// NOTE: If somebody is gonna add support for 3D/1D textures, change this doc comment.
// See the note on this page for why: https://www.khronos.org/opengl/wiki/Sampler_Object#Filtering
/// <summary>
/// If true, use bi-linear texture filtering if the texture cannot be rendered 1:1
/// </summary>
public bool Filter { get; set; }
/// <summary>
/// Controls how to wrap the texture if texture coordinates outside 0-1 are accessed.
/// </summary>
public TextureWrapMode WrapMode { get; set; }
public static TextureSampleParameters FromYaml(YamlMappingNode node)
{
var wrap = TextureWrapMode.None;
var filter = false;
if (node.TryGetNode("filter", out var filterNode))
{
filter = filterNode.AsBool();
}
if (node.TryGetNode("wrap", out var wrapNode))
{
switch (wrapNode.AsString())
{
case "none":
wrap = TextureWrapMode.None;
break;
case "repeat":
wrap = TextureWrapMode.Repeat;
break;
case "mirrored_repeat":
wrap = TextureWrapMode.MirroredRepeat;
break;
default:
throw new ArgumentException("Not a valid wrap mode.");
}
}
return new TextureSampleParameters {Filter = filter, WrapMode = wrap};
}
public static readonly TextureSampleParameters Default = new()
{
Filter = false,
WrapMode = TextureWrapMode.None
};
}
/// <summary>
/// Controls behavior when reading texture coordinates outside 0-1, which usually wraps the texture somehow.
/// </summary>
[PublicAPI]
public enum TextureWrapMode : byte
{
/// <summary>
/// Do not wrap, instead clamp to edge.
/// </summary>
None = 0,
/// <summary>
/// Repeat the texture.
/// </summary>
Repeat,
/// <summary>
/// Repeat the texture mirrored.
/// </summary>
MirroredRepeat,
var manager = IoCManager.Resolve<IClyde>();
return manager.LoadTextureFromPNGStream(stream, name, loadParameters);
}
}

View File

@@ -64,6 +64,7 @@ namespace Robust.Client.Input
common.AddFunction(EngineKeyFunctions.TextWordDelete);
common.AddFunction(EngineKeyFunctions.TextNewline);
common.AddFunction(EngineKeyFunctions.TextSubmit);
common.AddFunction(EngineKeyFunctions.MultilineTextSubmit);
common.AddFunction(EngineKeyFunctions.TextCopy);
common.AddFunction(EngineKeyFunctions.TextCut);
common.AddFunction(EngineKeyFunctions.TextPaste);

View File

@@ -8,6 +8,7 @@ using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
@@ -27,7 +28,7 @@ namespace Robust.Client.Map
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
private readonly Dictionary<ushort, Box2[]> _tileRegions = new();
private readonly Dictionary<int, Box2[]> _tileRegions = new();
public Box2 ErrorTileRegion { get; private set; }
@@ -38,7 +39,7 @@ namespace Robust.Client.Map
}
/// <inheritdoc />
public Box2[]? TileAtlasRegion(ushort tileType)
public Box2[]? TileAtlasRegion(int tileType)
{
if (_tileRegions.TryGetValue(tileType, out var region))
{

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -27,6 +28,6 @@ namespace Robust.Client.Map
/// Gets the region inside the texture atlas to use to draw a tile type.
/// </summary>
/// <returns>If null, do not draw the tile at all.</returns>
Box2[]? TileAtlasRegion(ushort tileType);
Box2[]? TileAtlasRegion(int tileType);
}
}

View File

@@ -6,6 +6,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Direction = Robust.Shared.Maths.Direction;
namespace Robust.Client.Map;
@@ -61,11 +62,16 @@ public sealed class TileEdgeOverlay : Overlay
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
var neighborTile = grid.GetTileRef(neighborIndices);
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
// If it's the same tile then no edge to be drawn.
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
continue;
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
continue;
var direction = new Vector2i(x, y).AsDirection();
// No edge tile

View File

@@ -62,8 +62,9 @@ namespace Robust.Client.Physics
Log.Info($"Received grid fixture debug data");
if (!_enableDebug) return;
_nodes[ev.Grid] = ev.Nodes;
_connections[ev.Grid] = ev.Connections;
var grid = GetEntity(ev.Grid);
_nodes[grid] = ev.Nodes;
_connections[grid] = ev.Connections;
}
private sealed class GridSplitNodeOverlay : Overlay

View File

@@ -20,7 +20,7 @@ namespace Robust.Client.Physics
{
if (args.Current is not JointComponentState jointState) return;
component.Relay = jointState.Relay;
component.Relay = EnsureEntity<JointComponent>(jointState.Relay, uid);
// Initial state gets applied before the entity (& entity's transform) have been initialized.
// So just let joint init code handle that.
@@ -29,7 +29,7 @@ namespace Robust.Client.Physics
component.Joints.Clear();
foreach (var (id, state) in jointState.Joints)
{
component.Joints[id] = state.GetJoint();
component.Joints[id] = state.GetJoint(EntityManager, uid);
}
return;
}
@@ -62,8 +62,8 @@ namespace Robust.Client.Physics
continue;
}
var other = state.UidA == uid ? state.UidB : state.UidA;
var uidA = GetEntity(state.UidA);
var other = uidA == uid ? GetEntity(state.UidB) : uidA;
// Add new joint (if possible).
// Need to wait for BOTH joint components to come in first before we can add it. Yay dependencies!
@@ -82,11 +82,11 @@ namespace Robust.Client.Physics
// TODO: component state handling ordering.
if (Transform(uid).MapID == MapId.Nullspace)
{
AddedJoints.Add(state.GetJoint());
AddedJoints.Add(state.GetJoint(EntityManager, uid));
continue;
}
AddJoint(state.GetJoint());
AddJoint(state.GetJoint(EntityManager, uid));
}
}
}

View File

@@ -28,10 +28,9 @@ public sealed partial class PhysicsSystem
private void UpdateIsPredicted()
{
var query = GetEntityQuery<PhysicsComponent>();
foreach (var uid in _toUpdate)
{
if (!query.TryGetComponent(uid, out var physics))
if (!PhysicsQuery.TryGetComponent(uid, out var physics))
continue;
var ev = new UpdateIsPredictedEvent(uid);

View File

@@ -16,7 +16,6 @@ namespace Robust.Client.Physics
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
public override void Update(float frameTime)

View File

@@ -8,6 +8,7 @@ using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
@@ -20,6 +21,7 @@ using Robust.Shared.Reflection;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Log;
using Direction = Robust.Shared.Maths.Direction;
namespace Robust.Client.Placement
{
@@ -225,7 +227,7 @@ namespace Robust.Client.Placement
}
}))
.Bind(EngineKeyFunctions.EditorPlaceObject, new PointerStateInputCmdHandler(
(session, coords, uid) =>
(session, netCoords, nent) =>
{
if (!IsActive)
return false;
@@ -239,15 +241,15 @@ namespace Robust.Client.Placement
if (Eraser)
{
if (HandleDeletion(coords))
if (HandleDeletion(netCoords))
return true;
if (uid == EntityUid.Invalid)
if (nent == EntityUid.Invalid)
{
return false;
}
HandleDeletion(uid);
HandleDeletion(nent);
}
else
{
@@ -428,7 +430,7 @@ namespace Robust.Client.Placement
var msg = new MsgPlacement();
msg.PlaceType = PlacementManagerMessage.RequestEntRemove;
msg.EntityUid = entity;
msg.EntityUid = EntityManager.GetNetEntity(entity);
_networkManager.ClientSendMessage(msg);
}
@@ -436,7 +438,7 @@ namespace Robust.Client.Placement
{
var msg = new MsgPlacement();
msg.PlaceType = PlacementManagerMessage.RequestRectRemove;
msg.EntityCoordinates = new EntityCoordinates(StartPoint.EntityId, rect.BottomLeft);
msg.NetCoordinates = new NetCoordinates(EntityManager.GetNetEntity(StartPoint.EntityId), rect.BottomLeft);
msg.RectSize = rect.Size;
_networkManager.ClientSendMessage(msg);
}
@@ -790,7 +792,7 @@ namespace Robust.Client.Placement
message.EntityTemplateName = CurrentPermission.EntityType;
// world x and y
message.EntityCoordinates = coordinates;
message.NetCoordinates = EntityManager.GetNetCoordinates(coordinates);
message.DirRcv = Direction;

View File

@@ -7,6 +7,7 @@ using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;

View File

@@ -7,7 +7,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.Player
{
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
public interface IPlayerManager : ISharedPlayerManager
{
new IEnumerable<ICommonSession> Sessions { get; }

View File

@@ -73,7 +73,6 @@ namespace Robust.Client.Player
ControlledEntity = entity;
InternalSession.AttachedEntity = entity;
if (!entMan.TryGetComponent<EyeComponent?>(entity, out var eye))
{
eye = entMan.AddComponent<EyeComponent>(entity);
@@ -84,7 +83,6 @@ namespace Robust.Client.Player
}
eye.NetSyncEnabled = false;
}
eye.Current = true;
EntityAttached?.Invoke(new EntityAttachedEventArgs(entity));
@@ -101,12 +99,6 @@ namespace Robust.Client.Player
{
var entMan = IoCManager.Resolve<IEntityManager>();
var previous = ControlledEntity;
if (entMan.TryGetComponent(previous, out MetaDataComponent? metaData) &&
metaData.EntityInitialized &&
!metaData.EntityDeleted)
{
entMan.GetComponent<EyeComponent>(previous.Value).Current = false;
}
ControlledEntity = null;
InternalSession.AttachedEntity = null;

View File

@@ -6,6 +6,7 @@ using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
@@ -24,6 +25,7 @@ namespace Robust.Client.Player
[Dependency] private readonly IClientNetManager _network = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly ILogManager _logMan = default!;
/// <summary>
/// Active sessions of connected clients to the server.
@@ -51,6 +53,8 @@ namespace Robust.Client.Player
/// <inheritdoc />
public int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? 0;
public ICommonSession? LocalSession => LocalPlayer?.Session;
/// <inheritdoc />
[ViewVariables]
public LocalPlayer? LocalPlayer
@@ -65,6 +69,8 @@ namespace Robust.Client.Player
}
}
private LocalPlayer? _localPlayer;
private ISawmill _sawmill = default!;
public event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
/// <inheritdoc />
@@ -82,6 +88,7 @@ namespace Robust.Client.Player
{
_client.RunLevelChanged += OnRunLevelChanged;
_sawmill = _logMan.GetSawmill("player");
_network.RegisterNetMessage<MsgPlayerListReq>();
_network.RegisterNetMessage<MsgPlayerList>(HandlePlayerList);
}
@@ -122,7 +129,13 @@ namespace Robust.Client.Player
if (myState != null)
{
UpdateAttachedEntity(myState.ControlledEntity);
var uid = _entManager.GetEntity(myState.ControlledEntity);
if (myState.ControlledEntity is {Valid: true} && !_entManager.EntityExists(uid))
{
_sawmill.Error($"Received player state for local player with an unknown net entity!");
}
UpdateAttachedEntity(uid);
UpdateSessionStatus(myState.Status);
}
@@ -181,11 +194,13 @@ namespace Robust.Client.Player
if (_sessions.TryGetValue(state.UserId, out var session))
{
var local = (PlayerSession) session;
var controlled = _entManager.GetEntity(state.ControlledEntity);
// Exists, update data.
if (local.Name == state.Name
&& local.Status == state.Status
&& local.Ping == state.Ping
&& local.AttachedEntity == state.ControlledEntity)
&& local.AttachedEntity == controlled)
{
continue;
}
@@ -194,7 +209,7 @@ namespace Robust.Client.Player
local.Name = state.Name;
local.Status = state.Status;
local.Ping = state.Ping;
local.AttachedEntity = state.ControlledEntity;
local.AttachedEntity = controlled;
}
else
{
@@ -206,7 +221,7 @@ namespace Robust.Client.Player
Name = state.Name,
Status = state.Status,
Ping = state.Ping,
AttachedEntity = state.ControlledEntity,
AttachedEntity = _entManager.GetEntity(state.ControlledEntity),
};
_sessions.Add(state.UserId, newSession);
if (state.UserId == LocalPlayer!.UserId)

View File

@@ -0,0 +1,22 @@
using Robust.Client.Replays.UI;
using Robust.Client.UserInterface;
using Robust.Shared.Console;
using Robust.Shared.IoC;
namespace Robust.Client.Replays.Commands;
public sealed class ReplayToggleUiCommand : BaseReplayCommand
{
[Dependency] private readonly IUserInterfaceManager _userInterfaceManager = default!;
public override string Command => "replay_toggleui";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var screen = _userInterfaceManager.ActiveScreen;
if (screen == null || !screen.TryGetWidget(out ReplayControlWidget? replayWidget))
return;
replayWidget.Visible = !replayWidget.Visible;
}
}

View File

@@ -83,8 +83,8 @@ public sealed partial class ReplayLoadManager
}
HashSet<ResPath> uploadedFiles = new();
var detached = new HashSet<EntityUid>();
var detachQueue = new Dictionary<GameTick, List<EntityUid>>();
var detached = new HashSet<NetEntity>();
var detachQueue = new Dictionary<GameTick, List<NetEntity>>();
if (initMessages != null)
UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
@@ -92,11 +92,11 @@ public sealed partial class ReplayLoadManager
ProcessQueue(GameTick.MaxValue, detachQueue, detached);
var entSpan = state0.EntityStates.Value;
Dictionary<EntityUid, EntityState> entStates = new(entSpan.Count);
Dictionary<NetEntity, EntityState> entStates = new(entSpan.Count);
foreach (var entState in entSpan)
{
var modifiedState = AddImplicitData(entState);
entStates.Add(entState.Uid, modifiedState);
entStates.Add(entState.NetEntity, modifiedState);
}
await callback(0, states.Count, LoadingState.ProcessingFiles, true);
@@ -112,11 +112,11 @@ public sealed partial class ReplayLoadManager
default,
entStates.Values.ToArray(),
playerStates.Values.ToArray(),
Array.Empty<EntityUid>());
Array.Empty<NetEntity>());
checkPoints.Add(new CheckpointState(state0, timeBase, cvars, 0, detached));
DebugTools.Assert(state0.EntityDeletions.Value.Count == 0);
var empty = Array.Empty<EntityUid>();
var empty = Array.Empty<NetEntity>();
TimeSpan GetTime(GameTick tick)
{
@@ -176,8 +176,8 @@ public sealed partial class ReplayLoadManager
private void ProcessQueue(
GameTick curTick,
Dictionary<GameTick, List<EntityUid>> detachQueue,
HashSet<EntityUid> detached)
Dictionary<GameTick, List<NetEntity>> detachQueue,
HashSet<NetEntity> detached)
{
foreach (var (tick, ents) in detachQueue)
{
@@ -192,7 +192,7 @@ public sealed partial class ReplayLoadManager
HashSet<ResPath> uploadedFiles,
Dictionary<Type, HashSet<string>> prototypes,
Dictionary<string, object> cvars,
Dictionary<GameTick, List<EntityUid>> detachQueue,
Dictionary<GameTick, List<NetEntity>> detachQueue,
ref (TimeSpan, GameTick) timeBase,
bool ignoreDuplicates = false)
{
@@ -301,8 +301,8 @@ public sealed partial class ReplayLoadManager
_locMan.ReloadLocalizations();
}
private void UpdateDeletions(NetListAsArray<EntityUid> entityDeletions,
Dictionary<EntityUid, EntityState> entStates, HashSet<EntityUid> detached)
private void UpdateDeletions(NetListAsArray<NetEntity> entityDeletions,
Dictionary<NetEntity, EntityState> entStates, HashSet<NetEntity> detached)
{
foreach (var ent in entityDeletions.Span)
{
@@ -311,16 +311,16 @@ public sealed partial class ReplayLoadManager
}
}
private void UpdateEntityStates(ReadOnlySpan<EntityState> span, Dictionary<EntityUid, EntityState> entStates,
ref int spawnedTracker, ref int stateTracker, HashSet<EntityUid> detached)
private void UpdateEntityStates(ReadOnlySpan<EntityState> span, Dictionary<NetEntity, EntityState> entStates,
ref int spawnedTracker, ref int stateTracker, HashSet<NetEntity> detached)
{
foreach (var entState in span)
{
detached.Remove(entState.Uid);
if (!entStates.TryGetValue(entState.Uid, out var oldEntState))
detached.Remove(entState.NetEntity);
if (!entStates.TryGetValue(entState.NetEntity, out var oldEntState))
{
var modifiedState = AddImplicitData(entState);
entStates[entState.Uid] = modifiedState;
entStates[entState.NetEntity] = modifiedState;
spawnedTracker++;
#if DEBUG
@@ -333,11 +333,11 @@ public sealed partial class ReplayLoadManager
}
stateTracker++;
DebugTools.Assert(oldEntState.Uid == entState.Uid);
entStates[entState.Uid] = MergeStates(entState, oldEntState.ComponentChanges.Value, oldEntState.NetComponents);
DebugTools.Assert(oldEntState.NetEntity == entState.NetEntity);
entStates[entState.NetEntity] = MergeStates(entState, oldEntState.ComponentChanges.Value, oldEntState.NetComponents);
#if DEBUG
foreach (var state in entStates[entState.Uid].ComponentChanges.Span)
foreach (var state in entStates[entState.NetEntity].ComponentChanges.Span)
{
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
}
@@ -388,7 +388,7 @@ public sealed partial class ReplayLoadManager
}
DebugTools.Assert(newState.NetComponents == null || newState.NetComponents.Count == combined.Count);
return new EntityState(newState.Uid, combined, newState.EntityLastModified, newState.NetComponents ?? oldNetComps);
return new EntityState(newState.NetEntity, combined, newState.EntityLastModified, newState.NetComponents ?? oldNetComps);
}
private void UpdatePlayerStates(ReadOnlySpan<PlayerState> span, Dictionary<NetUserId, PlayerState> playerStates)

View File

@@ -70,14 +70,14 @@ public sealed partial class ReplayLoadManager
{
// This shouldn't be possible, yet it has happened?
// TODO this should probably also throw an exception.
_sawmill.Error($"Encountered blank entity state? Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}. Attempting to continue.");
_sawmill.Error($"Encountered blank entity state? Entity: {entState.NetEntity}. Last modified: {entState.EntityLastModified}. Attempting to continue.");
return null;
}
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new MissingMetadataException(entState.Uid);
throw new MissingMetadataException(entState.NetEntity);
_sawmill.Error($"Missing metadata component. Entity: {entState.Uid}. Last modified: {entState.EntityLastModified}.");
_sawmill.Error($"Missing metadata component. Entity: {entState.NetEntity}. Last modified: {entState.EntityLastModified}.");
return null;
}
}

View File

@@ -68,10 +68,18 @@ public sealed partial class ReplayLoadManager
var metaState = (MetaDataComponentState?)ent.ComponentChanges.Value?
.FirstOrDefault(c => c.NetID == _metaId).State;
if (metaState == null)
throw new MissingMetadataException(ent.Uid);
throw new MissingMetadataException(ent.NetEntity);
_entMan.CreateEntityUninitialized(metaState.PrototypeId, ent.Uid);
entities.Add(ent.Uid);
var uid = _entMan.CreateEntityUninitialized(metaState.PrototypeId);
entities.Add(uid);
var metaComp = _entMan.GetComponent<MetaDataComponent>(uid);
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entMan.ClearNetEntity(metaComp.NetEntity);
_entMan.SetNetEntity(uid, ent.NetEntity, metaComp);
if (i++ % 50 == 0)
{

View File

@@ -19,7 +19,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
{
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly EntityManager _entMan = default!;
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly IClientNetManager _netMan = default!;
[Dependency] private readonly IComponentFactory _factory = default!;

View File

@@ -82,19 +82,28 @@ internal sealed partial class ReplayPlaybackManager
var metas = _entMan.GetEntityQuery<MetaDataComponent>();
foreach (var es in checkpoint.DetachedStates)
{
if (metas.TryGetComponent(es.Uid, out var meta) && !meta.EntityDeleted)
var uid = _entMan.GetEntity(es.NetEntity);
if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted)
continue;
;
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?
.FirstOrDefault(c => c.NetID == _metaId).State;
if (metaState == null)
throw new MissingMetadataException(es.Uid);
throw new MissingMetadataException(es.NetEntity);
_entMan.CreateEntityUninitialized(metaState.PrototypeId, es.Uid);
meta = metas.GetComponent(es.Uid);
_entMan.InitializeEntity(es.Uid, meta);
_entMan.StartEntity(es.Uid);
_entMan.CreateEntityUninitialized(metaState.PrototypeId, uid);
meta = metas.GetComponent(uid);
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entMan.ClearNetEntity(meta.NetEntity);
_entMan.SetNetEntity(uid, es.NetEntity, meta);
_entMan.InitializeEntity(uid, meta);
_entMan.StartEntity(uid);
meta.LastStateApplied = checkpoint.Tick;
}
}

View File

@@ -50,7 +50,7 @@ internal sealed partial class ReplayPlaybackManager
}
_timing.CurTick += 1;
_entMan.TickUpdate(args.DeltaSeconds, noPredictions: true);
_cEntManager.TickUpdate(args.DeltaSeconds, noPredictions: true);
if (!Playing || AutoPauseCountdown == null)
return;
@@ -82,7 +82,7 @@ internal sealed partial class ReplayPlaybackManager
// Maybe track our own detach queue and use _gameState.DetachImmediate()?
// That way we don't have to clone this. Downside would be that all entities will be immediately
// detached. I.e., the detach budget cvar will simply be ignored.
var clone = new List<EntityUid>(leavePvs.Entities);
var clone = new List<NetEntity>(leavePvs.Entities);
_gameState.QueuePvsDetach(clone, leavePvs.Tick);
continue;

View File

@@ -33,7 +33,8 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly IPrototypeManager _protoMan = default!;
[Dependency] private readonly IGameController _controller = default!;
[Dependency] private readonly IClientEntityManager _entMan = default!;
[Dependency] private readonly IClientEntityManager _cEntManager = default!;
[Dependency] private readonly ClientEntityManager _entMan = default!;
[Dependency] private readonly IConfigurationManager _confMan = default!;
[Dependency] private readonly NetworkResourceManager _netResMan = default!;
[Dependency] private readonly IClientGameStateManager _gameState = default!;

View File

@@ -95,12 +95,12 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
{
var tick = _timing.LastRealTick;
var players = _player.Sessions.Select(GetPlayerState).ToArray();
var deletions = Array.Empty<EntityUid>();
var deletions = Array.Empty<NetEntity>();
var fullRep = _state.GetFullRep();
var entStates = new EntityState[fullRep.Count];
var i = 0;
foreach (var (uid, dict) in fullRep)
foreach (var (netEntity, dict) in fullRep)
{
var compData = new ComponentChange[dict.Count];
var netComps = new HashSet<ushort>(dict.Keys);
@@ -110,7 +110,7 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
compData[j++] = new ComponentChange(id, compState, tick);
}
entStates[i++] = new EntityState(uid, compData, tick, netComps);
entStates[i++] = new EntityState(netEntity, compData, tick, netComps);
}
var state = new GameState(
@@ -121,16 +121,17 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
players,
deletions);
var detached = new List<EntityUid>();
var detached = new List<NetEntity>();
var query = _entMan.AllEntityQueryEnumerator<MetaDataComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (uid.IsClientSide())
if (_entMan.IsClientSide(uid))
continue;
DebugTools.Assert(fullRep.ContainsKey(uid));
var nent = comp.NetEntity;
DebugTools.Assert(fullRep.ContainsKey(nent));
if ((comp.Flags & MetaDataFlags.Detached) != 0)
detached.Add(uid);
detached.Add(nent);
}
var detachMsg = detached.Count > 0 ? new ReplayMessage.LeavePvs(detached, tick) : null;
@@ -144,7 +145,7 @@ internal sealed class ReplayRecordingManager : SharedReplayRecordingManager
UserId = session.UserId,
Status = session.Status,
Name = session.Name,
ControlledEntity = session.AttachedEntity,
ControlledEntity = _entMan.GetNetEntity(session.AttachedEntity),
};
}
}

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