Compare commits

..

16 Commits

Author SHA1 Message Date
PJB3005
c74ec36560 Version: 156.0.8 2025-09-26 13:42:22 +02:00
PJB3005
f8a6491d13 Validate that content assemblies have a limited list of names.
Also, only read assemblies once from disk

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

    Fix incorrect path combine in DirLoader and WritableDirProvider

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

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

    Move CEF cache out of data directory

    Don't want content messing with this...

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

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

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

    Update SpaceWizards.NFluidSynth to 0.2.2

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

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

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

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

Reported by @NarryG and @nyeogmi

* Sandbox updates

* Update ImageSharp again

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

16
.github/CODEOWNERS vendored
View File

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

View File

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

74
Directory.Packages.props Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -432,7 +432,7 @@ namespace Robust.Client
_parallelMgr.Initialize();
_prof.Initialize();
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)

View File

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

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Prometheus;
using Robust.Client.GameStates;
using Robust.Client.Player;
@@ -17,7 +16,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Manager for entities -- controls things like template loading and instantiation
/// </summary>
public sealed partial class ClientEntityManager : EntityManager, IClientEntityManagerInternal
public sealed class ClientEntityManager : EntityManager, IClientEntityManagerInternal
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientNetManager _networkManager = default!;
@@ -26,6 +25,8 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
protected override int NextEntityUid { get; set; } = EntityUid.ClientUid + 1;
public override void Initialize()
{
SetupNetworking();
@@ -36,16 +37,13 @@ namespace Robust.Client.GameObjects
public override void FlushEntities()
{
// Server doesn't network deletions on client shutdown so we need to
// manually clear these out or risk stale data getting used.
PendingNetEntityStates.Clear();
using var _ = _gameTiming.StartStateApplicationArea();
base.FlushEntities();
}
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, out MetaDataComponent metadata)
EntityUid IClientEntityManagerInternal.CreateEntity(string? prototypeName, EntityUid uid)
{
return base.CreateEntity(prototypeName, out metadata);
return base.CreateEntity(prototypeName, uid);
}
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta)
@@ -66,12 +64,9 @@ namespace Robust.Client.GameObjects
base.DirtyEntity(uid, meta);
}
public override void QueueDeleteEntity(EntityUid? uid)
public override void QueueDeleteEntity(EntityUid uid)
{
if (uid == null)
return;
if (IsClientSide(uid.Value))
if (uid.IsClientSide())
{
base.QueueDeleteEntity(uid);
return;
@@ -82,7 +77,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.Value)}. Trace: {Environment.StackTrace}");
LogManager.RootSawmill.Error($"Predicting the queued deletion of a networked entity: {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
}
/// <inheritdoc />
@@ -93,12 +88,12 @@ namespace Robust.Client.GameObjects
base.Dirty(uid, component, meta);
}
public override EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metaDataComponent = null)
public override EntityStringRepresentation ToPrettyString(EntityUid uid)
{
if (_playerManager.LocalPlayer?.ControlledEntity == uid)
return base.ToPrettyString(uid) with { Session = _playerManager.LocalPlayer.Session };
return base.ToPrettyString(uid);
else
return base.ToPrettyString(uid);
}
public override void RaisePredictiveEvent<T>(T msg)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
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;
@@ -15,35 +13,8 @@ public sealed class EyeSystem : SharedEyeSystem
{
base.Initialize();
SubscribeLocalEvent<EyeComponent, ComponentInit>(OnInit);
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();
SubscribeLocalEvent<EyeComponent, ComponentRemove>(OnRemove);
SubscribeLocalEvent<EyeComponent, ComponentHandleState>(OnHandleState);
}
private void OnInit(EntityUid uid, EyeComponent component, ComponentInit args)
@@ -51,35 +22,39 @@ public sealed class EyeSystem : SharedEyeSystem
component._eye = new Eye
{
Position = Transform(uid).MapPosition,
Zoom = component.Zoom,
DrawFov = component.DrawFov,
Rotation = component.Rotation,
Zoom = component._setZoomOnInitialize,
DrawFov = component._setDrawFovOnInitialize
};
}
/// <inheritdoc />
public override void FrameUpdate(float frameTime)
{
var query = AllEntityQuery<EyeComponent>();
while (query.MoveNext(out var uid, out var eyeComponent))
if ((_eyeManager.CurrentEye == component._eye) != component._setCurrentOnInitialize)
{
if (eyeComponent._eye == null)
continue;
if (!TryComp<TransformComponent>(eyeComponent.Target, out var xform))
if (component._setCurrentOnInitialize)
{
xform = Transform(uid);
eyeComponent.Target = null;
_eyeManager.ClearCurrentEye();
}
else
{
_eyeManager.CurrentEye = component._eye;
}
eyeComponent._eye.Position = xform.MapPosition;
}
}
}
/// <summary>
/// Raised on an entity when it is attached to one with an <see cref="EyeComponent"/>
/// </summary>
[ByRefEvent]
public readonly record struct EyeAttachedEvent(EntityUid Entity, EyeComponent Component);
private void OnRemove(EntityUid uid, EyeComponent component, ComponentRemove args)
{
component.Current = false;
}
private void OnHandleState(EntityUid uid, EyeComponent component, ref ComponentHandleState args)
{
if (args.Current is not EyeComponentState state)
{
return;
}
component.DrawFov = state.DrawFov;
// TODO: Should be a way for content to override lerping and lerp the zoom
component.Zoom = state.Zoom;
component.Offset = state.Offset;
component.VisibilityMask = state.VisibilityMask;
}
}

View File

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

View File

