Compare commits

...

123 Commits

Author SHA1 Message Date
Pieter-Jan Briers
91d4b32c66 Version: 167.0.1 2024-03-10 20:50:30 +01:00
Pieter-Jan Briers
e788f03a28 Backport 859f150404
(cherry picked from commit 24d5c26fa6)
(cherry picked from commit 688efac67b634c613539b783a9fb6e679948cd53)
2024-03-10 20:50:30 +01:00
metalgearsloth
77654a1628 Version: 167.0.0 2023-10-17 23:51:47 +11:00
Leon Friedrich
f3af813b57 Transform interpolation fixes (#4488) 2023-10-17 23:47:45 +11:00
Leon Friedrich
0623baedcf Fix PVS bug and add new test (#4444)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2023-10-17 23:33:42 +11:00
Leon Friedrich
2ade6c04c5 Adds more joint debug asserts (#4490) 2023-10-15 11:04:27 +11:00
Kara
a9df9097c1 Kill ContainerHelpers (#4486) 2023-10-15 03:58:25 +11:00
Kara
755dac719f Kill ComponentExt (#4485) 2023-10-15 03:58:04 +11:00
ElectroJr
7095a58685 Version: 166.0.0 2023-10-10 22:23:51 -04:00
Leon Friedrich
16e68a4351 Make PVS session overrides recursive (#4480) 2023-10-11 13:16:00 +11:00
DrSmugleaf
0152f9d1d8 Move Component lifecycle methods to EntityManager (#4483) 2023-10-10 17:34:40 -07:00
DrSmugleaf
16d916796a Add doc clarification to CopyByRef inheritance for the future coder (#4476) 2023-10-10 17:34:24 -07:00
Leon Friedrich
e865157432 Make AssertOwner() accept nullable components (#4475) 2023-10-10 04:41:26 +11:00
metalgearsloth
24d5ce4bd4 Make AddCompUninit obsolete (#4470) 2023-10-10 04:41:10 +11:00
deltanedas
662195e4ff add SortedSetSerializer (#4473) 2023-10-10 04:29:42 +11:00
DrSmugleaf
d00fd6f736 Make AnimationPlayerSystem only log a warning for networked components when the property is automatically networked (#4477) 2023-10-10 04:08:56 +11:00
Leon Friedrich
2d58c1071d Make ExpandPvsEvent lists nullable (#4479) 2023-10-10 04:07:59 +11:00
Leon Friedrich
a9db89d023 Fix nullable NetEntity conversion (#4481) 2023-10-10 04:07:30 +11:00
Leon Friedrich
684cabf3e6 Use metadata query in ToPrettyString() (#4478) 2023-10-09 19:07:31 +11:00
ElectroJr
a4f51f0cd9 Fix release notes 2023-10-08 12:41:45 -04:00
ElectroJr
a8ddd837c8 Version: 165.0.0 2023-10-08 12:39:03 -04:00
Leon Friedrich
82aace7997 Fix SplitContainer.MinDraggableWidth not working with mouse-blocking children. (#4439) 2023-10-09 03:28:23 +11:00
Leon Friedrich
01ce244b7b Validate default values of ProtoId and EntProtoId fields (#4462) 2023-10-02 03:49:45 +11:00
metalgearsloth
58aa6e5c75 Version: 164.0.0 2023-09-30 15:19:43 +10:00
DrSmugleaf
4818c3aab4 Make auto comp states infer when data should be cloned (#4461) 2023-09-30 15:14:10 +10:00
Leon Friedrich
3b6adeb5ff Reduce transform resolves in RecursiveDeleteEntity() (#4468) 2023-09-30 15:12:17 +10:00
metalgearsloth
889b8351be Version: 163.0.0 2023-09-30 14:40:55 +10:00
metalgearsloth
ac37b0a131 Move TimedDespawn to engine (#4448) 2023-09-30 14:35:28 +10:00
Leon Friedrich
f6f1fc425a Use ToPrettyString() in component resolve errors (#4467) 2023-09-30 14:31:40 +10:00
Leon Friedrich
7476628840 Give map and grid entities a default name (#4464) 2023-09-30 14:09:37 +10:00
Leon Friedrich
668cdbe76b Allow adding/removing of widgets in sub-controls (#4443) 2023-09-30 14:08:52 +10:00
Leon Friedrich
a0a6e9b111 Fix render error spam (#4463) 2023-09-30 14:07:27 +10:00
Leon Friedrich
06d28f04e6 Add ExecuteCommand() (#4466) 2023-09-30 14:07:07 +10:00
Leon Friedrich
57897161d0 Fix console backspace exception (#4465) 2023-09-30 14:05:29 +10:00
metalgearsloth
c4c528478e Use gamestate fields to avoid heap allocs (#4452) 2023-09-29 15:03:50 +10: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
237 changed files with 4828 additions and 3039 deletions

16
.github/CODEOWNERS vendored
View File

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

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,275 @@ END TEMPLATE-->
*None yet*
## 167.0.1
## 167.0.0
### Breaking changes
* Remove ComponentExtensions.
* Remove ContainerHelpers.
* Change some TransformSystem methods to fix clientside lerping.
### Bugfixes
* Fixed PVS bugs from dropped entity states.
### Other
* Add more joint debug asserts.
## 166.0.0
### Breaking changes
* EntityUid-NetEntity conversion methods now return null when given a null value, rather than returning an invalid id.
* ExpandPvsEvent now defaults to using null lists to reduce allocations.
* Various component lifestage related methods have been moved from the `Component` class to `EntityManager`.
* Session/client specific PVS overrides are now always recursive, which means that all children of the overriden entity will also get sent.
### New features
* Added a SortedSet yaml serializer.
### Other
* AddComponentUninitialized is now marked as obsolete and will be removed in the future.
* DebugTools.AssertOwner() now accepts null components.
## 165.0.0
### Breaking changes
* The arguments of `SplitContainer`s resize-finished event have changed.
### New features
* The YAML validator now checks the default values of ProtoId<T> and EntProtoId data fields.
### Bugfixes
* The minimum draggable area of split containers now blocks mouse inputs.
## 164.0.0
### Breaking changes
* Make automatic component states infer cloneData.
* Removed cloneData from AutoNetworkedFieldAttribute. This is now automatically inferred.
### Internal
* Reduce Transform GetComponents in RecursiveDeleteEntity.
## 163.0.0
### Breaking changes
* Moved TimedDespawn to engine for a component that deletes the attached entity after a timer has elapsed.
### New features
* Add ExecuteCommand for integration tests.
* Allow adding / removing widgets of cub-controls.
* Give maps / grids a default name to help with debugging.
* Use ToPrettyString in component resolve errors to help with debugging.
### Bugfixes
* Fix console backspace exception.
* Fix rendering invalid maps spamming exceptions every frame.
### Internal
* Move ClientGameStatemanager local variables to fields to avoid re-allocating every tick.
## 162.2.1
## 162.2.0
### New features
* Add support for automatically networking entity lists and sets.
* Add nullable conversion operators for ProtoIds.
* Add LocId serializer for validation.
### Bugfixes
* Fix deleting a contact inside of collision events throwing.
* Localize VV.
### Internal
* Use CollectionsMarshal in GameStateManager.
## 162.1.1
### Bugfixes
* Fixes "NoSpawn" entities appearing in the spawn menu.
## 162.1.0
### New features
* Mark ProtoId as NetSerializable.
### Bugfixes
* Temporarily revert NetForceAckThreshold change as it can lead to client stalling.
* Fix eye visibility layers not updating on children when a parent changes.
### Internal
* Use CollectionsMarshal in RobustTree and AddComponentInternal.
## 162.0.0
### New features
* Add entity categories for prototypes and deprecate the `noSpawn` tag.
* Add missing proxy method for `TryGetEntityData`.
* Add NetForceAckThreshold cvar to forcibly update acks for late clients.
### Internal
* Use CollectionMarshals in PVS and DynamicTree.
* Make the proxy methods use MetaQuery / TransformQuery.
## 161.1.0
### New features
* Add more DebugTools assert variations.
### Bugfixes
* Don't attempt to insert entities into deleted containers.
* Try to fix oldestAck not being set correctly leading to deletion history getting bloated for pvs.
## 161.0.0
### Breaking changes
* Point light animations now need to use different component fields in order to animate the lights. `Enabled` should be replaced with `AnimatedEnable` and `Radius` should be replaced with `AnimatedRadius`
### New features
* EntProtoId is now net-serializable
* Added print_pvs_ack command to debug PVS issues.
### Bugfixes
* Fixes AngleTypeParser not using InvariantCulture
* Fixed a bug that was causing `MetaDataComponent.LastComponentRemoved` to be updated improperly.
### Other
* The string representation of client-side entities now looks nicer and simply uses a 'c' prefix.
## 160.1.0
### New features
* Add optional MetaDataComponent args to Entitymanager methods.
### Internal
* Move _netComponents onto MetaDataComponent.
* Remove some component resolves internally on adding / removing components.
## 160.0.2
### Other
* Transform component and containers have new convenience fields to make using VIewVariables easier.
## 160.0.0
### Breaking changes
* ComponentReference has now been entirely removed.
* Sensor / non-hard physics bodies are now included in EntityLookup by default.
## 159.1.0
## 159.0.3
### Bugfixes
* Fix potentially deleted entities having states re-applied when NetEntities come in.
## 159.0.2
### Bugfixes
* Fix PointLight state handling not queueing ComponentTree updates.
## 159.0.1
### Bugfixes
* Fix pending entity states not being removed when coming in (only on entity deletion).
### Internal
* Remove PhysicsComponent ref from Fixture.
## 159.0.0
### Breaking changes
* Remove ComponentReference from PointLights.
* Move more of UserInterfaceSystem to shared.
* Mark some EntitySystem proxy methods as protected instead of public.
### New features
* Make entity deletion take in a nullable EntityUid.
* Added a method to send predicted messages via BUIs.
### Other
* Add Obsoletions to more sourcegen serv4 methods.
* Remove inactive reviewers from CODEOWNERs.
## 158.0.0
### Breaking changes
* Remove SharedEyeComponent.
* Add Tile Overlay edge priority.
## 157.1.0
### New features
* UI tooltips now use rich text labels.
## 157.0.0
### Breaking changes

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

@@ -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

@@ -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)
{

View File

@@ -35,14 +35,14 @@ public sealed partial class ClientEntityManager
if (NetEntityLookup.TryGetValue(nEntity, out var entity))
{
return 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;
return entity.Item1;
}
public override EntityCoordinates EnsureCoordinates<T>(NetCoordinates netCoordinates, EntityUid callerEntity)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Client.Player;
@@ -42,9 +43,9 @@ namespace Robust.Client.GameObjects
base.FlushEntities();
}
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName)
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, out MetaDataComponent metadata)
{
return base.CreateEntity(prototypeName);
return base.CreateEntity(prototypeName, out metadata);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)
@@ -65,9 +66,12 @@ namespace Robust.Client.GameObjects
base.DirtyEntity(uid, meta);
}
public override void QueueDeleteEntity(EntityUid uid)
public override void QueueDeleteEntity(EntityUid? uid)
{
if (IsClientSide(uid))
if (uid == null)
return;
if (IsClientSide(uid.Value))
{
base.QueueDeleteEntity(uid);
return;
@@ -78,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 />
@@ -89,12 +93,12 @@ namespace Robust.Client.GameObjects
base.Dirty(uid, component, meta);
}
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
public override EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metaDataComponent = null)
{
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
return base.ToPrettyString(uid) with { Session = _playerManager.LocalPlayer.Session };
else
return base.ToPrettyString(uid);
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

@@ -4,7 +4,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using TerraFX.Interop.Windows;
namespace Robust.Client.GameObjects
{
@@ -117,7 +116,7 @@ namespace Robust.Client.GameObjects
if (compTrack.ComponentType == null)
{
_sawmill.Error($"Attempted to play a component animation without any component specified.");
_sawmill.Error("Attempted to play a component animation without any component specified.");
return;
}
@@ -136,8 +135,14 @@ namespace Robust.Client.GameObjects
// In principle there is nothing wrong with this, as long as the property of the component being
// animated is not part of the networked state and setting it does not dirty the component. Hence only a
// warning in debug mode.
if (reg.NetID != null)
_sawmill.Warning($"Playing a component animation on a networked component {reg.Name} belonging to {ToPrettyString(component.Owner)}");
if (reg.NetID != null && compTrack.Property != null)
{
if (animatedComp.GetType().GetProperty(compTrack.Property) is { } property &&
property.HasCustomAttribute<AutoNetworkedFieldAttribute>())
{
_sawmill.Warning($"Playing a component animation on a networked component {reg.Name} belonging to {ToPrettyString(component.Owner)}");
}
}
}
#endif

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

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

View File

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

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,39 +18,19 @@ namespace Robust.Client.GameObjects
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentInit>(OnUserInterfaceInit);
SubscribeLocalEvent<ClientUserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
}
private void OnUserInterfaceInit(EntityUid uid, ClientUserInterfaceComponent component, ComponentInit args)
{
component._interfaces.Clear();
foreach (var data in component._interfaceData)
{
component._interfaces[data.UiKey] = data;
}
}
private void OnUserInterfaceShutdown(EntityUid uid, ClientUserInterfaceComponent component, ComponentShutdown args)
{
foreach (var bui in component.OpenInterfaces.Values)
{
bui.Dispose();
}
}
private void MessageReceived(BoundUIWrapMessage ev)
{
var uid = GetEntity(ev.Entity);
if (!TryComp<ClientUserInterfaceComponent>(uid, out var cmp))
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 = GetNetEntity(uid);
@@ -67,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:
@@ -78,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;
@@ -86,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);
@@ -97,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(GetNetEntity(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 CreateEntity(string? prototypeName, out MetaDataComponent metadata);
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;
@@ -54,7 +53,7 @@ public sealed class ClientDirtySystem : EntitySystem
var uid = args.BaseArgs.Owner;
var comp = args.BaseArgs.Component;
if (!_timing.InPrediction || !comp.NetSyncEnabled || IsClientSide(uid))
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.

View File

@@ -4,7 +4,9 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Microsoft.Extensions.ObjectPool;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Physics;
@@ -47,10 +49,18 @@ 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, (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 readonly HashSet<NetEntity> _stateEnts = new();
private readonly List<EntityUid> _toDelete = new();
private readonly List<Component> _toRemove = new();
private readonly Dictionary<NetEntity, Dictionary<ushort, ComponentState>> _outputData = new();
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
private readonly ObjectPool<Dictionary<ushort, ComponentState>> _compDataPool =
new DefaultObjectPool<Dictionary<ushort, ComponentState>>(new DictPolicy<ushort, ComponentState>(), 256);
private uint _metaCompNetId;
@@ -101,6 +111,13 @@ namespace Robust.Client.GameStates
public event Action<MsgStateLeavePvs>? PvsLeave;
#if DEBUG
/// <summary>
/// If true, this will cause received game states to be ignored. Used by integration tests.
/// </summary>
public bool DropStates;
#endif
/// <inheritdoc />
public void Initialize()
{
@@ -193,6 +210,10 @@ namespace Robust.Client.GameStates
private void HandleStateMessage(MsgState message)
{
#if DEBUG
if (DropStates)
return;
#endif
// We ONLY ack states that are definitely going to get applied. Otherwise the sever might assume that we
// applied a state containing entity-creation information, which it would then no longer send to us when
// we re-encounter this entity
@@ -267,18 +288,15 @@ namespace Robust.Client.GameStates
continue;
}
if (PredictionNeedsResetting)
try
{
try
{
ResetPredictedEntities();
}
catch (Exception e)
{
// avoid exception spam from repeatedly trying to reset the same entity.
_entitySystemManager.GetEntitySystem<ClientDirtySystem>().Reset();
_runtimeLog.LogException(e, "ResetPredictedEntities");
}
ResetPredictedEntities();
}
catch (Exception e)
{
// avoid exception spam from repeatedly trying to reset the same entity.
_entitySystemManager.GetEntitySystem<ClientDirtySystem>().Reset();
_runtimeLog.LogException(e, "ResetPredictedEntities");
}
// If we were waiting for a new state, we are now applying it.
@@ -467,20 +485,22 @@ namespace Robust.Client.GameStates
public void ResetPredictedEntities()
{
PredictionNeedsResetting = false;
using var _ = _prof.Group("ResetPredictedEntities");
using var __ = _timing.StartStateApplicationArea();
// This is terrible, and I hate it. This also needs to run even when prediction is disabled.
_entitySystemManager.GetEntitySystem<SharedGridTraversalSystem>().QueuedEvents.Clear();
_entitySystemManager.GetEntitySystem<TransformSystem>().Reset();
if (!PredictionNeedsResetting)
return;
PredictionNeedsResetting = false;
var countReset = 0;
var system = _entitySystemManager.GetEntitySystem<ClientDirtySystem>();
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
RemQueue<Component> toRemove = new();
// This is terrible, and I hate it.
_entitySystemManager.GetEntitySystem<SharedGridTraversalSystem>().QueuedEvents.Clear();
_entitySystemManager.GetEntitySystem<TransformSystem>().Reset();
foreach (var entity in system.DirtyEntities)
{
DebugTools.Assert(toRemove.Count == 0);
@@ -497,11 +517,7 @@ 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)
{
if (!comp.NetSyncEnabled)
continue;
@@ -549,13 +565,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()}");
@@ -592,16 +608,15 @@ namespace Robust.Client.GameStates
/// </remarks>
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
{
var outputData = new Dictionary<NetEntity, Dictionary<ushort, ComponentState>>();
var bus = _entityManager.EventBus;
foreach (var netEntity in createdEntities)
{
var createdEntity = _entityManager.GetEntity(netEntity);
var compData = new Dictionary<ushort, ComponentState>();
outputData.Add(netEntity, compData);
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
var compData = _compDataPool.Get();
_outputData.Add(netEntity, compData);
foreach (var (netId, component) in _entityManager.GetNetComponents(createdEntity))
foreach (var (netId, component) in meta.NetComponents)
{
if (!component.NetSyncEnabled)
continue;
@@ -612,7 +627,14 @@ namespace Robust.Client.GameStates
}
}
_processor.MergeImplicitData(outputData);
_processor.MergeImplicitData(_outputData);
foreach (var data in _outputData.Values)
{
_compDataPool.Return(data);
}
_outputData.Clear();
}
private void AckGameState(GameTick sequence)
@@ -690,11 +712,9 @@ namespace Robust.Client.GameStates
if (metaState == null)
throw new MissingMetadataException(es.NetEntity);
var uid = _entities.CreateEntity(metaState.PrototypeId);
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
_toCreate.Add(es.NetEntity, es);
_toApply.Add(uid, (false, GameTick.Zero, es, null));
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.
@@ -712,6 +732,8 @@ namespace Robust.Client.GameStates
var pending = _pendingReapplyNetStates.GetOrNew(owner);
pending.Add(type);
}
_entityManager.PendingNetEntityStates.Remove(es.NetEntity);
}
}
@@ -720,12 +742,11 @@ namespace Robust.Client.GameStates
foreach (var es in curSpan)
{
var uid = _entityManager.GetEntity(es.NetEntity);
if (!metas.TryGetComponent(uid, out var meta) || _toCreate.ContainsKey(es.NetEntity))
{
if (_toCreate.ContainsKey(es.NetEntity))
continue;
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
continue;
}
bool isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
if (isEnteringPvs)
@@ -739,7 +760,7 @@ namespace Robust.Client.GameStates
continue;
}
_toApply.Add(uid, (isEnteringPvs, meta.LastStateApplied, es, null));
_toApply.Add(uid.Value, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
meta.LastStateApplied = curState.ToSequence;
}
@@ -753,39 +774,46 @@ namespace Robust.Client.GameStates
{
foreach (var es in nextState.EntityStates.Span)
{
if (!_entityManager.TryGetEntity(es.NetEntity, out var uid))
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
continue;
DebugTools.Assert(metas.HasComponent(uid));
// 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.Value, out var state))
_toApply[uid.Value] = (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.Value] = (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)
{
if (_toApply.ContainsKey(uid))
// Original entity referencing the NetEntity may have been deleted.
if (!metas.TryGetComponent(uid, out var meta))
continue;
_toApply[uid] = (false, GameTick.Zero, null, null);
// 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);
_queuedBroadphaseUpdates.Clear();
// Apply entity states.
using (_prof.Group("Apply States"))
{
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)
@@ -798,7 +826,7 @@ namespace Robust.Client.GameStates
DebugTools.Assert(xform.Broadphase == BroadphaseData.Invalid);
xform.Broadphase = null;
if (!_toApply.TryGetValue(xform.ParentUid, out var parent) || !parent.EnteringPvs)
queuedBroadphaseUpdates.Add((entity, xform));
_queuedBroadphaseUpdates.Add((entity, xform));
}
_prof.WriteValue("Count", ProfData.Int32(_toApply.Count));
@@ -809,7 +837,7 @@ namespace Robust.Client.GameStates
{
try
{
foreach (var (uid, xform) in queuedBroadphaseUpdates)
foreach (var (uid, xform) in _queuedBroadphaseUpdates)
{
lookupSys.FindAndAddToEntityTree(uid, true, xform);
}
@@ -826,7 +854,7 @@ namespace Robust.Client.GameStates
{
try
{
ProcessDeletions(delSpan, xforms, metas, xformSys);
ProcessDeletions(delSpan, xforms, xformSys);
}
catch (Exception e)
{
@@ -863,17 +891,17 @@ namespace Robust.Client.GameStates
_sawmill.Info($"Resetting all entity states to tick {state.ToSequence}.");
// Construct hashset for set.Contains() checks.
_stateEnts.Clear();
var entityStates = state.EntityStates.Span;
var stateEnts = new HashSet<NetEntity>();
foreach (var entState in entityStates)
{
stateEnts.Add(entState.NetEntity);
_stateEnts.Add(entState.NetEntity);
}
var xforms = _entities.GetEntityQuery<TransformComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
var toDelete = new List<EntityUid>(Math.Max(64, _entities.EntityCount - stateEnts.Count));
_toDelete.Clear();
// Client side entities won't need the transform, but that should always be a tiny minority of entities
var metaQuery = _entityManager.AllEntityQueryEnumerator<MetaDataComponent, TransformComponent>();
@@ -884,14 +912,16 @@ namespace Robust.Client.GameStates
if (metadata.NetEntity.IsClientSide())
{
if (deleteClientEntities)
toDelete.Add(ent);
_toDelete.Add(ent);
continue;
}
if (stateEnts.Contains(netEnt))
if (_stateEnts.Contains(netEnt))
{
if (resetAllEntities || metadata.LastStateApplied > state.ToSequence)
metadata.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it?
continue;
}
@@ -909,13 +939,14 @@ namespace Robust.Client.GameStates
&& !deleteClientEntities // don't add duplicates
&& _entities.IsClientSide(child.Value))
{
toDelete.Add(child.Value);
_toDelete.Add(child.Value);
}
}
toDelete.Add(ent);
_toDelete.Add(ent);
}
foreach (var ent in toDelete)
foreach (var ent in _toDelete)
{
_entities.DeleteEntity(ent);
}
@@ -924,7 +955,6 @@ namespace Robust.Client.GameStates
private void ProcessDeletions(
ReadOnlySpan<NetEntity> delSpan,
EntityQuery<TransformComponent> xforms,
EntityQuery<MetaDataComponent> metas,
SharedTransformSystem xformSys)
{
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
@@ -995,6 +1025,7 @@ namespace Robust.Client.GameStates
// things like container insertion and ejection.
using var _ = _prof.Group("Leave PVS");
detached.EnsureCapacity(toDetach.Count);
foreach (var (tick, ents) in toDetach)
{
@@ -1017,9 +1048,7 @@ namespace Robust.Client.GameStates
{
foreach (var netEntity in entities)
{
var ent = _entityManager.GetEntity(netEntity);
if (!metas.TryGetComponent(ent, out var meta))
if (!_entityManager.TryGetEntityData(netEntity, out var ent, out var meta))
continue;
if (meta.LastStateApplied > maxTick)
@@ -1035,10 +1064,10 @@ namespace Robust.Client.GameStates
if (lastStateApplied.HasValue)
meta.LastStateApplied = lastStateApplied.Value;
var xform = xforms.GetComponent(ent);
var xform = xforms.GetComponent(ent.Value);
if (xform.ParentUid.IsValid())
{
lookupSys.RemoveFromEntityTree(ent, xform);
lookupSys.RemoveFromEntityTree(ent.Value, xform);
xform.Broadphase = BroadphaseData.Invalid;
// In some cursed scenarios an entity inside of a container can leave PVS without the container itself leaving PVS.
@@ -1047,13 +1076,13 @@ namespace Robust.Client.GameStates
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
containerSys.TryGetContainingContainer(xform.ParentUid, ent, out container, null, true))
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
{
container.Remove(ent, _entities, xform, meta, false, true);
container.Remove(ent.Value, _entities, xform, meta, false, true);
}
meta._flags |= MetaDataFlags.Detached;
xformSys.DetachParentToNull(ent, xform);
xformSys.DetachParentToNull(ent.Value, xform);
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
if (container != null)
@@ -1125,26 +1154,25 @@ 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)
{
_compStateWork.Clear();
var meta = _entityManager.GetComponent<MetaDataComponent>(uid);
var netEntity = meta.NetEntity;
// First remove any deleted components
if (curState?.NetComponents != null)
{
RemQueue<Component> toRemove = new();
foreach (var (id, comp) in _entities.GetNetComponents(uid))
_toRemove.Clear();
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
toRemove.Add(comp);
_toRemove.Add(comp);
}
foreach (var comp in toRemove)
foreach (var comp in _toRemove)
{
_entities.RemoveComponent(uid, comp);
_entities.RemoveComponent(uid, comp, meta);
}
}
@@ -1157,12 +1185,11 @@ namespace Robust.Client.GameStates
// the entity. most notably, all entities will have been ejected from their containers.
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);
@@ -1172,12 +1199,11 @@ namespace Robust.Client.GameStates
{
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;
@@ -1193,17 +1219,20 @@ 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);
}
}
@@ -1220,14 +1249,19 @@ namespace Robust.Client.GameStates
if (netId == null)
continue;
if (_compStateWork.ContainsKey(netId.Value) ||
!_entityManager.TryGetComponent(uid, type, out var comp) ||
if (!meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
!lastState.TryGetValue(netId.Value, out var lastCompState))
{
continue;
}
_compStateWork[netId.Value] = (comp, lastCompState, null);
ref var compState =
ref CollectionsMarshal.GetValueRefOrAddDefault(_compStateWork, netId.Value, out var exists);
if (exists)
continue;
compState = (comp, lastCompState, null);
}
}
@@ -1241,10 +1275,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
}
@@ -1392,12 +1426,11 @@ namespace Robust.Client.GameStates
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);
@@ -1405,20 +1438,24 @@ namespace Robust.Client.GameStates
}
// ensure we don't have any extra components
RemQueue<Component> toRemove = new();
foreach (var (id, comp) in _entities.GetNetComponents(uid))
_toRemove.Clear();
foreach (var (id, comp) in meta.NetComponents)
{
if (comp.NetSyncEnabled && !lastState.ContainsKey(id))
toRemove.Add(comp);
_toRemove.Add(comp);
}
foreach (var comp in toRemove)
foreach (var comp in _toRemove)
{
_entities.RemoveComponent(uid, comp);
}
}
#endregion
public bool IsQueuedForDetach(NetEntity entity)
=> _processor.IsQueuedForDetach(entity);
void IPostInjectInit.PostInject()
{
_sawmill = _logMan.GetSawmill(CVars.NetPredict.Name);

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;
@@ -361,9 +362,11 @@ namespace Robust.Client.GameStates
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;
}
@@ -379,7 +382,7 @@ namespace Robust.Client.GameStates
}
serverDelta.ApplyToFullState(implicitCompState);
fullRep[netId] = implicitCompState;
serverState = implicitCompState;
DebugTools.Assert(implicitCompState is IComponentDeltaState d && d.FullState);
}
}
@@ -401,6 +404,18 @@ namespace Robust.Client.GameStates
return _lastStateFullRep.TryGetValue(entity, out dictionary);
}
public bool IsQueuedForDetach(NetEntity entity)
{
// This isn't fast, but its just meant for use in tests & debug asserts.
foreach (var msg in _pvsDetachMessages.Values)
{
if (msg.Contains(entity))
return true;
}
return false;
}
public int CalculateBufferSize(GameTick fromTick)
{
bool foundState;

View File

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

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
@@ -336,10 +338,12 @@ namespace Robust.Client.Graphics.Clyde
}
var mapId = eye.Position.MapId;
if (mapId == MapId.Nullspace)
return;
// If this map has lighting disabled, return
var mapUid = _mapManager.GetMapEntityId(mapId);
if (!_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
if (!_entityManager.TryGetComponent<MapComponent>(mapUid, out var map) || !map.LightingEnabled)
{
return;
}

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

@@ -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;

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;

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

@@ -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)
@@ -83,11 +82,8 @@ namespace Robust.Client.Physics
continue;
}
xform.PrevPosition = position;
xform.PrevRotation = rotation;
xform.LerpParent = parentUid;
xform.NextPosition = xform.LocalPosition;
xform.NextRotation = xform.LocalRotation;
// Transform system will handle lerping.
_transform.SetLocalPositionRotation(uid, xform.LocalPosition, xform.LocalRotation, xform);
}
component.LerpData.Clear();

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
{

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

@@ -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

@@ -53,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

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Resources;
@@ -116,9 +118,9 @@ namespace Robust.Client.ResourceManagement
var dirType = stateObject.DirCount switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
1 => RsiDirectionType.Dir1,
4 => RsiDirectionType.Dir4,
8 => RsiDirectionType.Dir8,
_ => throw new InvalidOperationException()
};

View File

@@ -1,6 +1,7 @@
using System.IO;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;

View File

@@ -17,7 +17,7 @@
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.2" Condition="'$(UseSystemSqlite)' != 'True'" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.NFluidsynth" Version="0.1.1" PrivateAssets="compile" />
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.7" />
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" PrivateAssets="compile" />
<PackageReference Include="OpenTK.OpenAL" Version="4.7.5" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" PrivateAssets="compile" />

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Spawners;
namespace Robust.Client.Spawners;
public sealed class TimedDespawnSystem : SharedTimedDespawnSystem
{
protected override bool CanDelete(EntityUid uid)
{
return IsClientSide(uid);
}
}

View File

@@ -10,6 +10,7 @@ using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using static Robust.Client.UserInterface.Controls.BaseButton;
using static Robust.Client.UserInterface.Controls.LineEdit;
@@ -199,7 +200,7 @@ public sealed class EntitySpawningUIController : UIController
continue;
}
if (prototype.NoSpawn)
if (prototype.HideSpawnMenu)
{
continue;
}

View File

@@ -7,6 +7,7 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;

View File

@@ -1,5 +1,7 @@
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -21,7 +23,7 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public TextureRect DisplayRect { get; }
public RSI.State.Direction RsiDirection { get; } = RSI.State.Direction.South;
public RsiDirection RsiDirection { get; } = RsiDirection.South;
public AnimatedTextureRect()
{

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
using Timer = Robust.Shared.Timing.Timer;

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls

View File

@@ -98,6 +98,11 @@ namespace Robust.Client.UserInterface.Controls
OnTextChanged?.Invoke(new LineEditEventArgs(this, _text));
}
public void ForceSubmitText()
{
OnTextEntered?.Invoke(new LineEditEventArgs(this, _text));
}
/// <summary>
/// The text
/// </summary>
@@ -607,7 +612,7 @@ namespace Robust.Client.UserInterface.Controls
{
if (Editable)
{
OnTextEntered?.Invoke(new LineEditEventArgs(this, _text));
ForceSubmitText();
}
args.Handle();

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.BoxContainer;

View File

@@ -50,13 +50,13 @@ namespace Robust.Client.UserInterface.Controls
Action<Range> ev = _scrollValueChanged;
_hScrollBar = new HScrollBar
{
Visible = false,
Visible = _hScrollEnabled,
VerticalAlignment = VAlignment.Bottom,
HorizontalAlignment = HAlignment.Stretch
};
_vScrollBar = new VScrollBar
{
Visible = false,
Visible = _vScrollEnabled,
VerticalAlignment = VAlignment.Stretch,
HorizontalAlignment = HAlignment.Right
};

View File

@@ -2,6 +2,7 @@ using System;
using System.Numerics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Controls
@@ -14,7 +15,17 @@ namespace Robust.Client.UserInterface.Controls
/// for each enum value to see how the different options work.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public SplitResizeMode ResizeMode { get; set; }
public SplitResizeMode ResizeMode
{
get => _resizeMode;
set
{
_resizeMode = value;
_splitDragArea.Visible = value != SplitResizeMode.NotResizable;
}
}
private SplitResizeMode _resizeMode = SplitResizeMode.RespectChildrenMinSize;
/// <summary>
/// Width of the split in virtual pixels
@@ -30,7 +41,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
private float _splitWidth;
private float _splitWidth = 10;
/// <summary>
/// This width determines the minimum size of the draggable area around the split. This has no effect if it
@@ -38,11 +49,13 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
public float MinDraggableWidth = 10f;
public float DraggableWidth => MathF.Max(MinDraggableWidth, _splitWidth);
/// <summary>
/// Virtual pixel offset from the edge beyond which the split cannot be moved.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public float SplitEdgeSeparation { get; set; }
public float SplitEdgeSeparation { get; set; } = 10;
private float _splitStart;
@@ -58,6 +71,7 @@ namespace Robust.Client.UserInterface.Controls
_splitStart = value - _splitWidth / 2;
ClampSplitCenter();
InvalidateMeasure();
OnSplitResized?.Invoke();
}
}
@@ -81,13 +95,51 @@ namespace Robust.Client.UserInterface.Controls
}
}
private SplitState _splitState;
private bool _dragging;
private SplitState _splitState = SplitState.Auto;
private SplitOrientation _orientation;
private SplitStretchDirection _stretchDirection;
private SplitStretchDirection _stretchDirection = SplitStretchDirection.BottomRight;
private bool _dragging;
private float _dragOffset;
private bool Vertical => Orientation == SplitOrientation.Vertical;
private readonly SplitDragControl _splitDragArea = new();
/// <summary>
/// The upper/left control in the split container.
/// </summary>
public Control? First
{
get
{
if (ChildCount < 3)
return null;
DebugTools.AssertNotEqual(GetChild(0), _splitDragArea);
return GetChild(0);
}
}
/// <summary>
/// The lower/right control in the split container.
/// </summary>
public Control? Second
{
get
{
if (ChildCount < 3)
return null;
DebugTools.AssertNotEqual(GetChild(1), _splitDragArea);
return GetChild(1);
}
}
public (Control First, Control Second)? Splits => ChildCount < 3 ? null : (GetChild(0), GetChild(1));
public event Action? OnSplitResizeFinished;
public event Action? OnSplitResized;
/// <summary>
/// Whether the split position should be set manually or automatically.
/// </summary>
@@ -131,73 +183,53 @@ namespace Robust.Client.UserInterface.Controls
public SplitContainer()
{
MouseFilter = MouseFilterMode.Stop;
_splitState = SplitState.Auto;
_stretchDirection = SplitStretchDirection.BottomRight;
_dragging = false;
ResizeMode = SplitResizeMode.RespectChildrenMinSize;
SplitWidth = 10;
SplitEdgeSeparation = 10;
AddChild(_splitDragArea);
_splitDragArea.Visible = _resizeMode != SplitResizeMode.NotResizable;
_splitDragArea.DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize;
_splitDragArea.OnMouseUp += StopDragging;
_splitDragArea.OnMouseDown += StartDragging;
_splitDragArea.OnMouseMove += OnMove;
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
private void OnMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
if (ResizeMode == SplitResizeMode.NotResizable)
return;
if (ResizeMode == SplitResizeMode.NotResizable) return;
if (!_dragging)
return;
if (_dragging)
{
var newOffset = Vertical ? args.RelativePosition.Y : args.RelativePosition.X;
SplitCenter = newOffset;
DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize;
}
else
{
// on mouseover, check if they are over the split and change the cursor accordingly
var cursor = CursorShape.Arrow;
if (CanDragAt(args.RelativePosition))
{
cursor = Vertical ? CursorShape.VResize : CursorShape.HResize;
}
DefaultCursorShape = cursor;
}
// Source control might be either the container, or the separator.
// So we manually calculate the relative coordinates wrt the container.
var relative = args.GlobalPosition - GlobalPosition;
SplitCenter = Vertical ? relative.Y - _dragOffset : relative.X + _dragOffset;
}
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
protected override void ChildAdded(Control newChild)
{
base.KeyBindDown(args);
if (ResizeMode == SplitResizeMode.NotResizable) return;
if (_dragging || args.Function != EngineKeyFunctions.UIClick) return;
if (CanDragAt(args.RelativePosition))
{
_dragging = true;
_splitState = SplitState.Manual;
}
base.ChildAdded(newChild);
_splitDragArea.SetPositionLast();
}
protected internal override void KeyBindUp(GUIBoundKeyEventArgs args)
public void StartDragging(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (ResizeMode == SplitResizeMode.NotResizable || _dragging)
return;
if (args.Function != EngineKeyFunctions.UIClick) return;
_dragging = true;
_dragOffset = DraggableWidth / 2 - (Vertical ? args.RelativePosition.Y : args.RelativePosition.X);
_splitState = SplitState.Manual;
DefaultCursorShape = Vertical ? CursorShape.VResize : CursorShape.HResize;
}
private void StopDragging(GUIBoundKeyEventArgs args)
{
if (!_dragging)
return;
_dragging = false;
DefaultCursorShape = CursorShape.Arrow;
}
private bool CanDragAt(Vector2 relativePosition)
{
var distance = Vertical
? Math.Abs(relativePosition.Y - SplitCenter)
: Math.Abs(relativePosition.X - SplitCenter);
return distance <= _splitWidth || distance <= MinDraggableWidth;
OnSplitResizeFinished?.Invoke();
}
/// <summary>
@@ -218,7 +250,7 @@ namespace Robust.Client.UserInterface.Controls
desiredSplit += Vertical ? desiredSize.Value.Y - Size.Y : desiredSize.Value.X - Size.X;
_splitStart = MathHelper.Clamp(desiredSplit, SplitEdgeSeparation, splitMax);
if (ResizeMode == SplitResizeMode.RespectChildrenMinSize && ChildCount == 2)
if (ResizeMode == SplitResizeMode.RespectChildrenMinSize && ChildCount == 3)
{
var first = GetChild(0);
var second = GetChild(1);
@@ -234,7 +266,7 @@ namespace Robust.Client.UserInterface.Controls
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
if (ChildCount != 2)
if (ChildCount < 3)
{
return finalSize;
}
@@ -287,15 +319,23 @@ namespace Robust.Client.UserInterface.Controls
}
}
// location & size of the draggable area may be larger than the split area.
var dragCenter = _splitStart + _splitWidth / 2;
var dragWidth = DraggableWidth;
var dragStart = dragCenter - dragWidth / 2;
var dragEnd = dragCenter + dragWidth / 2;
if (Vertical)
{
first.Arrange(new UIBox2(0, 0, finalSize.X, _splitStart));
second.Arrange(new UIBox2(0, _splitStart + _splitWidth, finalSize.X, finalSize.Y));
_splitDragArea.Arrange(new UIBox2(0, dragStart, finalSize.X, dragEnd));
}
else
{
first.Arrange(new UIBox2(0, 0, _splitStart, finalSize.Y));
second.Arrange(new UIBox2(_splitStart + _splitWidth, 0, finalSize.X, finalSize.Y));
_splitDragArea.Arrange(new UIBox2(dragStart, 0, dragEnd, finalSize.Y));
}
return finalSize;
@@ -303,7 +343,7 @@ namespace Robust.Client.UserInterface.Controls
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
if (ChildCount != 2)
if (ChildCount < 3)
{
return Vector2.Zero;
}
@@ -420,5 +460,40 @@ namespace Robust.Client.UserInterface.Controls
/// </summary>
TopLeft,
}
/// <summary>
/// Simple control to intercept mous events and redirect them to the parent.
/// </summary>
private sealed class SplitDragControl : Control
{
public event Action<GUIBoundKeyEventArgs>? OnMouseDown;
public event Action<GUIBoundKeyEventArgs>? OnMouseUp;
public event Action<GUIMouseMoveEventArgs>? OnMouseMove;
public SplitDragControl()
{
MouseFilter = MouseFilterMode.Stop;
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
{
base.MouseMove(args);
OnMouseMove?.Invoke(args);
}
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
if (args.Function == EngineKeyFunctions.UIClick)
OnMouseDown?.Invoke(args);
}
protected internal override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
if (args.Function == EngineKeyFunctions.UIClick)
OnMouseUp?.Invoke(args);
}
}
}
}

View File

@@ -7,6 +7,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Direction = Robust.Shared.Maths.Direction;
namespace Robust.Client.UserInterface.Controls
{

View File

@@ -2,6 +2,7 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;

View File

@@ -2,6 +2,7 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Maths;

View File

@@ -3,6 +3,7 @@ using System.Numerics;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;

View File

@@ -1,16 +1,32 @@
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.CustomControls
{
public sealed class Tooltip : PanelContainer
{
private readonly Label _label;
private readonly RichTextLabel _label;
public string? Text
{
get => _label.Text;
set => _label.Text = value;
get => _label.GetMessage();
set
{
if (value == null)
{
_label.SetMessage(string.Empty);
}
else
{
_label.SetMessage(value);
}
}
}
public void SetMessage(FormattedMessage message)
{
_label.SetMessage(message);
}
/// <summary>
@@ -28,7 +44,7 @@ namespace Robust.Client.UserInterface.CustomControls
AddChild(vbox);
vbox.AddChild(_label = new Label());
vbox.AddChild(_label = new RichTextLabel());
}
protected override void FrameUpdate(FrameEventArgs args)

View File

@@ -2,6 +2,7 @@
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.StylesheetHelpers;

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;

View File

@@ -44,7 +44,7 @@ namespace Robust.Client.UserInterface
LayoutContainer.SetPosition(tooltip, new Vector2(screenPosition.X, screenPosition.Y - combinedMinSize.Y));
var right = tooltip.Position.X + combinedMinSize.X;
var top = tooltip.Position.Y - combinedMinSize.Y;
var top = tooltip.Position.Y;
if (right > screenBounds.X)
{

View File

@@ -69,12 +69,11 @@ public abstract class UIScreen : LayoutContainer
public void RemoveWidget<T>() where T : UIWidget, new()
{
if (_widgets.TryGetValue(typeof(T), out var widget))
{
RemoveChild(widget);
}
if (!_widgets.Remove(typeof(T), out var widget))
return;
_widgets.Remove(typeof(T));
widget.Parent?.RemoveChild(widget);
RemoveChildren(widget);
}
internal void OnRemoved()
@@ -103,6 +102,14 @@ public abstract class UIScreen : LayoutContainer
AddChild(widget);
}
public void AddWidgetDirect(UIWidget widget)
{
if (!_widgets.TryAdd(widget.GetType(), widget))
throw new Exception("Tried to add duplicate widget to screen!");
RegisterChildren(widget);
}
public T? GetWidget<T>() where T : UIWidget, new()
{
return (T?) _widgets.GetValueOrDefault(typeof(T));

View File

@@ -1,39 +1,40 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.Maths;
using static Robust.Client.GameObjects.SpriteComponent;
using RSIDirection = Robust.Client.Graphics.RSI.State.Direction;
using Direction = Robust.Shared.Maths.Direction;
namespace Robust.Client.Utility
{
public static class DirExt
{
public static Direction Convert(this RSIDirection dir)
public static Direction Convert(this RsiDirection dir)
{
switch (dir)
{
case RSIDirection.South:
case RsiDirection.South:
return Direction.South;
case RSIDirection.North:
case RsiDirection.North:
return Direction.North;
case RSIDirection.East:
case RsiDirection.East:
return Direction.East;
case RSIDirection.West:
case RsiDirection.West:
return Direction.West;
case RSIDirection.SouthEast:
case RsiDirection.SouthEast:
return Direction.SouthEast;
case RSIDirection.SouthWest:
case RsiDirection.SouthWest:
return Direction.SouthWest;
case RSIDirection.NorthEast:
case RsiDirection.NorthEast:
return Direction.NorthEast;
case RSIDirection.NorthWest:
case RsiDirection.NorthWest:
return Direction.NorthWest;
}
@@ -46,54 +47,54 @@ namespace Robust.Client.Utility
/// <param name="dir">The direction to round</param>
/// <returns><paramref name="dir"/> if it's a cardinal direction, otherwise either north or
/// south.</returns>
public static RSIDirection RoundToCardinal(this RSIDirection dir)
public static RsiDirection RoundToCardinal(this RsiDirection dir)
{
switch (dir)
{
case RSIDirection.NorthEast:
case RSIDirection.NorthWest:
return RSIDirection.North;
case RsiDirection.NorthEast:
case RsiDirection.NorthWest:
return RsiDirection.North;
case RSIDirection.SouthEast:
case RSIDirection.SouthWest:
return RSIDirection.South;
case RsiDirection.SouthEast:
case RsiDirection.SouthWest:
return RsiDirection.South;
default:
return dir;
}
}
public static RSIDirection Convert(this Direction dir, RSI.State.DirectionType type)
public static RsiDirection Convert(this Direction dir, RsiDirectionType type)
{
if (type == RSI.State.DirectionType.Dir1)
return RSIDirection.South;
if (type == RsiDirectionType.Dir1)
return RsiDirection.South;
switch (dir)
{
case Direction.North:
return RSIDirection.North;
return RsiDirection.North;
case Direction.South:
return RSIDirection.South;
return RsiDirection.South;
case Direction.East:
return RSIDirection.East;
return RsiDirection.East;
case Direction.West:
return RSIDirection.West;
return RsiDirection.West;
}
var rsiDir = dir switch
{
Direction.SouthEast => RSIDirection.SouthEast,
Direction.SouthWest => RSIDirection.SouthWest,
Direction.NorthEast => RSIDirection.NorthEast,
Direction.NorthWest => RSIDirection.NorthWest,
Direction.SouthEast => RsiDirection.SouthEast,
Direction.SouthWest => RsiDirection.SouthWest,
Direction.NorthEast => RsiDirection.NorthEast,
Direction.NorthWest => RsiDirection.NorthWest,
_ => throw new ArgumentException($"Unknown dir: {dir}.", nameof(dir))
};
// Round down to a four-way direction if appropriate.
if (type == RSI.State.DirectionType.Dir4)
if (type == RsiDirectionType.Dir4)
{
return RoundToCardinal(rsiDir);
}
@@ -111,18 +112,18 @@ namespace Robust.Client.Utility
return (Direction)(((int)dir + 1) % 8);
}
public static RSIDirection ToRsiDirection(this Angle angle, RSI.State.DirectionType type)
public static RsiDirection ToRsiDirection(this Angle angle, RsiDirectionType type)
{
return type switch
{
RSI.State.DirectionType.Dir1 => RSIDirection.South,
RSI.State.DirectionType.Dir4 => angle.GetCardinalDir().Convert(type),
RSI.State.DirectionType.Dir8 => angle.GetDir().Convert(type),
RsiDirectionType.Dir1 => RsiDirection.South,
RsiDirectionType.Dir4 => angle.GetCardinalDir().Convert(type),
RsiDirectionType.Dir8 => angle.GetDir().Convert(type),
_ => throw new ArgumentOutOfRangeException($"Unknown rsi direction type: {type}.", nameof(type))
};
}
public static RSIDirection OffsetRsiDir(this RSIDirection dir, DirectionOffset offset)
public static RsiDirection OffsetRsiDir(this RsiDirection dir, DirectionOffset offset)
{
// There is probably a better way to do this.
// Eh.
@@ -136,32 +137,32 @@ namespace Robust.Client.Utility
case DirectionOffset.Clockwise:
return dir switch
{
RSIDirection.North => RSIDirection.East,
RSIDirection.East => RSIDirection.South,
RSIDirection.South => RSIDirection.West,
RSIDirection.West => RSIDirection.North,
RsiDirection.North => RsiDirection.East,
RsiDirection.East => RsiDirection.South,
RsiDirection.South => RsiDirection.West,
RsiDirection.West => RsiDirection.North,
_ => throw new NotImplementedException()
};
case DirectionOffset.CounterClockwise:
return dir switch
{
RSIDirection.North => RSIDirection.West,
RSIDirection.East => RSIDirection.North,
RSIDirection.South => RSIDirection.East,
RSIDirection.West => RSIDirection.South,
RsiDirection.North => RsiDirection.West,
RsiDirection.East => RsiDirection.North,
RsiDirection.South => RsiDirection.East,
RsiDirection.West => RsiDirection.South,
_ => throw new NotImplementedException()
};
case DirectionOffset.Flip:
switch (dir)
{
case RSIDirection.North:
return RSIDirection.South;
case RSIDirection.East:
return RSIDirection.West;
case RSIDirection.South:
return RSIDirection.North;
case RSIDirection.West:
return RSIDirection.East;
case RsiDirection.North:
return RsiDirection.South;
case RsiDirection.East:
return RsiDirection.West;
case RsiDirection.South:
return RsiDirection.North;
case RsiDirection.West:
return RsiDirection.East;
default:
throw new NotImplementedException();
}

View File

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

View File

@@ -10,6 +10,7 @@ using Robust.Client.ViewVariables.Editors;
using Robust.Client.ViewVariables.Instances;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
@@ -190,6 +191,11 @@ namespace Robust.Client.ViewVariables
return new VVPropEditorEntityUid();
}
if (type == typeof(NetEntity))
{
return new VVPropEditorNetEntity();
}
if (type == typeof(Color))
{
return new VVPropEditorColor();
@@ -227,7 +233,7 @@ namespace Robust.Client.ViewVariables
instance = new ViewVariablesInstanceObject(this, _robustSerializer);
}
var window = new DefaultWindow {Title = "View Variables"};
var window = new DefaultWindow {Title = Loc.GetString("view-variables")};
instance.Initialize(window, obj);
window.OnClose += () => _closeInstance(instance, false);
_windows.Add(instance, window);
@@ -245,7 +251,7 @@ namespace Robust.Client.ViewVariables
{
var window = new DefaultWindow
{
Title = "View Variables",
Title = Loc.GetString("view-variables"),
SetSize = _defaultWindowSize
};
var loadingLabel = new Label {Text = "Retrieving remote object data from server..."};

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