@@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects
/// <param name="message">Arguments for this event.</param>
/// <param name="replay">if true, current cmd state will not be checked or updated - use this for "replaying" an
/// old input that was saved or buffered until further processing could be done</param>
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, IFullInputCmdMessage message, bool replay = false)
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
{
#if DEBUG
@@ -78,27 +78,14 @@ namespace Robust.Client.GameObjects
continue;
// local handlers can block sending over the network.
if (handler.HandleCmdMessage(EntityManager, session, message))
if (handler.HandleCmdMessage(session, message))
{
return true;
}
}
// send it off to the server
var clientMsg = (ClientFullInputCmdMessage)message;
var fullMsg = new FullInputCmdMessage(
clientMsg.Tick,
clientMsg.SubTick,
(int)clientMsg.InputSequence,
clientMsg.InputFunctionId,
clientMsg.State,
GetNetCoordinates(clientMsg.Coordinates),
clientMsg.ScreenCoordinates)
{
Uid = GetNetEntity(clientMsg.Uid)
};
DispatchInputCommand(clientMsg, fullMsg);
DispatchInputCommand(message);
return false;
}
@@ -106,7 +93,7 @@ namespace Robust.Client.GameObjects
/// Handle a predicted input command.
/// </summary>
/// <param name="inputCmd">Input command to handle as predicted.</param>
public void PredictInputCommand(IFullInputCmdMessage inputCmd)
public void PredictInputCommand(FullInputCmdMessage inputCmd)
{
DebugTools.AssertNotNull(_playerManager.LocalPlayer);
@@ -116,16 +103,15 @@ namespace Robust.Client.GameObjects
var session = _playerManager.LocalPlayer!.Session;
foreach (var handler in BindRegistry.GetHandlers(keyFunc))
{
if (handler.HandleCmdMessage(EntityManager, session, inputCmd))
break;
if (handler.HandleCmdMessage(session, inputCmd)) break;
}
Predicted = false;
}
private void DispatchInputCommand(ClientFullInputCmdMessage clientMsg, FullInputCmdMessage message)
private void DispatchInputCommand(FullInputCmdMessage message)
{
_stateManager.InputCommandDispatched(clientMsg, message);
_stateManager.InputCommandDispatched(message);
EntityManager.EntityNetManager?.SendSystemNetworkMessage(message, message.InputSequence);
}
@@ -166,7 +152,7 @@ namespace Robust.Client.GameObjects
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
GetNetCoordinates(coords), new ScreenCoordinates(0, 0, default), NetEntity.Invalid);
coords, new ScreenCoordinates(0, 0, default), EntityUid.Invalid);
HandleInputCommand(localPlayer.Session, keyFunction, message);
}

View File

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

View File

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

View File

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

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using JetBrains.Annotations;
@@ -24,6 +25,11 @@ 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.
@@ -42,77 +48,21 @@ namespace Robust.Client.GameObjects
_lerpingTransforms.Clear();
}
public override void ActivateLerp(EntityUid uid, TransformComponent xform)
public override void ActivateLerp(TransformComponent xform)
{
// This lerping logic is pretty convoluted and generally assumes that the client does not mispredict.
// A more foolproof solution would be to just cache the coordinates at which any given entity was most
// recently rendered and using that as the lerp origin. However that'd require enumerating over all entities
// every tick which is pretty icky.
// The general considerations are:
// - If the client receives a server state for an entity moving from a->b and predicts nothing else, then it
// should show the entity lerping.
// - If the client predicts an entity will move while already lerping due to a state-application, it should
// clear the state's lerp, under the assumption that the client predicted the state and already rendered
// the entity in the 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;
if (xform.ActivelyLerping)
return;
}
xform.LastLerp = _gameTiming.CurTick;
if (!_gameTiming.IsFirstTimePredicted)
{
xform.ActivelyLerping = false;
return;
}
xform.ActivelyLerping = true;
_lerpingTransforms.Add(xform);
}
if (!xform.ActivelyLerping)
{
_lerpingTransforms.Add(xform);
xform.ActivelyLerping = true;
xform.PredictedLerp = true;
xform.PrevRotation = xform._localRotation;
xform.PrevPosition = xform._localPosition;
xform.LerpParent = xform.ParentUid;
return;
}
if (!xform.PredictedLerp || xform.LerpParent != xform.ParentUid)
{
// Existing lerp was not due to prediction, but due to state application. That lerp should already
// have been rendered, so we will start a new lerp from the current position.
xform.PrevRotation = xform._localRotation;
xform.PrevPosition = xform._localPosition;
xform.LerpParent = xform.ParentUid;
}
public override void DeactivateLerp(TransformComponent component)
{
// this should cause the lerp to do nothing
component.NextPosition = null;
component.NextRotation = null;
component.LerpParent = EntityUid.Invalid;
}
public override void FrameUpdate(float frameTime)
@@ -124,13 +74,11 @@ 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.ActivelyLerping
&& transform.LerpParent == transform.ParentUid
if (transform.LerpParent == transform.ParentUid
&& transform.ParentUid.IsValid()
&& !transform.Deleted)
{
@@ -142,7 +90,8 @@ namespace Robust.Client.GameObjects
if (distance is > MinInterpolationDistanceSquared and < MaxInterpolationDistanceSquared)
{
SetLocalPositionNoLerp(uid, Vector2.Lerp(lerpSource, lerpDest, step), transform);
transform.LocalPosition = Vector2.Lerp(lerpSource, lerpDest, step);
// Setting LocalPosition clears LerpPosition so fix that.
transform.NextPosition = lerpDest;
found = true;
}
@@ -152,9 +101,15 @@ namespace Robust.Client.GameObjects
{
var lerpDest = transform.NextRotation.Value;
var lerpSource = transform.PrevRotation;
SetLocalRotationNoLerp(uid, Angle.Lerp(lerpSource, lerpDest, step), transform);
transform.NextRotation = lerpDest;
found = true;
var distance = Math.Abs(Angle.ShortestDistance(lerpDest, lerpSource));
if (distance is > MinInterpolationAngle and < MaxInterpolationAngle)
{
transform.LocalRotation = Angle.Lerp(lerpSource, lerpDest, step);
// Setting LocalRotation clears LerpAngle so fix that.
transform.NextRotation = lerpDest;
found = true;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -4,9 +4,7 @@ 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;
@@ -49,18 +47,8 @@ namespace Robust.Client.GameStates
= new();
// Game state dictionaries that get used every tick.
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 readonly Dictionary<EntityUid, (bool EnteringPvs, GameTick LastApplied, EntityState? curState, EntityState?nextState)> _toApply = new();
private readonly Dictionary<EntityUid, EntityState> _toCreate = new();
private uint _metaCompNetId;
@@ -111,13 +99,6 @@ 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()
{
@@ -176,7 +157,7 @@ namespace Robust.Client.GameStates
}
}
public void InputCommandDispatched(ClientFullInputCmdMessage clientMessage, FullInputCmdMessage message)
public void InputCommandDispatched(FullInputCmdMessage message)
{
if (!IsPredictionEnabled)
{
@@ -210,10 +191,6 @@ 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
@@ -224,7 +201,7 @@ namespace Robust.Client.GameStates
public void UpdateFullRep(GameState state, bool cloneDelta = false)
=> _processor.UpdateFullRep(state, cloneDelta);
public Dictionary<NetEntity, Dictionary<ushort, ComponentState>> GetFullRep()
public Dictionary<EntityUid, Dictionary<ushort, ComponentState>> GetFullRep()
=> _processor.GetFullRep();
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
@@ -233,7 +210,7 @@ namespace Robust.Client.GameStates
PvsLeave?.Invoke(message);
}
public void QueuePvsDetach(List<NetEntity> entities, GameTick tick)
public void QueuePvsDetach(List<EntityUid> entities, GameTick tick)
{
_processor.AddLeavePvsMessage(entities, tick);
if (_replayRecording.IsRecording)
@@ -288,15 +265,18 @@ namespace Robust.Client.GameStates
continue;
}
try
if (PredictionNeedsResetting)
{
ResetPredictedEntities();
}
catch (Exception e)
{
// avoid exception spam from repeatedly trying to reset the same entity.
_entitySystemManager.GetEntitySystem<ClientDirtySystem>().Reset();
_runtimeLog.LogException(e, "ResetPredictedEntities");
try
{
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.
@@ -318,7 +298,7 @@ namespace Robust.Client.GameStates
_processor.UpdateFullRep(curState);
}
IEnumerable<NetEntity> createdEntities;
IEnumerable<EntityUid> createdEntities;
using (_prof.Group("ApplyGameState"))
{
if (_timing.LastProcessedTick < targetProcessedTick && nextState != null)
@@ -343,7 +323,7 @@ namespace Robust.Client.GameStates
catch (MissingMetadataException e)
{
// Something has gone wrong. Probably a missing meta-data component. Perhaps a full server state will fix it.
RequestFullState(e.NetEntity);
RequestFullState(e.Uid);
throw;
}
#endif
@@ -412,10 +392,10 @@ namespace Robust.Client.GameStates
}
}
public void RequestFullState(NetEntity? missingEntity = null)
public void RequestFullState(EntityUid? missingEntity = null)
{
_sawmill.Info("Requesting full server state");
_network.ClientSendMessage(new MsgStateRequestFull { Tick = _timing.LastRealTick , MissingEntity = missingEntity ?? NetEntity.Invalid });
_network.ClientSendMessage(new MsgStateRequestFull { Tick = _timing.LastRealTick , MissingEntity = missingEntity ?? EntityUid.Invalid });
_processor.RequestFullState();
}
@@ -485,22 +465,20 @@ 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>();
var query = _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);
@@ -508,8 +486,7 @@ namespace Robust.Client.GameStates
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($"Entity {entity} was made dirty.");
if (!metaQuery.TryGetComponent(entity, out var meta) ||
!_processor.TryGetLastServerStates(meta.NetEntity, out var last))
if (!_processor.TryGetLastServerStates(entity, out var last))
{
// Entity was probably deleted on the server so do nothing.
continue;
@@ -517,7 +494,11 @@ namespace Robust.Client.GameStates
countReset += 1;
foreach (var (netId, comp) in meta.NetComponents)
var netComps = _entityManager.GetNetComponentsOrNull(entity);
if (netComps == null)
continue;
foreach (var (netId, comp) in netComps.Value)
{
if (!comp.NetSyncEnabled)
continue;
@@ -565,13 +546,13 @@ namespace Robust.Client.GameStates
{
foreach (var netId in netIds)
{
if (meta.NetComponents.ContainsKey(netId))
if (_entities.HasComponent(entity, netId))
continue;
if (!last.TryGetValue(netId, out var state))
continue;
var comp = _entityManager.AddComponent(entity, netId, meta);
var comp = _entityManager.AddComponent(entity, netId);
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was removed: {comp.GetType()}");
@@ -583,6 +564,7 @@ namespace Robust.Client.GameStates
}
}
var meta = query.GetComponent(entity);
DebugTools.Assert(meta.EntityLastModifiedTick > _timing.LastRealTick);
meta.EntityLastModifiedTick = _timing.LastRealTick;
}
@@ -606,17 +588,17 @@ namespace Robust.Client.GameStates
/// initial server state for any newly created entity. It does this by simply using the standard <see
/// cref="IEntityManager.GetComponentState(IEventBus, IComponent)"/>.
/// </remarks>
private void MergeImplicitData(IEnumerable<NetEntity> createdEntities)
private void MergeImplicitData(IEnumerable<EntityUid> createdEntities)
{
var outputData = new Dictionary<EntityUid, Dictionary<ushort, ComponentState>>();
var bus = _entityManager.EventBus;
foreach (var netEntity in createdEntities)
foreach (var createdEntity in createdEntities)
{
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
var compData = _compDataPool.Get();
_outputData.Add(netEntity, compData);
var compData = new Dictionary<ushort, ComponentState>();
outputData.Add(createdEntity, compData);
foreach (var (netId, component) in meta.NetComponents)
foreach (var (netId, component) in _entityManager.GetNetComponents(createdEntity))
{
if (!component.NetSyncEnabled)
continue;
@@ -627,14 +609,7 @@ namespace Robust.Client.GameStates
}
}
_processor.MergeImplicitData(_outputData);
foreach (var data in _outputData.Values)
{
_compDataPool.Return(data);
}
_outputData.Clear();
_processor.MergeImplicitData(outputData);
}
private void AckGameState(GameTick sequence)
@@ -642,7 +617,7 @@ namespace Robust.Client.GameStates
_network.ClientSendMessage(new MsgStateAck() { Sequence = sequence });
}
public IEnumerable<NetEntity> ApplyGameState(GameState curState, GameState? nextState)
public IEnumerable<EntityUid> ApplyGameState(GameState curState, GameState? nextState)
{
using var _ = _timing.StartStateApplicationArea();
@@ -662,7 +637,7 @@ namespace Robust.Client.GameStates
_config.TickProcessMessages();
}
(IEnumerable<NetEntity> Created, List<NetEntity> Detached) output;
(IEnumerable<EntityUid> Created, List<EntityUid> Detached) output;
using (_prof.Group("Entity"))
{
output = ApplyEntityStates(curState, nextState);
@@ -681,7 +656,7 @@ namespace Robust.Client.GameStates
return output.Created;
}
private (IEnumerable<NetEntity> Created, List<NetEntity> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
private (IEnumerable<EntityUid> Created, List<EntityUid> Detached) ApplyEntityStates(GameState curState, GameState? nextState)
{
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
@@ -690,7 +665,6 @@ namespace Robust.Client.GameStates
var enteringPvs = 0;
_toApply.Clear();
_toCreate.Clear();
_pendingReapplyNetStates.Clear();
var curSpan = curState.EntityStates.Span;
// Create new entities
@@ -701,40 +675,21 @@ namespace Robust.Client.GameStates
foreach (var es in curSpan)
{
if (_entityManager.TryGetEntity(es.NetEntity, out var nUid))
{
DebugTools.Assert(_entityManager.EntityExists(nUid));
if (metas.HasComponent(es.Uid))
continue;
}
count++;
var uid = es.Uid;
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?.FirstOrDefault(c => c.NetID == _metaCompNetId).State;
if (metaState == null)
throw new MissingMetadataException(es.NetEntity);
throw new MissingMetadataException(uid);
var uid = _entities.CreateEntity(metaState.PrototypeId, out var newMeta);
_toCreate.Add(es.NetEntity, es);
_toApply.Add(uid, (es.NetEntity, newMeta, false, GameTick.Zero, es, null));
_entities.CreateEntity(metaState.PrototypeId, uid);
_toCreate.Add(uid, es);
_toApply.Add(uid, (false, GameTick.Zero, es, null));
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entityManager.ClearNetEntity(newMeta.NetEntity);
_entityManager.SetNetEntity(uid, es.NetEntity, newMeta);
var newMeta = metas.GetComponent(uid);
newMeta.LastStateApplied = curState.ToSequence;
// Check if there's any component states awaiting this entity.
if (_entityManager.PendingNetEntityStates.TryGetValue(es.NetEntity, out var value))
{
foreach (var (type, owner) in value)
{
var pending = _pendingReapplyNetStates.GetOrNew(owner);
pending.Add(type);
}
_entityManager.PendingNetEntityStates.Remove(es.NetEntity);
}
}
_prof.WriteValue("Count", ProfData.Int32(count));
@@ -742,11 +697,10 @@ namespace Robust.Client.GameStates
foreach (var es in curSpan)
{
if (_toCreate.ContainsKey(es.NetEntity))
continue;
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
if (!metas.TryGetComponent(es.Uid, out var meta) || _toCreate.ContainsKey(es.Uid))
{
continue;
}
bool isEnteringPvs = (meta.Flags & MetaDataFlags.Detached) != 0;
if (isEnteringPvs)
@@ -760,7 +714,7 @@ namespace Robust.Client.GameStates
continue;
}
_toApply.Add(uid.Value, (es.NetEntity, meta, isEnteringPvs, meta.LastStateApplied, es, null));
_toApply.Add(es.Uid, (isEnteringPvs, meta.LastStateApplied, es, null));
meta.LastStateApplied = curState.ToSequence;
}
@@ -774,46 +728,30 @@ namespace Robust.Client.GameStates
{
foreach (var es in nextState.EntityStates.Span)
{
if (!_entityManager.TryGetEntityData(es.NetEntity, out var uid, out var meta))
var uid = es.Uid;
if (!metas.TryGetComponent(uid, out var meta))
continue;
// Does the next state actually have any future information about this entity that could be used for interpolation?
if (es.EntityLastModified != nextState.ToSequence)
continue;
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);
if (_toApply.TryGetValue(uid, out var state))
_toApply[uid] = (state.EnteringPvs, state.LastApplied, state.curState, es);
else
state = (es.NetEntity, meta, false, GameTick.Zero, null, es);
_toApply[uid] = (false, GameTick.Zero, null, es);
}
}
// Check pending states and see if we need to force any entities to re-run component states.
foreach (var uid in _pendingReapplyNetStates.Keys)
{
// Original entity referencing the NetEntity may have been deleted.
if (!metas.TryGetComponent(uid, out var meta))
continue;
// State already being re-applied so don't bulldoze it.
ref var state = ref CollectionsMarshal.GetValueRefOrAddDefault(_toApply, uid, out var exists);
if (exists)
continue;
state = (meta.NetEntity, meta, false, GameTick.Zero, null, null);
}
_queuedBroadphaseUpdates.Clear();
var queuedBroadphaseUpdates = new List<(EntityUid, TransformComponent)>(enteringPvs);
// Apply entity states.
using (_prof.Group("Apply States"))
{
foreach (var (entity, data) in _toApply)
{
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
HandleEntityState(entity, _entities.EventBus, data.curState,
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
if (!data.EnteringPvs)
@@ -826,9 +764,8 @@ 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));
}
@@ -837,7 +774,7 @@ namespace Robust.Client.GameStates
{
try
{
foreach (var (uid, xform) in _queuedBroadphaseUpdates)
foreach (var (uid, xform) in queuedBroadphaseUpdates)
{
lookupSys.FindAndAddToEntityTree(uid, true, xform);
}
@@ -854,7 +791,7 @@ namespace Robust.Client.GameStates
{
try
{
ProcessDeletions(delSpan, xforms, xformSys);
ProcessDeletions(delSpan, xforms, metas, xformSys);
}
catch (Exception e)
{
@@ -891,40 +828,38 @@ 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<EntityUid>(entityStates.Length);
foreach (var entState in entityStates)
{
_stateEnts.Add(entState.NetEntity);
stateEnts.Add(entState.Uid);
}
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
_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>();
while (metaQuery.MoveNext(out var ent, out var metadata, out var xform))
var currentEnts = _entities.GetEntities();
var toDelete = new List<EntityUid>(Math.Max(64, _entities.EntityCount - stateEnts.Count));
foreach (var ent in currentEnts)
{
var netEnt = metadata.NetEntity;
if (metadata.NetEntity.IsClientSide())
if (ent.IsClientSide())
{
if (deleteClientEntities)
_toDelete.Add(ent);
toDelete.Add(ent);
continue;
}
if (_stateEnts.Contains(netEnt))
if (stateEnts.Contains(ent) && metas.TryGetComponent(ent, out var meta))
{
if (resetAllEntities || metadata.LastStateApplied > state.ToSequence)
metadata.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it?
if (resetAllEntities || meta.LastStateApplied > state.ToSequence)
meta.LastStateApplied = GameTick.Zero; // TODO track last-state-applied for individual components? Is it even worth it?
continue;
}
if (!xforms.TryGetComponent(ent, out var xform))
continue;
// This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to
// null. First we will detach the parent in order to reduce the number of broadphase/lookup updates.
xformSys.DetachParentToNull(ent, xform);
@@ -937,24 +872,24 @@ namespace Robust.Client.GameStates
if (deleteClientChildren
&& !deleteClientEntities // don't add duplicates
&& _entities.IsClientSide(child.Value))
&& child.Value.IsClientSide())
{
_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);
}
}
private void ProcessDeletions(
ReadOnlySpan<NetEntity> delSpan,
ReadOnlySpan<EntityUid> delSpan,
EntityQuery<TransformComponent> xforms,
EntityQuery<MetaDataComponent> metas,
SharedTransformSystem xformSys)
{
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
@@ -969,19 +904,13 @@ namespace Robust.Client.GameStates
using var _ = _prof.Group("Deletion");
foreach (var netEntity in delSpan)
foreach (var id in delSpan)
{
// Don't worry about this for later.
_entityManager.PendingNetEntityStates.Remove(netEntity);
if (!_entityManager.TryGetEntity(netEntity, out var id))
continue;
if (!xforms.TryGetComponent(id, out var xform))
continue; // Already deleted? or never sent to us?
// First, a single recursive map change
xformSys.DetachParentToNull(id.Value, xform);
xformSys.DetachParentToNull(id, xform);
// Then detach all children.
var childEnumerator = xform.ChildEnumerator;
@@ -991,12 +920,12 @@ namespace Robust.Client.GameStates
}
// Finally, delete the entity.
_entities.DeleteEntity(id.Value);
_entities.DeleteEntity(id);
}
_prof.WriteValue("Count", ProfData.Int32(delSpan.Length));
}
public void DetachImmediate(List<NetEntity> entities)
public void DetachImmediate(List<EntityUid> entities)
{
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xforms = _entities.GetEntityQuery<TransformComponent>();
@@ -1006,7 +935,7 @@ namespace Robust.Client.GameStates
Detach(GameTick.MaxValue, null, entities, metas, xforms, xformSys, containerSys, lookupSys);
}
private List<NetEntity> ProcessPvsDeparture(
private List<EntityUid> ProcessPvsDeparture(
GameTick toTick,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms,
@@ -1015,7 +944,7 @@ namespace Robust.Client.GameStates
EntityLookupSystem lookupSys)
{
var toDetach = _processor.GetEntitiesToDetach(toTick, _pvsDetachBudget);
var detached = new List<NetEntity>();
var detached = new List<EntityUid>();
if (toDetach.Count == 0)
return detached;
@@ -1025,7 +954,6 @@ 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)
{
@@ -1038,17 +966,17 @@ namespace Robust.Client.GameStates
private void Detach(GameTick maxTick,
GameTick? lastStateApplied,
List<NetEntity> entities,
List<EntityUid> entities,
EntityQuery<MetaDataComponent> metas,
EntityQuery<TransformComponent> xforms,
SharedTransformSystem xformSys,
ContainerSystem containerSys,
EntityLookupSystem lookupSys,
List<NetEntity>? detached = null)
List<EntityUid>? detached = null)
{
foreach (var netEntity in entities)
foreach (var ent in entities)
{
if (!_entityManager.TryGetEntityData(netEntity, out var ent, out var meta))
if (!metas.TryGetComponent(ent, out var meta))
continue;
if (meta.LastStateApplied > maxTick)
@@ -1064,60 +992,57 @@ namespace Robust.Client.GameStates
if (lastStateApplied.HasValue)
meta.LastStateApplied = lastStateApplied.Value;
var xform = xforms.GetComponent(ent.Value);
var xform = xforms.GetComponent(ent);
if (xform.ParentUid.IsValid())
{
lookupSys.RemoveFromEntityTree(ent.Value, xform);
lookupSys.RemoveFromEntityTree(ent, xform);
xform.Broadphase = BroadphaseData.Invalid;
// In some cursed scenarios an entity inside of a container can leave PVS without the container itself leaving PVS.
// In those situations, we need to add the entity back to the list of expected entities after detaching.
BaseContainer? container = null;
IContainer? container = null;
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
containerSys.TryGetContainingContainer(xform.ParentUid, ent, out container, null, true))
{
container.Remove(ent.Value, _entities, xform, meta, false, true);
container.Remove(ent, _entities, xform, meta, false, true);
}
meta._flags |= MetaDataFlags.Detached;
xformSys.DetachParentToNull(ent.Value, xform);
xformSys.DetachParentToNull(ent, xform);
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
if (container != null)
containerSys.AddExpectedEntity(netEntity, container);
containerSys.AddExpectedEntity(ent, container);
}
detached?.Add(netEntity);
detached?.Add(ent);
}
}
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
private void InitializeAndStart(Dictionary<EntityUid, EntityState> toCreate)
{
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
#if EXCEPTION_TOLERANCE
var brokenEnts = new List<EntityUid>();
HashSet<EntityUid> brokenEnts = new HashSet<EntityUid>();
#endif
using (_prof.Group("Initialize Entity"))
{
foreach (var netEntity in toCreate.Keys)
foreach (var entity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
#endif
_entities.InitializeEntity(entity, metaQuery.GetComponent(entity));
_entities.InitializeEntity(entity);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}");
_sawmill.Error($"Server entity threw in Init: ent={_entityManager.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(netEntity);
toCreate.Remove(entity);
}
#endif
}
@@ -1125,9 +1050,8 @@ namespace Robust.Client.GameStates
using (_prof.Group("Start Entity"))
{
foreach (var netEntity in toCreate.Keys)
foreach (var entity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
@@ -1140,7 +1064,7 @@ namespace Robust.Client.GameStates
_sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(netEntity);
toCreate.Remove(entity);
}
#endif
}
@@ -1154,25 +1078,25 @@ namespace Robust.Client.GameStates
#endif
}
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
private void HandleEntityState(EntityUid uid, IEventBus bus, EntityState? curState,
EntityState? nextState, GameTick lastApplied, GameTick toTick, bool enteringPvs)
{
_compStateWork.Clear();
var size = (curState?.ComponentChanges.Span.Length ?? 0) + (nextState?.ComponentChanges.Span.Length ?? 0);
var compStateWork = new Dictionary<ushort, (IComponent Component, ComponentState? curState, ComponentState? nextState)>(size);
// First remove any deleted components
if (curState?.NetComponents != null)
{
_toRemove.Clear();
foreach (var (id, comp) in meta.NetComponents)
RemQueue<Component> toRemove = new();
foreach (var (id, comp) in _entities.GetNetComponents(uid))
{
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, meta);
_entities.RemoveComponent(uid, comp);
}
}
@@ -1183,32 +1107,34 @@ namespace Robust.Client.GameStates
//
// as to why we need to reset: because in the process of detaching to null-space, we will have dirtied
// the entity. most notably, all entities will have been ejected from their containers.
foreach (var (id, state) in _processor.GetLastServerStates(netEntity))
foreach (var (id, state) in _processor.GetLastServerStates(uid))
{
if (!meta.NetComponents.TryGetValue(id, out var comp))
if (!_entityManager.TryGetComponent(uid, id, out var comp))
{
comp = (Component) _compFactory.GetComponent(id);
comp.Owner = uid;
_entityManager.AddComponent(uid, comp, true, metadata: meta);
comp = _compFactory.GetComponent(id);
var newComp = (Component)comp;
newComp.Owner = uid;
_entityManager.AddComponent(uid, newComp, true);
}
_compStateWork[id] = (comp, state, null);
compStateWork[id] = (comp, state, null);
}
}
else if (curState != null)
{
foreach (var compChange in curState.ComponentChanges.Span)
{
if (!meta.NetComponents.TryGetValue(compChange.NetID, out var comp))
if (!_entityManager.TryGetComponent(uid, compChange.NetID, out var comp))
{
comp = (Component) _compFactory.GetComponent(compChange.NetID);
comp.Owner = uid;
_entityManager.AddComponent(uid, comp, true, metadata:meta);
comp = _compFactory.GetComponent(compChange.NetID);
var newComp = (Component)comp;
newComp.Owner = uid;
_entityManager.AddComponent(uid, newComp, true);
}
else if (compChange.LastModifiedTick <= lastApplied && lastApplied != GameTick.Zero)
continue;
_compStateWork[compChange.NetID] = (comp, compChange.State, null);
compStateWork[compChange.NetID] = (comp, compChange.State, null);
}
}
@@ -1219,53 +1145,21 @@ namespace Robust.Client.GameStates
if (compState.LastModifiedTick != toTick + 1)
continue;
if (!meta.NetComponents.TryGetValue(compState.NetID, out var comp))
if (!_entityManager.TryGetComponent(uid, 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;
}
ref var state =
ref CollectionsMarshal.GetValueRefOrAddDefault(_compStateWork, compState.NetID, out var exists);
if (exists)
state = (comp, state.curState, compState.State);
if (compStateWork.TryGetValue(compState.NetID, out var state))
compStateWork[compState.NetID] = (comp, state.curState, compState.State);
else
state = (comp, null, compState.State);
compStateWork[compState.NetID] = (comp, null, compState.State);
}
}
// If we have a NetEntity we reference come in then apply their state.
if (_pendingReapplyNetStates.TryGetValue(uid, out var reapplyTypes))
{
var lastState = _processor.GetLastServerStates(netEntity);
foreach (var type in reapplyTypes)
{
var compRef = _compFactory.GetRegistration(type);
var netId = compRef.NetID;
if (netId == null)
continue;
if (!meta.NetComponents.TryGetValue(netId.Value, out var comp) ||
!lastState.TryGetValue(netId.Value, out var lastCompState))
{
continue;
}
ref var compState =
ref CollectionsMarshal.GetValueRefOrAddDefault(_compStateWork, netId.Value, out var exists);
if (exists)
continue;
compState = (comp, lastCompState, null);
}
}
foreach (var (comp, cur, next) in _compStateWork.Values)
foreach (var (comp, cur, next) in compStateWork.Values)
{
try
{
@@ -1275,10 +1169,10 @@ namespace Robust.Client.GameStates
catch (Exception e)
{
#if EXCEPTION_TOLERANCE
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
_sawmill.Error($"Failed to apply comp state: entity={comp.Owner}, comp={comp.GetType()}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
#else
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
_sawmill.Error($"Failed to apply comp state: entity={uid}, comp={comp.GetType()}");
throw;
#endif
}
@@ -1286,7 +1180,6 @@ namespace Robust.Client.GameStates
}
#region Debug Commands
private bool TryParseUid(IConsoleShell shell, string[] args, out EntityUid uid, [NotNullWhen(true)] out MetaDataComponent? meta)
{
if (args.Length != 1)
@@ -1345,7 +1238,7 @@ namespace Robust.Client.GameStates
var xform = _entities.GetComponent<TransformComponent>(uid);
if (xform.ParentUid.IsValid())
{
BaseContainer? container = null;
IContainer? container = null;
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
_entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0)
@@ -1356,7 +1249,7 @@ namespace Robust.Client.GameStates
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
if (container != null)
containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container);
containerSys.AddExpectedEntity(uid, container);
}
}
@@ -1373,21 +1266,18 @@ namespace Robust.Client.GameStates
// If this is not a client-side entity, it also needs to be removed from the full-server state dictionary to
// avoid errors. This has to be done recursively for all children.
void _recursiveRemoveState(NetEntity netEntity, TransformComponent xform, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> xformQuery)
void _recursiveRemoveState(TransformComponent xform, EntityQuery<TransformComponent> query)
{
_processor._lastStateFullRep.Remove(netEntity);
_processor._lastStateFullRep.Remove(xform.Owner);
foreach (var child in xform.ChildEntities)
{
if (xformQuery.TryGetComponent(child, out var childXform) &&
metaQuery.TryGetComponent(child, out var childMeta))
{
_recursiveRemoveState(childMeta.NetEntity, childXform, metaQuery, xformQuery);
}
if (query.TryGetComponent(child, out var childXform))
_recursiveRemoveState(childXform, query);
}
}
if (!_entities.IsClientSide(uid) && _entities.TryGetComponent(uid, out TransformComponent? xform))
_recursiveRemoveState(meta.NetEntity, xform, _entities.GetEntityQuery<MetaDataComponent>(), _entities.GetEntityQuery<TransformComponent>());
if (!uid.IsClientSide() && _entities.TryGetComponent(uid, out TransformComponent? xform))
_recursiveRemoveState(xform, _entities.GetEntityQuery<TransformComponent>());
// Set ApplyingState to true to avoid logging errors about predicting the deletion of networked entities.
using (_timing.StartStateApplicationArea())
@@ -1421,16 +1311,17 @@ namespace Robust.Client.GameStates
meta.Flags &= ~MetaDataFlags.Detached;
if (!_processor.TryGetLastServerStates(meta.NetEntity, out var lastState))
if (!_processor.TryGetLastServerStates(uid, out var lastState))
return;
foreach (var (id, state) in lastState)
{
if (!meta.NetComponents.TryGetValue(id, out var comp))
if (!_entityManager.TryGetComponent(uid, id, out var comp))
{
comp = (Component) _compFactory.GetComponent(id);
comp.Owner = uid;
_entityManager.AddComponent(uid, comp, true, meta);
comp = _compFactory.GetComponent(id);
var newComp = (Component)comp;
newComp.Owner = uid;
_entityManager.AddComponent(uid, newComp, true);
}
var handleState = new ComponentHandleState(state, null);
@@ -1438,24 +1329,20 @@ namespace Robust.Client.GameStates
}
// ensure we don't have any extra components
_toRemove.Clear();
foreach (var (id, comp) in meta.NetComponents)
RemQueue<Component> toRemove = new();
foreach (var (id, comp) in _entities.GetNetComponents(uid))
{
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);
@@ -1465,9 +1352,9 @@ namespace Robust.Client.GameStates
public sealed class GameStateAppliedArgs : EventArgs
{
public GameState AppliedState { get; }
public readonly List<NetEntity> Detached;
public readonly List<EntityUid> Detached;
public GameStateAppliedArgs(GameState appliedState, List<NetEntity> detached)
public GameStateAppliedArgs(GameState appliedState, List<EntityUid> detached)
{
AppliedState = appliedState;
Detached = detached;
@@ -1476,12 +1363,12 @@ namespace Robust.Client.GameStates
public sealed class MissingMetadataException : Exception
{
public readonly NetEntity NetEntity;
public readonly EntityUid Uid;
public MissingMetadataException(NetEntity netEntity)
: base($"Server state is missing the metadata component for a new entity: {netEntity}.")
public MissingMetadataException(EntityUid uid)
: base($"Server state is missing the metadata component for a new entity: {uid}.")
{
NetEntity = netEntity;
Uid = uid;
}
}
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,17 @@
#nullable enable
using System.Numerics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Graphics
#nullable enable
namespace Robust.Client.Graphics
{
/// <inheritdoc />
[Virtual]
public class Eye : IEye
{
private Vector2 _scale = Vector2.One / 2f;
private Vector2 _scale = Vector2.One/2f;
private Angle _rotation = Angle.Zero;
private MapCoordinates _coords;

View File

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

View File

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

View File

@@ -1,9 +1,9 @@
using System.Numerics;
using System.Numerics;
using JetBrains.Annotations;
using Robust.Shared.Map;
using Robust.Shared.Maths;
namespace Robust.Shared.Graphics
namespace Robust.Client.Graphics
{
/// <summary>
/// An Eye is a point through which the player can view the world.

View File

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

View File

@@ -2,7 +2,6 @@ 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,7 +9,6 @@ using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;

View File

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

View File

@@ -16,10 +16,8 @@ using OGLTextureWrapMode = OpenToolkit.Graphics.OpenGL.TextureWrapMode;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
using Robust.Shared.Physics;
using Robust.Client.ComponentTrees;
using Robust.Shared.Graphics;
using static Robust.Shared.GameObjects.OccluderComponent;
using Robust.Shared.Utility;
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
@@ -338,12 +336,10 @@ 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.TryGetComponent<MapComponent>(mapUid, out var map) || !map.LightingEnabled)
if (!_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
{
return;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,7 +12,6 @@ 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;
@@ -22,7 +21,6 @@ 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,7 +8,6 @@ using Robust.Client.Audio;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,6 @@
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;
@@ -27,7 +25,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, RsiDirectionType rsiDirection, float[] delays, Texture[][] icons)
internal State(Vector2i size, RSI rsi, StateId stateId, DirectionType direction, float[] delays, Texture[][] icons)
{
DebugTools.Assert(size.X > 0);
DebugTools.Assert(size.Y > 0);
@@ -36,7 +34,7 @@ namespace Robust.Client.Graphics
Size = size;
RSI = rsi;
StateId = stateId;
RsiDirections = rsiDirection;
Directions = direction;
Delays = delays;
TotalDelay = delays.Sum();
Icons = icons;
@@ -65,7 +63,7 @@ namespace Robust.Client.Graphics
/// <summary>
/// How many directions this state has.
/// </summary>
public RsiDirectionType RsiDirections { get; }
public DirectionType Directions { get; }
/// <summary>
/// The first frame of the "south" direction.
@@ -92,14 +90,14 @@ namespace Robust.Client.Graphics
int IRsiStateLike.AnimationFrameCount => DelayCount;
public Texture GetFrame(RsiDirection rsiDirection, int frame)
public Texture GetFrame(Direction direction, int frame)
{
return Icons[(int) rsiDirection][frame];
return Icons[(int) direction][frame];
}
public Texture[] GetFrames(RsiDirection rsiDirection)
public Texture[] GetFrames(Direction direction)
{
return Icons[(int) rsiDirection];
return Icons[(int) direction];
}
/// <summary>
@@ -126,12 +124,52 @@ namespace Robust.Client.Graphics
Texture IDirectionalTextureProvider.TextureFor(Shared.Maths.Direction dir)
{
if (RsiDirections == RsiDirectionType.Dir1)
if (Directions == DirectionType.Dir1)
{
return Frame0;
}
return GetFrame(dir.Convert(RsiDirections), 0);
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,
}
}
}

View File

@@ -1,6 +1,5 @@
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,112 +1,233 @@
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;
/// <summary>
/// Contains a texture used for drawing things.
/// </summary>
[PublicAPI]
public abstract class Texture : IRsiStateLike
namespace Robust.Client.Graphics
{
/// <summary>
/// The width of the texture, in pixels.
/// Contains a texture used for drawing things.
/// </summary>
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)
[PublicAPI]
public abstract class Texture : IRsiStateLike
{
Size = size;
}
/// <summary>
/// The width of the texture, in pixels.
/// </summary>
public int Width => Size.X;
Texture IDirectionalTextureProvider.Default => this;
/// <summary>
/// The height of the texture, in pixels.
/// </summary>
public int Height => Size.Y;
Texture IDirectionalTextureProvider.TextureFor(Direction dir)
{
return this;
}
/// <summary>
/// The size of the texture, in pixels.
/// </summary>
public Vector2i Size { get; /*protected set;*/ }
RsiDirectionType IRsiStateLike.RsiDirections => RsiDirectionType.Dir1;
bool IRsiStateLike.IsAnimated => false;
int IRsiStateLike.AnimationFrameCount => 0;
public Color this[int x, int y] => this.GetPixel(x, y);
float IRsiStateLike.GetDelay(int frame)
{
if (frame != 0)
throw new IndexOutOfRangeException();
protected Texture(Vector2i size)
{
Size = size;
}
return 0;
}
public static Texture Transparent =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.Transparent);
Texture IRsiStateLike.GetFrame(RsiDirection dir, int frame)
{
if (frame != 0)
throw new IndexOutOfRangeException();
public static Texture White =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.White);
return this;
}
public static Texture Black =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.Black);
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="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 static Texture Transparent =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.Transparent);
/// <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 White =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.White);
Texture IDirectionalTextureProvider.Default => this;
public static Texture Black =>
IoCManager.Resolve<IClydeInternal>().GetStockTexture(ClydeStockTexture.Black);
Texture IDirectionalTextureProvider.TextureFor(Direction dir)
{
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="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);
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 an image from a stream containing PNG data.
/// Flags for loading of textures.
/// </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="Robust.Client.Graphics.TextureLoadParameters.Default"/> if <c>null</c>.
/// </param>
public static Texture LoadFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParameters = null)
[PublicAPI]
public struct TextureLoadParameters
{
var manager = IoCManager.Resolve<IClyde>();
return manager.LoadTextureFromPNGStream(stream, name, loadParameters);
/// <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,
}
}

View File

@@ -8,7 +8,6 @@ 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,6 +1,5 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Maths;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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