Compare commits

...

148 Commits

Author SHA1 Message Date
Silver
9216ff9e4b Fix Dark eldritch issue with alertorderserializer 2022-03-17 14:13:26 -06:00
Silver
f1857f3c53 Adds AlertOrderSerializer 2022-03-14 16:03:08 -06:00
Silver
04b7181a78 Adds EnumReferenceSerializer 2022-03-03 12:29:37 -07:00
metalgearsloth
fa1de04df7 Version: 0.8.67 2022-03-03 21:18:00 +11:00
metalgearsloth
33251222cd EntityLookup as a system (#2573) 2022-03-03 21:17:01 +11:00
Kara D
70c1e8680f Version: 0.8.66 2022-03-01 15:06:19 -07:00
Kara D
0d214769e6 vs2022 fix 2022-03-01 15:05:49 -07:00
Paul
495bbe891a obsoletes serhooks 2022-03-01 21:49:24 +01:00
Rember
5f68f569c3 BaseContainer.Insert() will now reset the entity's rotation after res… (#2576) 2022-03-01 23:23:56 +11:00
Leon Friedrich
52c883cb1f Fix lsmap and lsgrid (#2574) 2022-03-01 20:20:03 +11:00
metalgearsloth
4b20bae597 RobustUnitTest tweaks (#2568) 2022-03-01 13:35:32 +11:00
Leon Friedrich
64ff046ceb loadmap command tweak (#2572)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2022-02-28 21:17:58 +11:00
Kara D
5f2566a97d Version: 0.8.65 2022-02-27 12:59:54 -07:00
Kara D
c3735d1d5e vs2022 moment 2022-02-27 12:59:25 -07:00
metalgearsloth
749e547773 Version: 0.8.64 2022-02-28 00:45:53 +11:00
metalgearsloth
af8a010f43 Optimise PVS cangetcompstate (#2560) 2022-02-28 00:45:16 +11:00
metalgearsloth
a8a29e814f Allow content to override occlusion directions (#2528) 2022-02-27 23:15:45 +11:00
metalgearsloth
c3cb5406f6 Fix culling disabled (#2485)
Co-authored-by: metalgearsloth <metalgearsloth@gmail.com>
2022-02-27 22:59:36 +11:00
mirrorcult
73f26d93ca Merge pull request #2565 from ElectroJr/mapping-actions 2022-02-26 20:06:48 -07:00
metalgearsloth
5e86a99060 Version: 0.8.63 2022-02-27 13:00:41 +11:00
Paul Ritter
ce9070b966 changes pvs chunkorder to use a tree-structure, should fix children being sent without parents (#2562)
Co-authored-by: Paul Ritter <ritter.paul1@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-02-27 13:00:10 +11:00
Vera Aguilera Puerto
aa5bcefaf2 Add System.Threading.Monitor (lock keyword) to sandbox whitelist. 2022-02-26 16:50:41 +01:00
ElectroJr
2df267fc28 Merge remote-tracking branch 'upstream/master' into mapping-actions 2022-02-24 04:13:45 +13:00
ElectroJr
306fcce337 remove todo 2022-02-24 04:05:04 +13:00
ElectroJr
3683b66ef8 Allow SpriteSpecifier to load from entity prototype 2022-02-23 23:37:05 +13:00
Acruid
08a52fb892 MapChunk Cleanup (#2555)
* All IPhysShapes now expose a property to get the local AABB.

* Removed IMapChunk. It's internal, we only have 1 implementation in the engine, no need for abstraction, and removing it helps perf.

* Cleaned up issues in MapChunk file.

* Encapsulate _tiles access inside MapChunk.

* Remove IEnumerable<TileRef> from MapChunk.

* Remove CollidesWithChunk

* Move CalcWorldAABB and RegenerateCollision from MapChunk to MapGrid.
Remove MapChunk.GridId.

* Removed MapChunk.GetAllTiles

* Removed the terrible mocked unit tests.

* Moved the GetTileRef functions from MapChunk to MapGrid.

* Add an event raised on MapChunk when a tile is modified.
Completely remove the IMapGrid dependency from MapChunk.

* Fix bug where you cannot change the tile damage of a tile.
2022-02-21 20:49:30 -08:00
Pieter-Jan Briers
fd3c54b373 Seal some classes. 2022-02-21 14:02:34 +01:00
Pieter-Jan Briers
adee05b009 Make client start with non-seekable streams.
Not marking #2439 as fixed yet due to lack of thorough testing.
2022-02-21 11:23:37 +01:00
Pieter-Jan Briers
a66d40eb19 CVar to force ResourceManager to provide seekability for streams.
To help with #2439
2022-02-21 11:23:37 +01:00
metalgearsloth
d065a96e01 Don't serialize grid fixtures (#2477) 2022-02-21 20:15:30 +11:00
Kara D
ef7709aa78 Version: 0.8.62 2022-02-20 19:34:25 -07:00
Paul Ritter
305b330075 bandaid 2022-02-21 02:30:03 +01:00
Acruid
bec4297ce1 Move Map Pause data from MapManager to MapComponent. (#2543)
* Encapsulated existing _pausedMaps access in MapManager.Pause.
Added more unit tests.

* Moved the MapPaused flag from MapManager.Pause to MapComponent.

* Moved the MapPreInit flag from MapManager.Pause to MapComponent.

* Changed visibility so content can't access the flags directly.

* It's not the code that is wrong, it's the tests that are wrong!

* Removed completely obsolete bookkeeping event.
2022-02-20 13:36:45 -08:00
metalgearsloth
18b21b3d60 Version: 0.8.61 2022-02-20 17:44:49 +11:00
mirrorcult
9b263417b9 Merge pull request #2541 from metalgearsloth/2022-02-14_1 2022-02-19 12:33:46 -07:00
Acruid
13da0a7925 Adds command buffer to client, and input command injection (#2538)
Adds the command buffer to the console.
adds wait <ticks> command to console.
Adds the `incmd` command to inject input commands directly into the simulation, bypassing the frontend.
Removes client side permission check for debug builds.
2022-02-18 19:27:38 -08:00
Kara D
bb28db2412 Version: 0.8.60 2022-02-18 15:50:15 -07:00
mirrorcult
489d150ae0 Merge pull request #2558 from PaulRitter/2022_02_18_real_pvs_hours 2022-02-18 15:49:20 -07:00
Paul Ritter
077dbaf933 yo this slaps 2022-02-18 20:49:03 +01:00
Paul Ritter
26f83ac7a2 bored trainride 2022-02-17 15:08:55 +01:00
metalgearsloth
822009b429 Version: 0.8.59 2022-02-17 18:05:50 +11:00
Leon Friedrich
e07a4e516c Add random vector functions to IRobustRandom (#2551) 2022-02-17 18:05:12 +11:00
metalgearsloth
9ec38b5538 Optimise PVS recursion (#2524) 2022-02-17 18:03:14 +11:00
metalgearsloth
97da770978 More physics ECS (#2529) 2022-02-17 17:52:27 +11:00
metalgearsloth
3acbc8235d Funny struct enumerator for xform kids Part 2 (#2492) 2022-02-17 17:19:08 +11:00
Sam Weaver
a32359d5d4 Only include shadow casting lights in the "max lights per scene" count (#2446)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2022-02-17 14:24:36 +11:00
Paul
78fd39aee2 could fix a racecondition? maybe? 2022-02-16 23:55:04 +01:00
Kara D
fca5c14c67 Version: 0.8.58 2022-02-16 15:53:10 -07:00
Kara D
f60e3a14ef Fix VS2022 17.1 issue 2022-02-16 15:49:19 -07:00
Leon Friedrich
503a7032f9 Hopefully fix appearance component mispredict reconciliation (#2460) 2022-02-16 23:26:13 +01:00
metalgearsloth
918bbd3f01 Version: 0.8.57 2022-02-17 01:06:34 +11:00
Leon Friedrich
a1441d5051 Properly support sprite layer transforms. (#2533) 2022-02-17 00:06:14 +11:00
Leon Friedrich
58d6189c40 Allow sprite layer data to be set using PrototypeLayerData. (#2442) 2022-02-16 19:37:40 +11:00
Leon Friedrich
58defed1d2 Fix InteractionOutline 2: Electric boogaloo (#2511)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-02-16 18:32:40 +11:00
Paul
27a94384d0 v0.8.56 2022-02-15 18:16:07 +01:00
Paul Ritter
4b8f5815db pvs caching (#2547)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-02-15 18:14:44 +01:00
metalgearsloth
d830eef435 Version: 0.8.55 2022-02-15 21:16:35 +11:00
metalgearsloth
e7c4bf7341 Don't use worldbounds for PVS (#2473) 2022-02-15 21:07:41 +11:00
ike709
162e646404 Fix a weird issue with config (#2546)
Co-authored-by: ike709 <ike709@github.com>
2022-02-15 20:57:32 +11:00
metalgearsloth
93049fcacf More transform optimisations (#2519) 2022-02-15 20:43:49 +11:00
metalgearsloth
5b90db4beb inline 2022-02-14 18:30:33 +11:00
metalgearsloth
7692ff736b Some more ECS stuff
- Proxy methods on EntityManager
- Transform system stuff
2022-02-14 18:24:52 +11:00
Kara D
f25ad8ece9 Version: 0.8.54 2022-02-13 20:25:00 -07:00
mirrorcult
1aaf3b9250 Merge pull request #2521 from ElectroJr/public-AppearanceSystem 2022-02-13 20:24:23 -07:00
mirrorcult
11f6cff6df Merge pull request #2539 from Acruid/vv_fix_entName 2022-02-13 20:01:53 -07:00
mirrorcult
c2ea57c95a Merge pull request #2503 from Ygg01/linguini-multiline-error-fix 2022-02-13 20:00:47 -07:00
Ygg01
f61ae5da6b Update Linguini and line info in errors 2022-02-14 01:50:11 +01:00
Acruid
8ae9a5f2da Fix a typo where setting the description of an entity in VV sets the name. 2022-02-13 16:35:51 -08:00
Ygg01
3f7e89e006 Merge remote-tracking branch 'origin/master' into linguini-multiline-error-fix 2022-02-13 23:35:28 +01:00
Ygg01
cee4e4d62e Enable Fluent logs for only .ftl files (#2532) 2022-02-12 21:59:08 +11:00
metalgearsloth
14a3783760 Version: 0.8.53 2022-02-12 15:56:21 +11:00
mirrorcult
b4607f7b1f Draw effects below FOV (#2534) 2022-02-12 15:54:50 +11:00
Acruid
5a28c16cae Map Pausing Fixes (#2520) 2022-02-12 15:54:03 +11:00
ElectroJr
9e8bf861ea Merge remote-tracking branch 'upstream/master' into public-AppearanceSystem 2022-02-11 20:43:19 +13:00
ElectroJr
b540f04a7a Version: 0.8.52 2022-02-10 21:46:08 +13:00
Leon Friedrich
bcaa7001ad Hopefully fix container occlusion bug? (#2464) 2022-02-10 09:29:26 +01:00
metalgearsloth
3f0fba7b4e Version: 0.8.51 2022-02-10 12:57:00 +11:00
metalgearsloth
d7d7a53045 Raise appearance event by ref 2022-02-10 12:56:14 +11:00
metalgearsloth
40daba9adf Version: 0.8.50 2022-02-10 12:53:26 +11:00
mirrorcult
5665f8eb1c Abstract VisualizerSystem (#2522) 2022-02-10 12:52:42 +11:00
metalgearsloth
8f0d562f3e Version: 0.8.49 2022-02-10 11:21:29 +11:00
ike709
f3950e940e Re-adds UpdateEntityTree() to EntityLookup's interface (#2525) 2022-02-10 11:15:09 +11:00
Leon Friedrich
3fdca65cc9 Make occluder use try get map (#2516) 2022-02-10 01:40:11 +11:00
ElectroJr
4cfb9210d0 Make AppearanceSystem public 2022-02-09 04:55:31 +13:00
ike709
23d0b8a555 Expose LastTick on IGameTiming (#2518) 2022-02-08 14:02:36 +11:00
mirrorcult
452b03d5a6 Merge pull request #2513 from metalgearsloth/2022-02-07_EnsureCompOut 2022-02-07 13:41:59 -07:00
metalgearsloth
c8f2dab381 Don't crash on VV edit for angles (#2517) 2022-02-08 02:58:55 +11:00
metalgearsloth
7409df07f8 Merge remote-tracking branch 'upstream/master' into 2022-02-07_EnsureCompOut 2022-02-07 19:55:45 +11:00
metalgearsloth
973406b91d Cleanup contact code a lot (#2480) 2022-02-07 13:10:58 +11:00
metalgearsloth
2e646fe2b6 EntityQueryEnumerator "fix" (#2501) 2022-02-07 12:06:50 +11:00
mirrorcult
6b902d22d4 Merge pull request #2504 from metalgearsloth/2022-02-03_appearance-events
Appearance data event
2022-02-06 11:23:01 -07:00
metalgearsloth
83e6d52e58 Version: 0.8.47 2022-02-07 02:56:24 +11:00
metalgearsloth
025fdcd0b3 GetHardAABB (#2514) 2022-02-07 02:10:02 +11:00
metalgearsloth
fe3ace92bd Another helper 2022-02-07 02:04:57 +11:00
metalgearsloth
317070f167 Some animation helpers for cargo telepad too 2022-02-07 02:00:31 +11:00
metalgearsloth
3524363ad4 fix build 2022-02-07 01:44:27 +11:00
metalgearsloth
90287dbb09 Merge remote-tracking branch 'upstream/master' into 2022-02-03_appearance-events
# Conflicts:
#	Robust.Shared/GameObjects/Components/Appearance/AppearanceComponent.cs
2022-02-07 01:42:53 +11:00
metalgearsloth
9a2f35f2d3 Also pass comp 2022-02-07 01:42:32 +11:00
metalgearsloth
3f10dbe770 EnsureComponent<T> with out variable
Useful for EnsureComponent<T>(uid, out var comp) && comp.Thing
2022-02-07 00:07:01 +11:00
metalgearsloth
8fcba93ada Don't draw 0-mass non-hard fixtures as invalid (#2499) 2022-02-06 19:42:47 +11:00
ElectroJr
35366b5bc1 Version: 0.8.46 2022-02-06 15:18:56 +13:00
mirrorcult
d8bf31ecc4 Merge pull request #2510 from ElectroJr/fix-sprite-bug 2022-02-05 15:23:25 -07:00
Pieter-Jan Briers
de4d255841 Analyzer to enforce classes to be either [Virtual], abstract, or sealed. (#2469)
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2022-02-05 19:31:58 +01:00
Leon Friedrich
ec768c563f Add spin command (#2484) 2022-02-05 12:38:02 +11:00
metalgearsloth
2114d2bc67 Funny struct enumerator for xform kids (#2487) 2022-02-05 12:32:13 +11:00
ElectroJr
43478d0e45 avoid unnecessary RoundToCardinal calls 2022-02-05 14:27:31 +13:00
Leon Friedrich
561699b26e Replace EntityDirtyEvent with C# event. (#2495) 2022-02-05 12:23:51 +11:00
ElectroJr
460923e54d fix sprite bug 2022-02-05 14:10:28 +13:00
Kara D
c299104f0c Version: 0.8.45 2022-02-04 14:35:35 -07:00
Kara D
224bebeeb5 ok then 2022-02-04 14:35:15 -07:00
Kara D
52c8e8ecdf Version: 0.8.44 2022-02-04 14:20:48 -07:00
mirrorcult
ac636b2703 Merge pull request #2441 from metalgearsloth/2022-01-20_more-caching 2022-02-04 14:13:26 -07:00
mirrorcult
a892f9ac99 Merge pull request #2506 from metalgearsloth/2022-02-04_xform-children 2022-02-04 14:13:18 -07:00
mirrorcult
2ebee8f40c Merge pull request #2508 from metalgearsloth/2022-02-04_powa 2022-02-04 14:13:10 -07:00
mirrorcult
43741dc5fc Merge pull request #2490 from metalgearsloth/2022-02-01_game-state 2022-02-04 14:12:56 -07:00
metalgearsloth
da9467b678 Reduce FindGridsIntersecting allocs 2022-02-04 17:44:44 +11:00
Leon Friedrich
8f24a7b4fc Reduce ContainerHelper IoCManager resolves (#2498) 2022-02-04 12:51:29 +11:00
metalgearsloth
ff48959e97 Merge remote-tracking branch 'upstream/master' into 2022-01-20_more-caching 2022-02-04 12:46:01 +11:00
metalgearsloth
09e648d086 Version: 0.8.43 2022-02-04 12:43:43 +11:00
metalgearsloth
1d1db2e109 Also that sneaky pvs one 2022-02-04 12:40:40 +11:00
metalgearsloth
6a902286cd Merge remote-tracking branch 'upstream/master' into 2022-01-20_more-caching 2022-02-04 12:38:32 +11:00
metalgearsloth
933724e4ee Nuke CompGetStateAttempt allocs (#2497) 2022-02-04 12:35:57 +11:00
metalgearsloth
53034e6f05 Replace xform children sortedset with hashset 2022-02-04 12:27:51 +11:00
Ygg01
681f77a796 Change newline append 2022-02-04 02:01:52 +01:00
metalgearsloth
6a31c5649c Tweak 2022-02-03 18:03:56 +11:00
metalgearsloth
7bd90578dc Appearance data event
We agreed that AppearanceVisualizers should probably be deprecated in favour of just using a system instead.
2022-02-03 18:01:31 +11:00
Ygg01
2a7f1cbf48 Ensure that tests work regardless of platform 2022-02-03 04:06:53 +01:00
metalgearsloth
c076be95a6 Version: 0.8.42 2022-02-03 13:26:13 +11:00
metalgearsloth
7b732dd68a Remove unnecessary resolves from MapLoader (#2462) 2022-02-03 13:25:22 +11:00
metalgearsloth
3e34b1414e The end of IMapInit (#2479) 2022-02-03 13:15:17 +11:00
Acruid
8456cd90d1 Move MapGrid data from MapManager to MapGridComponent (#2430) 2022-02-03 13:15:06 +11:00
Ygg01
c4e63cfdc7 Fix multiline errors, add some tests 2022-02-03 01:43:45 +01:00
Pieter-Jan Briers
d716e1ff62 Fix EstPixelSize for Rg32f.
This function literally isn't used but that won't stop me from making this commit.
2022-02-02 21:46:42 +01:00
metalgearsloth
8e9acc9191 Reduce broadphase allocs a l'il bit (#2493) 2022-02-03 00:52:04 +11:00
metalgearsloth
773a068b7d Merge remote-tracking branch 'upstream/master' into 2022-02-01_game-state 2022-02-02 22:13:17 +11:00
metalgearsloth
b37bfff155 Remove string allocs from client prediction (#2494) 2022-02-02 21:59:40 +11:00
Leon Friedrich
a14cce222b Misc physics/shutdown related optimizations (#2496) 2022-02-02 18:12:53 +11:00
Leon Friedrich
c077e09436 Add explicit transform matrices (#2483) 2022-02-02 10:03:45 +11:00
Leon Friedrich
213955566b remove GetBus() (#2491) 2022-02-02 10:03:03 +11:00
metalgearsloth
b98cd3e6e1 Reduce batch for lowerpop I guess 2022-02-01 23:11:35 +11:00
metalgearsloth
987c8e8229 Reduce gamestate allocs
Parallel.ForEach has no right to be that expensive.
2022-02-01 23:11:05 +11:00
metalgearsloth
fcbb97ec55 Fix edge collisions (#2482) 2022-02-01 15:28:12 +11:00
metalgearsloth
1f6170c000 Merge remote-tracking branch 'upstream/master' into 2022-01-20_more-caching 2022-01-31 18:21:23 +11:00
metalgearsloth
e438e53d36 Silly caching 2022-01-31 17:50:30 +11:00
metalgearsloth
792608f0d0 Merge remote-tracking branch 'upstream/master' into 2022-01-20_more-caching 2022-01-31 17:42:09 +11:00
metalgearsloth
7b5678fd0f woops 2022-01-31 17:34:49 +11:00
metalgearsloth
88c328a636 Smol entityquery change 2022-01-31 17:33:21 +11:00
metalgearsloth
f9573530a8 Nuke entitylookup to get it merged 2022-01-31 17:30:53 +11:00
718 changed files with 9064 additions and 6804 deletions

View File

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

View File

@@ -1,10 +1,14 @@
using Microsoft.CodeAnalysis;
namespace Robust.Generators
namespace Robust.Analyzers;
public static class Diagnostics
{
public static class Diagnostics
{
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
}
public const string IdExplicitInterface = "RA0000";
public const string IdSerializable = "RA0001";
public const string IdFriend = "RA0002";
public const string IdExplicitVirtual = "RA0003";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
}

View File

@@ -24,14 +24,14 @@ namespace Robust.Analyzers
SyntaxKind.OverrideKeyword
};
public const string DiagnosticId = "RA0000";
private const string Title = "No explicit interface specified";
private const string MessageFormat = "No explicit interface specified";
private const string Description = "Make sure to specify the interface in your method-declaration.";
private const string Category = "Usage";
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdExplicitInterface,
"No explicit interface specified",
"No explicit interface specified",
"Usage",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Make sure to specify the interface in your method-declaration.");
private const string RequiresExplicitImplementationAttributeMetadataName =
"Robust.Shared.Analyzers.RequiresExplicitImplementationAttribute";

View File

@@ -0,0 +1,176 @@
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ExplicitVirtualAnalyzer : DiagnosticAnalyzer
{
internal const string Attribute = "Robust.Shared.Analyzers.VirtualAttribute";
[SuppressMessage("ReSharper", "RS2008")]
private static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdExplicitVirtual,
"Class must be explicitly marked as [Virtual], abstract, static or sealed",
"Class must be explicitly marked as [Virtual], abstract, static or sealed",
"Usage",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "Class must be explicitly marked as [Virtual], abstract, static or sealed.");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.ClassDeclaration);
}
private static bool HasAttribute(INamedTypeSymbol namedTypeSymbol, INamedTypeSymbol attrSymbol)
{
return namedTypeSymbol.GetAttributes()
.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
}
private static void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
var attrSymbol = context.Compilation.GetTypeByMetadataName(Attribute);
var classDecl = (ClassDeclarationSyntax)context.Node;
var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDecl);
if (classSymbol == null)
return;
if (classSymbol.IsSealed || classSymbol.IsAbstract || classSymbol.IsStatic)
return;
if (HasAttribute(classSymbol, attrSymbol))
return;
var diag = Diagnostic.Create(Rule, classDecl.Keyword.GetLocation());
context.ReportDiagnostic(diag);
}
}
// Doesn't work as I'd hoped: Roslyn doesn't provide an API for global usings and I can't get batch changes to work.
/*
[ExportCodeFixProvider(LanguageNames.CSharp)]
public sealed class ExplicitVirtualCodeFixProvider : CodeFixProvider
{
private const string TitleSealed = "Annotate class as sealed.";
private const string TitleVirtual = "Annotate class as [Virtual].";
private const string TitleAbstract = "Annotate class as abstract.";
private const string TitleStatic = "Annotate class as static.";
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
foreach (var diagnostic in context.Diagnostics)
{
var span = diagnostic.Location.SourceSpan;
var classDecl = root.FindToken(span.Start).Parent.AncestorsAndSelf().OfType<ClassDeclarationSyntax>()
.First();
context.RegisterCodeFix(
CodeAction.Create(
TitleVirtual,
c => FixVirtualAsync(context.Document, classDecl, c),
TitleVirtual),
diagnostic);
context.RegisterCodeFix(
CodeAction.Create(
TitleStatic,
c => FixStaticAsync(context.Document, classDecl, c),
TitleStatic),
diagnostic);
context.RegisterCodeFix(
CodeAction.Create(
TitleSealed,
c => FixSealedAsync(context.Document, classDecl, c),
TitleSealed),
diagnostic);
context.RegisterCodeFix(
CodeAction.Create(
TitleAbstract,
c => FixAbstractAsync(context.Document, classDecl, c),
TitleAbstract),
diagnostic);
}
}
private async Task<Document> FixVirtualAsync(
Document document,
ClassDeclarationSyntax classDecl,
CancellationToken cancellationToken)
{
var ns = "Robust.Shared.Analyzers";
var attrib = SyntaxFactory.Attribute(SyntaxFactory.ParseName("Virtual"));
var newClassDecl = classDecl.AddAttributeLists(
SyntaxFactory.AttributeList(SyntaxFactory.SeparatedList(new[] { attrib })));
var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken);
root = root.ReplaceNode(classDecl, newClassDecl);
var options = await document.GetOptionsAsync(cancellationToken);
if (root.Usings.All(u => u.Name.ToString() != ns))
{
root = root.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)));
}
return document.WithSyntaxRoot(root);
}
private async Task<Document> FixStaticAsync(
Document document,
ClassDeclarationSyntax classDecl,
CancellationToken cancellationToken)
{
var newClassDecl = classDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword));
var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken);
root = root.ReplaceNode(classDecl, newClassDecl);
return document.WithSyntaxRoot(root);
}
private async Task<Document> FixAbstractAsync(
Document document,
ClassDeclarationSyntax classDecl,
CancellationToken cancellationToken)
{
var newClassDecl = classDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.AbstractKeyword));
var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken);
root = root.ReplaceNode(classDecl, newClassDecl);
return document.WithSyntaxRoot(root);
}
private async Task<Document> FixSealedAsync(
Document document,
ClassDeclarationSyntax classDecl,
CancellationToken cancellationToken)
{
var newClassDecl = classDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.SealedKeyword));
var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(cancellationToken);
root = root.ReplaceNode(classDecl, newClassDecl);
return document.WithSyntaxRoot(root);
}
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(Diagnostics.IdExplicitVirtual);
}
*/

View File

@@ -14,15 +14,15 @@ namespace Robust.Analyzers
{
const string FriendAttribute = "Robust.Shared.Analyzers.FriendAttribute";
public const string DiagnosticId = "RA0002";
private const string Title = "Tried to access friend-only member";
private const string MessageFormat = "Tried to access member \"{0}\" in class \"{1}\" which can only be accessed by friend classes";
private const string Description = "Make sure to specify the accessing class in the friends attribute.";
private const string Category = "Usage";
[SuppressMessage("ReSharper", "RS2008")]
private static readonly DiagnosticDescriptor Rule = new (DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Error, true, Description);
private static readonly DiagnosticDescriptor Rule = new (
Diagnostics.IdFriend,
"Tried to access friend-only member",
"Tried to access member \"{0}\" in class \"{1}\" which can only be accessed by friend classes",
"Usage",
DiagnosticSeverity.Error,
true,
"Make sure to specify the accessing class in the friends attribute.");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

View File

@@ -1,9 +1,7 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Generators;
namespace Robust.Analyzers
{

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9</LangVersion>
<LangVersion>10</LangVersion>
</PropertyGroup>
<ItemGroup>

View File

@@ -17,19 +17,21 @@ namespace Robust.Analyzers
public class SerializableAnalyzer : DiagnosticAnalyzer
{
// Metadata of the analyzer
public const string DiagnosticId = "RA0001";
// You could use LocalizedString but it's a little more complicated for this sample
private const string Title = "Class not marked as (Net)Serializable";
private const string MessageFormat = "Class not marked as (Net)Serializable";
private const string Description = "The class should be marked as (Net)Serializable.";
private const string Category = "Usage";
private const string RequiresSerializableAttributeMetadataName = "Robust.Shared.Analyzers.RequiresSerializableAttribute";
private const string SerializableAttributeMetadataName = "System.SerializableAttribute";
private const string NetSerializableAttributeMetadataName = "Robust.Shared.Serialization.NetSerializableAttribute";
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description);
[SuppressMessage("ReSharper", "RS2008")] private static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdSerializable,
"Class not marked as (Net)Serializable",
"Class not marked as (Net)Serializable",
"Usage",
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "The class should be marked as (Net)Serializable.");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
@@ -139,7 +141,8 @@ namespace Robust.Analyzers
return document.WithSyntaxRoot(root);
}
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(SerializableAnalyzer.DiagnosticId);
public sealed override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(Diagnostics.IdSerializable);
public override FixAllProvider GetFixAllProvider()
{

View File

@@ -1,7 +1,9 @@
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
namespace Robust.Benchmarks.NumericsHelpers
{
[Virtual]
public class AddBenchmark
{
[Params(32, 128)]

View File

@@ -4,7 +4,7 @@ using System;
namespace Robust.Benchmarks
{
internal class Program
internal static class Program
{
// --allCategories=ctg1,ctg2
// --anyCategories=ctg1,ctg2

View File

@@ -9,7 +9,7 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Benchmarks.Serialization
{
public class BenchmarkIntSerializer : ITypeSerializer<int, ValueDataNode>
public sealed class BenchmarkIntSerializer : ITypeSerializer<int, ValueDataNode>
{
public ValidationNode Validate(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies, ISerializationContext? context = null)

View File

@@ -2,6 +2,7 @@
using System.Linq;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
@@ -13,6 +14,7 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Copy
{
[MemoryDiagnoser]
[Virtual]
public class SerializationCopyBenchmark : SerializationBenchmark
{
public SerializationCopyBenchmark()

View File

@@ -3,7 +3,7 @@ using Robust.Shared.Serialization;
namespace Robust.Benchmarks.Serialization.Definitions
{
public class BenchmarkFlags
public sealed class BenchmarkFlags
{
public const int Zero = 1 << 0;
public const int ThirtyOne = 1 << 31;

View File

@@ -1,8 +1,10 @@
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Benchmarks.Serialization.Definitions
{
[DataDefinition]
[Virtual]
public class DataDefinitionWithString
{
[DataField("string")]

View File

@@ -11,7 +11,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
/// Taken from content.
/// </summary>
[Prototype("seed")]
public class SeedDataDefinition : IPrototype
public sealed class SeedDataDefinition : IPrototype
{
public const string Prototype = @"
- type: seed

View File

@@ -1,9 +1,11 @@
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager;
namespace Robust.Benchmarks.Serialization.Initialize
{
[MemoryDiagnoser]
[Virtual]
public class SerializationInitializeBenchmark : SerializationBenchmark
{
[IterationCleanup]

View File

@@ -1,6 +1,7 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
@@ -12,6 +13,7 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Read
{
[MemoryDiagnoser]
[Virtual]
public class SerializationReadBenchmark : SerializationBenchmark
{
public SerializationReadBenchmark()

View File

@@ -1,5 +1,6 @@
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Markdown.Mapping;
using Robust.Shared.Serialization.Markdown.Sequence;
using Robust.Shared.Serialization.Markdown.Value;
@@ -7,6 +8,7 @@ using Robust.Shared.Serialization.Markdown.Value;
namespace Robust.Benchmarks.Serialization
{
[MemoryDiagnoser]
[Virtual]
public class SerializationArrayBenchmark : SerializationBenchmark
{
public SerializationArrayBenchmark()

View File

@@ -2,6 +2,7 @@
using System.IO;
using BenchmarkDotNet.Attributes;
using Robust.Benchmarks.Serialization.Definitions;
using Robust.Shared.Analyzers;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Mapping;
@@ -13,6 +14,7 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Benchmarks.Serialization.Write
{
[MemoryDiagnoser]
[Virtual]
public class SerializationWriteBenchmark : SerializationBenchmark
{
public SerializationWriteBenchmark()

View File

@@ -3,7 +3,7 @@ using Robust.Shared.Animations;
namespace Robust.Client.Animations
{
public class AnimationTrackControlProperty : AnimationTrackProperty
public sealed class AnimationTrackControlProperty : AnimationTrackProperty
{
public string? Property { get; set; }

View File

@@ -58,7 +58,7 @@ namespace Robust.Client.Audio.Midi
void Shutdown();
}
internal class MidiManager : IMidiManager
internal sealed class MidiManager : IMidiManager
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
@@ -392,7 +392,7 @@ namespace Robust.Client.Audio.Midi
/// <summary>
/// This class is used to load soundfonts.
/// </summary>
private class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
{
private readonly Dictionary<int, Stream> _openStreams = new();
private int _nextStreamId = 1;

View File

@@ -180,7 +180,7 @@ namespace Robust.Client.Audio.Midi
internal void InternalDispose();
}
internal class MidiRenderer : IMidiRenderer
internal sealed class MidiRenderer : IMidiRenderer
{
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;

View File

@@ -20,13 +20,12 @@ using Robust.Shared.Utility;
namespace Robust.Client
{
/// <inheritdoc />
public class BaseClient : IBaseClient
public sealed class BaseClient : IBaseClient
{
[Dependency] private readonly IClientNetManager _net = default!;
[Dependency] private readonly IPlayerManager _playMan = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IDiscordRichPresence _discord = default!;
[Dependency] private readonly IGameTiming _timing = default!;
@@ -213,7 +212,6 @@ namespace Robust.Client
{
_entityManager.Startup();
_mapManager.Startup();
_entityLookup.Startup();
_timing.ResetSimTime();
_timing.Paused = false;
@@ -224,7 +222,6 @@ namespace Robust.Client
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
_gameStates.Reset();
_playMan.Shutdown();
_entityLookup.Shutdown();
_entityManager.Shutdown();
_mapManager.Shutdown();
_discord.ClearPresence();
@@ -291,7 +288,7 @@ namespace Robust.Client
/// <summary>
/// Event arguments for when something changed with the player.
/// </summary>
public class PlayerEventArgs : EventArgs
public sealed class PlayerEventArgs : EventArgs
{
/// <summary>
/// The session that triggered the event.
@@ -310,7 +307,7 @@ namespace Robust.Client
/// <summary>
/// Event arguments for when the RunLevel has changed in the BaseClient.
/// </summary>
public class RunLevelChangedEventArgs : EventArgs
public sealed class RunLevelChangedEventArgs : EventArgs
{
/// <summary>
/// RunLevel that the BaseClient switched from.
@@ -335,7 +332,7 @@ namespace Robust.Client
/// <summary>
/// Info about the server and player that is sent to the client while connecting.
/// </summary>
public class ServerInfo
public sealed class ServerInfo
{
public ServerInfo(string serverName)
{

View File

@@ -43,11 +43,10 @@ namespace Robust.Client
IoCManager.Register<IGameTiming, ClientGameTiming>();
IoCManager.Register<IClientGameTiming, ClientGameTiming>();
IoCManager.Register<IPrototypeManager, ClientPrototypeManager>();
IoCManager.Register<IMapManager, ClientMapManager>();
IoCManager.Register<IMapManagerInternal, ClientMapManager>();
IoCManager.Register<IClientMapManager, ClientMapManager>();
IoCManager.Register<IMapManager, NetworkedMapManager>();
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
@@ -72,7 +71,6 @@ namespace Robust.Client
IoCManager.Register<IStateManager, StateManager>();
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
IoCManager.Register<IDebugDrawing, DebugDrawing>();
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IMidiManager, MidiManager>();

View File

@@ -2,7 +2,7 @@
namespace Robust.Client.Console
{
public class ClientConGroupController : IClientConGroupController
public sealed class ClientConGroupController : IClientConGroupController
{
private IClientConGroupImplementation? _implementation;
public event Action? ConGroupUpdated;

View File

@@ -14,7 +14,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.Console
{
public class AddStringArgs : EventArgs
public sealed class AddStringArgs : EventArgs
{
public string Text { get; }
@@ -30,7 +30,7 @@ namespace Robust.Client.Console
}
}
public class AddFormattedMessageArgs : EventArgs
public sealed class AddFormattedMessageArgs : EventArgs
{
public readonly FormattedMessage Message;
@@ -41,7 +41,7 @@ namespace Robust.Client.Console
}
/// <inheritdoc cref="IClientConsoleHost" />
internal class ClientConsoleHost : ConsoleHost, IClientConsoleHost
internal sealed class ClientConsoleHost : ConsoleHost, IClientConsoleHost
{
[Dependency] private readonly IClientConGroupController _conGroup = default!;
@@ -109,13 +109,13 @@ namespace Robust.Client.Console
if (AvailableCommands.ContainsKey(commandName))
{
var playerManager = IoCManager.Resolve<IPlayerManager>();
#if !DEBUG
if (!_conGroup.CanCommand(commandName) && playerManager.LocalPlayer?.Session.Status > SessionStatus.Connecting)
{
WriteError(null, $"Insufficient perms for command: {commandName}");
return;
}
#endif
var command1 = AvailableCommands[commandName];
args.RemoveAt(0);
var shell = new ConsoleShell(this, null);
@@ -209,7 +209,7 @@ namespace Robust.Client.Console
/// These dummies are made purely so list and help can list server-side commands.
/// </summary>
[Reflect(false)]
internal class ServerDummyCommand : IConsoleCommand
internal sealed class ServerDummyCommand : IConsoleCommand
{
internal ServerDummyCommand(string command, string help, string description)
{

View File

@@ -59,7 +59,7 @@ namespace Robust.Client.Console.Commands
}
[UsedImplicitly]
public class SaveConfig : IConsoleCommand
public sealed class SaveConfig : IConsoleCommand
{
public string Command => "saveconfig";
public string Description => "Saves the client configuration to the config file";

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Console;
namespace Robust.Client.Console.Commands
{
class ClearCommand : IConsoleCommand
sealed class ClearCommand : IConsoleCommand
{
public string Command => "cls";
public string Help => "Clears the debug console of all messages.";
@@ -18,7 +18,7 @@ namespace Robust.Client.Console.Commands
}
}
class FillCommand : IConsoleCommand
sealed class FillCommand : IConsoleCommand
{
public string Command => "fill";
public string Help => "Fills the console with some nonsense for debugging.";

View File

@@ -30,7 +30,7 @@ using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console.Commands
{
internal class DumpEntitiesCommand : IConsoleCommand
internal sealed class DumpEntitiesCommand : IConsoleCommand
{
public string Command => "dumpentities";
public string Help => "Dump entity list";
@@ -47,7 +47,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class GetComponentRegistrationCommand : IConsoleCommand
internal sealed class GetComponentRegistrationCommand : IConsoleCommand
{
public string Command => "getcomponentregistration";
public string Help => "Usage: getcomponentregistration <componentName>";
@@ -93,7 +93,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class ToggleMonitorCommand : IConsoleCommand
internal sealed class ToggleMonitorCommand : IConsoleCommand
{
public string Command => "monitor";
@@ -148,7 +148,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class ExceptionCommand : IConsoleCommand
internal sealed class ExceptionCommand : IConsoleCommand
{
public string Command => "fuck";
public string Help => "Throws an exception";
@@ -160,7 +160,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class ShowPositionsCommand : IConsoleCommand
internal sealed class ShowPositionsCommand : IConsoleCommand
{
public string Command => "showpos";
public string Help => "";
@@ -168,12 +168,12 @@ namespace Robust.Client.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IDebugDrawing>();
var mgr = EntitySystem.Get<DebugDrawingSystem>();
mgr.DebugPositions = !mgr.DebugPositions;
}
}
internal class ShowRayCommand : IConsoleCommand
internal sealed class ShowRayCommand : IConsoleCommand
{
public string Command => "showrays";
public string Help => "Usage: showrays <raylifetime>";
@@ -200,7 +200,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class DisconnectCommand : IConsoleCommand
internal sealed class DisconnectCommand : IConsoleCommand
{
public string Command => "disconnect";
public string Help => "";
@@ -212,7 +212,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class EntityInfoCommand : IConsoleCommand
internal sealed class EntityInfoCommand : IConsoleCommand
{
public string Command => "entfo";
@@ -264,7 +264,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class SnapGridGetCell : IConsoleCommand
internal sealed class SnapGridGetCell : IConsoleCommand
{
public string Command => "sggcell";
public string Help => "sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.";
@@ -313,7 +313,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class SetPlayerName : IConsoleCommand
internal sealed class SetPlayerName : IConsoleCommand
{
public string Command => "overrideplayername";
public string Description => "Changes the name used when attempting to connect to the server.";
@@ -333,7 +333,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class LoadResource : IConsoleCommand
internal sealed class LoadResource : IConsoleCommand
{
public string Command => "ldrsc";
public string Description => "Pre-caches a resource.";
@@ -370,7 +370,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class ReloadResource : IConsoleCommand
internal sealed class ReloadResource : IConsoleCommand
{
public string Command => "rldrsc";
public string Description => "Reloads a resource.";
@@ -404,7 +404,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class GridTileCount : IConsoleCommand
internal sealed class GridTileCount : IConsoleCommand
{
public string Command => "gridtc";
public string Description => "Gets the tile count of a grid";
@@ -438,7 +438,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class GuiDumpCommand : IConsoleCommand
internal sealed class GuiDumpCommand : IConsoleCommand
{
public string Command => "guidump";
public string Description => "Dump GUI tree to /guidump.txt in user data.";
@@ -512,7 +512,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class UITestCommand : IConsoleCommand
internal sealed class UITestCommand : IConsoleCommand
{
public string Command => "uitest";
public string Description => "Open a dummy UI testing window";
@@ -644,7 +644,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class SetClipboardCommand : IConsoleCommand
internal sealed class SetClipboardCommand : IConsoleCommand
{
public string Command => "setclipboard";
public string Description => "Sets the system clipboard";
@@ -657,7 +657,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class GetClipboardCommand : IConsoleCommand
internal sealed class GetClipboardCommand : IConsoleCommand
{
public string Command => "getclipboard";
public string Description => "Gets the system clipboard";
@@ -670,7 +670,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class ToggleLight : IConsoleCommand
internal sealed class ToggleLight : IConsoleCommand
{
public string Command => "togglelight";
public string Description => "Toggles light rendering.";
@@ -684,7 +684,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class ToggleFOV : IConsoleCommand
internal sealed class ToggleFOV : IConsoleCommand
{
public string Command => "togglefov";
public string Description => "Toggles fov for client.";
@@ -698,7 +698,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class ToggleHardFOV : IConsoleCommand
internal sealed class ToggleHardFOV : IConsoleCommand
{
public string Command => "togglehardfov";
public string Description => "Toggles hard fov for client (for debugging space-station-14#2353).";
@@ -712,7 +712,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class ToggleShadows : IConsoleCommand
internal sealed class ToggleShadows : IConsoleCommand
{
public string Command => "toggleshadows";
public string Description => "Toggles shadow rendering.";
@@ -725,7 +725,7 @@ namespace Robust.Client.Console.Commands
mgr.DrawShadows = !mgr.DrawShadows;
}
}
internal class ToggleLightBuf : IConsoleCommand
internal sealed class ToggleLightBuf : IConsoleCommand
{
public string Command => "togglelightbuf";
public string Description => "Toggles lighting rendering. This includes shadows but not FOV.";
@@ -739,7 +739,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class GcCommand : IConsoleCommand
internal sealed class GcCommand : IConsoleCommand
{
public string Command => "gc";
public string Description => "Run the GC.";
@@ -761,7 +761,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class GcFullCommand : IConsoleCommand
internal sealed class GcFullCommand : IConsoleCommand
{
public string Command => "gcf";
public string Description => "Run the GC, fully, compacting LOH and everything.";
@@ -774,7 +774,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class GcModeCommand : IConsoleCommand
internal sealed class GcModeCommand : IConsoleCommand
{
public string Command => "gc_mode";
@@ -816,7 +816,7 @@ namespace Robust.Client.Console.Commands
}
internal class SerializeStatsCommand : IConsoleCommand
internal sealed class SerializeStatsCommand : IConsoleCommand
{
public string Command => "szr_stats";
@@ -836,7 +836,7 @@ namespace Robust.Client.Console.Commands
}
internal class ChunkInfoCommand : IConsoleCommand
internal sealed class ChunkInfoCommand : IConsoleCommand
{
public string Command => "chunkinfo";
public string Description => "Gets info about a chunk under your mouse cursor.";
@@ -861,11 +861,11 @@ namespace Robust.Client.Console.Commands
var chunkIndex = grid.LocalToChunkIndices(grid.MapToGrid(mousePos));
var chunk = internalGrid.GetChunk(chunkIndex);
shell.WriteLine($"worldBounds: {chunk.CalcWorldAABB()} localBounds: {chunk.CalcLocalBounds()}");
shell.WriteLine($"worldBounds: {internalGrid.CalcWorldAABB(chunk)} localBounds: {chunk.CachedBounds}");
}
}
internal class ReloadShadersCommand : IConsoleCommand
internal sealed class ReloadShadersCommand : IConsoleCommand
{
public string Command => "rldshader";
@@ -1036,7 +1036,7 @@ namespace Robust.Client.Console.Commands
}
internal class ClydeDebugLayerCommand : IConsoleCommand
internal sealed class ClydeDebugLayerCommand : IConsoleCommand
{
public string Command => "cldbglyr";
public string Description => "Toggle fov and light debug layers";
@@ -1061,7 +1061,7 @@ namespace Robust.Client.Console.Commands
}
}
internal class GetKeyInfoCommand : IConsoleCommand
internal sealed class GetKeyInfoCommand : IConsoleCommand
{
public string Command => "keyinfo";
public string Description => "Keys key info for a key";

View File

@@ -4,7 +4,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
public class GridChunkBBCommand : IConsoleCommand
public sealed class GridChunkBBCommand : IConsoleCommand
{
public string Command => "showchunkbb";
public string Description => "Displays chunk bounds for the purposes of rendering";

View File

@@ -5,7 +5,7 @@ using Robust.Shared.Network;
namespace Robust.Client.Console.Commands
{
class HelpCommand : IConsoleCommand
sealed class HelpCommand : IConsoleCommand
{
public string Command => "help";
public string Help => "When no arguments are provided, displays a generic help text. When an argument is passed, display the help text for the command with that name.";
@@ -44,7 +44,7 @@ namespace Robust.Client.Console.Commands
}
}
class ListCommand : IConsoleCommand
sealed class ListCommand : IConsoleCommand
{
public string Command => "list";
public string Help => "Usage: list [filter]\n" +

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Console;
namespace Robust.Client.Console.Commands
{
class LogSetLevelCommand : IConsoleCommand
sealed class LogSetLevelCommand : IConsoleCommand
{
public string Command => "loglevel";
public string Description => "Changes the log level for a provided sawmill.";
@@ -40,7 +40,7 @@ namespace Robust.Client.Console.Commands
}
}
class TestLog : IConsoleCommand
sealed class TestLog : IConsoleCommand
{
public string Command => "testlog";
public string Description => "Writes a test log to a sawmill.";

View File

@@ -4,7 +4,7 @@ using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
class HardQuitCommand : IConsoleCommand
sealed class HardQuitCommand : IConsoleCommand
{
public string Command => "hardquit";
public string Description => "Kills the game client instantly.";
@@ -16,7 +16,7 @@ namespace Robust.Client.Console.Commands
}
}
class QuitCommand : IConsoleCommand
sealed class QuitCommand : IConsoleCommand
{
public string Command => "quit";
public string Description => "Shuts down the game client gracefully.";

View File

@@ -6,7 +6,7 @@ using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
[UsedImplicitly]
public class SetInputContextCommand : IConsoleCommand
public sealed class SetInputContextCommand : IConsoleCommand
{
public string Command => "setinputcontext";
public string Description => "Sets the active input context.";

View File

@@ -4,7 +4,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
public class VelocitiesCommand : IConsoleCommand
public sealed class VelocitiesCommand : IConsoleCommand
{
public string Command => "showvelocities";
public string Description => "Displays your angular and linear velocities";

View File

@@ -11,7 +11,7 @@ using static Robust.Shared.Network.Messages.MsgScriptCompletionResponse;
namespace Robust.Client.Console
{
public class Completions : DefaultWindow
public sealed class Completions : DefaultWindow
{
private HistoryLineEdit _textBar;
private ScrollContainer _suggestPanel = new()
@@ -75,7 +75,7 @@ namespace Robust.Client.Console
}
// Label and ghetto button.
public class Entry : RichTextLabel
public sealed class Entry : RichTextLabel
{
public readonly LiteResult Result;

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Network.Messages;
namespace Robust.Client.Console
{
public partial class ScriptClient : IScriptClient
public sealed partial class ScriptClient : IScriptClient
{
[Dependency] private readonly IClientConGroupController _conGroupController = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;

View File

@@ -15,7 +15,7 @@ using static Robust.Client.UserInterface.Controls.BoxContainer;
namespace Robust.Client.Console
{
public class WatchWindow : DefaultWindow
public sealed class WatchWindow : DefaultWindow
{
private readonly IReflectionManager _reflectionManager;

View File

@@ -6,17 +6,21 @@ using Robust.Shared.Maths;
namespace Robust.Client.Debugging
{
/// <inheritdoc />
public class DebugDrawing : IDebugDrawing
/// <summary>
/// A collection of visual debug overlays for the client game.
/// </summary>
public sealed class DebugDrawingSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
private bool _debugPositions;
/// <inheritdoc />
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>
public bool DebugPositions
{
get => _debugPositions;
@@ -42,13 +46,13 @@ namespace Robust.Client.Debugging
private sealed class EntityPositionOverlay : Overlay
{
private readonly IEntityLookup _lookup;
private readonly EntityLookupSystem _lookup;
private readonly IEyeManager _eyeManager;
private readonly IEntityManager _entityManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager, IEntityManager entityManager)
public EntityPositionOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IEntityManager entityManager)
{
_lookup = lookup;
_eyeManager = eyeManager;
@@ -61,13 +65,11 @@ namespace Robust.Client.Debugging
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var viewport = _eyeManager.GetWorldViewport();
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
{
var transform = _entityManager.GetComponent<TransformComponent>(entity);
var center = transform.WorldPosition;
var worldRotation = transform.WorldRotation;
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);

View File

@@ -209,8 +209,8 @@ namespace Robust.Client.Debugging
foreach (var fixture in _entityManager.GetComponent<FixturesComponent>(physBody.Owner).Fixtures.Values)
{
// Invalid shape - Box2D doesn't check for IsSensor
if (physBody.BodyType == BodyType.Dynamic && fixture.Mass == 0f)
// Invalid shape - Box2D doesn't check for IsSensor but we will for sanity.
if (physBody.BodyType == BodyType.Dynamic && fixture.Mass == 0f && fixture.Hard)
{
DrawShape(worldHandle, fixture, xform, Color.Red.WithAlpha(AlphaModifier));
}

View File

@@ -1,13 +0,0 @@
namespace Robust.Client.Debugging
{
/// <summary>
/// A collection of visual debug overlays for the client game.
/// </summary>
public interface IDebugDrawing
{
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>
bool DebugPositions { get; set; }
}
}

View File

@@ -7,7 +7,7 @@ namespace Robust.Client
{
internal partial class GameController
{
internal class LoaderEntryPoint : ILoaderEntryPoint
internal sealed class LoaderEntryPoint : ILoaderEntryPoint
{
public void Main(IMainArgs args)
{

View File

@@ -50,7 +50,6 @@ namespace Robust.Client
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
@@ -455,6 +454,7 @@ namespace Robust.Client
private void Tick(FrameEventArgs frameEventArgs)
{
_modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs);
_console.CommandBufferExecute();
_timerManager.UpdateTimers(frameEventArgs);
_taskManager.ProcessPendingTasks();
@@ -470,7 +470,6 @@ namespace Robust.Client
// The last real tick is the current tick! This way we won't be in "prediction" mode.
_gameTiming.LastRealTick = _gameTiming.CurTick;
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
_lookup.Update();
}
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
@@ -510,7 +509,7 @@ namespace Robust.Client
logManager.GetSawmill("discord").Level = LogLevel.Warning;
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
logManager.GetSawmill("szr").Level = LogLevel.Info;
logManager.GetSawmill("loc").Level = LogLevel.Error;
logManager.GetSawmill("loc").Level = LogLevel.Warning;
#if DEBUG_ONLY_FCE_INFO
#if DEBUG_ONLY_FCE_LOG
@@ -575,7 +574,6 @@ namespace Robust.Client
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
_clydeAudio.Shutdown();

View File

@@ -3,7 +3,7 @@ using Robust.Shared.Utility;
namespace Robust.Client
{
public class GameControllerOptions
public sealed class GameControllerOptions
{
/// <summary>
/// Whether content sandboxing will be enabled & enforced.

View File

@@ -6,7 +6,7 @@ using Robust.Shared.Reflection;
namespace Robust.Client.GameObjects
{
internal class ClientComponentFactory : ComponentFactory
internal sealed class ClientComponentFactory : ComponentFactory
{
public ClientComponentFactory(IDynamicTypeFactoryInternal typeFactory, IReflectionManager reflectionManager, IConsoleHost conHost)
: base(typeFactory, reflectionManager, conHost)

View File

@@ -4,6 +4,7 @@ using System.Runtime.Serialization;
namespace Robust.Client.GameObjects
{
[Serializable]
[Virtual]
public class ComponentStateApplyException : Exception
{
public ComponentStateApplyException()

View File

@@ -1,4 +1,5 @@
using Robust.Shared.GameObjects;
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Client.GameObjects;
@@ -14,6 +15,7 @@ public abstract class AppearanceVisualizer
/// Initializes an entity to be managed by this appearance controller.
/// DO NOT assume this is your only entity. Visualizers are shared.
/// </summary>
[Obsolete("Subscribe to your component being initialised instead.")]
public virtual void InitializeEntity(EntityUid entity)
{
}
@@ -23,6 +25,7 @@ public abstract class AppearanceVisualizer
/// Update its visuals here.
/// </summary>
/// <param name="component">The appearance component of the entity that might need updating.</param>
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
public virtual void OnChangeData(AppearanceComponent component)
{
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
@@ -9,39 +10,10 @@ namespace Robust.Client.GameObjects;
/// This is the client instance of <see cref="AppearanceComponent"/>.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(AppearanceComponent))]
[ComponentReference(typeof(AppearanceComponent)), Friend(typeof(AppearanceSystem))]
public sealed class ClientAppearanceComponent : AppearanceComponent
{
[ViewVariables]
private bool _appearanceDirty;
[ViewVariables]
[DataField("visuals")]
internal List<AppearanceVisualizer> Visualizers = new();
protected override void MarkDirty()
{
if (_appearanceDirty)
return;
EntitySystem.Get<AppearanceSystem>().EnqueueUpdate(this);
_appearanceDirty = true;
}
protected override void Initialize()
{
base.Initialize();
foreach (var visual in Visualizers)
{
visual.InitializeEntity(Owner);
}
MarkDirty();
}
internal void UnmarkDirty()
{
_appearanceDirty = false;
}
}

View File

@@ -9,7 +9,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedEyeComponent))]
public class EyeComponent : SharedEyeComponent
public sealed class EyeComponent : SharedEyeComponent
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;

View File

@@ -12,7 +12,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public class IconComponent : Component, ISerializationHooks
public sealed class IconComponent : Component, ISerializationHooks
{
public IDirectionalTextureProvider? Icon { get; private set; }

View File

@@ -8,7 +8,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Defines data fields used in the <see cref="InputSystem"/>.
/// </summary>
public class InputComponent : Component
public sealed class InputComponent : Component
{
/// <summary>
/// The context that will be made active for a client that attaches to this entity.

View File

@@ -8,7 +8,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(OccluderComponent))]
internal sealed class ClientOccluderComponent : OccluderComponent
public sealed class ClientOccluderComponent : OccluderComponent
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
@@ -40,13 +40,14 @@ namespace Robust.Client.GameObjects
public void AnchorStateChanged()
{
SendDirty();
var xform = _entityManager.GetComponent<TransformComponent>(Owner);
SendDirty(xform);
if(!_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
if(!xform.Anchored)
return;
var grid = _mapManager.GetGrid(_entityManager.GetComponent<TransformComponent>(Owner).GridID);
_lastPosition = (_entityManager.GetComponent<TransformComponent>(Owner).GridID, grid.TileIndicesFor(_entityManager.GetComponent<TransformComponent>(Owner).Coordinates));
var grid = _mapManager.GetGrid(xform.GridID);
_lastPosition = (xform.GridID, grid.TileIndicesFor(xform.Coordinates));
}
protected override void Shutdown()
@@ -56,9 +57,10 @@ namespace Robust.Client.GameObjects
SendDirty();
}
private void SendDirty()
private void SendDirty(TransformComponent? xform = null)
{
if (_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
xform ??= _entityManager.GetComponent<TransformComponent>(Owner);
if (xform.Anchored)
{
_entityManager.EventBus.RaiseEvent(EventSource.Local,
new OccluderDirtyEvent(Owner, _lastPosition));
@@ -69,13 +71,29 @@ namespace Robust.Client.GameObjects
{
Occluding = OccluderDir.None;
if (Deleted || !_entityManager.GetComponent<TransformComponent>(Owner).Anchored)
if (Deleted)
return;
// Content may want to override the default behavior for occlusion.
var xform = _entityManager.GetComponent<TransformComponent>(Owner);
var ev = new OccluderDirectionsEvent
{
Component = xform,
};
_entityManager.EventBus.RaiseLocalEvent(Owner, ref ev);
if (ev.Handled)
{
Occluding = ev.Directions;
return;
}
var grid = _mapManager.GetGrid(_entityManager.GetComponent<TransformComponent>(Owner).GridID);
var position = _entityManager.GetComponent<TransformComponent>(Owner).Coordinates;
if (!xform.Anchored)
return;
var grid = _mapManager.GetGrid(xform.GridID);
var position = xform.Coordinates;
void CheckDir(Direction dir, OccluderDir oclDir)
{
foreach (var neighbor in grid.GetInDir(position, dir))
@@ -88,7 +106,7 @@ namespace Robust.Client.GameObjects
}
}
var angle = _entityManager.GetComponent<TransformComponent>(Owner).LocalRotation;
var angle = xform.LocalRotation;
var dirRolling = angle.GetCardinalDir();
// dirRolling starts at effective south
@@ -103,15 +121,28 @@ namespace Robust.Client.GameObjects
CheckDir(dirRolling, OccluderDir.East);
}
}
[Flags]
internal enum OccluderDir : byte
{
None = 0,
North = 1,
East = 1 << 1,
South = 1 << 2,
West = 1 << 3,
}
[Flags]
public enum OccluderDir : byte
{
None = 0,
North = 1,
East = 1 << 1,
South = 1 << 2,
West = 1 << 3,
}
/// <summary>
/// Raised by occluders when trying to get occlusion directions.
/// </summary>
[ByRefEvent]
public struct OccluderDirectionsEvent
{
public bool Handled = false;
public OccluderDir Directions = OccluderDir.None;
public TransformComponent Component = default!;
public OccluderDirectionsEvent() {}
}
}

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedPointLightComponent))]
public class PointLightComponent : SharedPointLightComponent, ISerializationHooks
public sealed class PointLightComponent : SharedPointLightComponent, ISerializationHooks
{
[Dependency] private readonly IEntityManager _entityManager = default!;
@@ -139,7 +139,7 @@ namespace Robust.Client.GameObjects
internal RenderingTreeComponent? RenderTree { get; set; }
}
public class PointLightRadiusChangedEvent : EntityEventArgs
public sealed class PointLightRadiusChangedEvent : EntityEventArgs
{
public PointLightComponent PointLightComponent { get; }

View File

@@ -119,7 +119,8 @@ namespace Robust.Client.GameObjects
/// This is useful to allow layer map configs to be defined in prototypes,
/// while still allowing code to create configs if they're absent.
/// </remarks>
void LayerMapReserveBlank(object key);
/// <returns>Index of the new layer.</returns>
int LayerMapReserveBlank(object key);
/// <summary>
/// Adds a layer without texture (thus falling back to the error texture).
@@ -145,8 +146,8 @@ namespace Robust.Client.GameObjects
void RemoveLayer(int layer);
void RemoveLayer(object layerKey);
void LayerSetShader(int layer, ShaderInstance shader);
void LayerSetShader(object layerKey, ShaderInstance shader);
void LayerSetShader(int layer, ShaderInstance shader, string? prototype = null);
void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null);
void LayerSetShader(int layer, string shaderName);
void LayerSetShader(object layerKey, string shaderName);
@@ -217,8 +218,8 @@ namespace Robust.Client.GameObjects
int GetLayerDirectionCount(ISpriteLayer layer);
/// <summary>
/// Calculate sprite bounding box in world-space coordinates.
/// Calculate the rotated sprite bounding box in world-space coordinates.
/// </summary>
Box2 CalculateBoundingBox(Vector2 worldPos);
Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null);
}
}

View File

@@ -25,8 +25,6 @@ namespace Robust.Client.GameObjects
RSI.State.Direction EffectiveDirection(Angle worldRotation);
Vector2 LocalToLayer(Vector2 localPos);
/// <summary>
/// Layer size in pixels.
/// Don't account layer scale or sprite world transform.
@@ -37,6 +35,6 @@ namespace Robust.Client.GameObjects
/// Calculate layer bounding box in sprite local-space coordinates.
/// </summary>
/// <returns>Bounding box in sprite local-space coordinates.</returns>
Box2 CalculateBoundingBox();
Box2 CalculateBoundingBox(Angle worldAngle);
}
}

View File

@@ -57,7 +57,7 @@ namespace Robust.Client.GameObjects
private bool _enabled;
}
public class SpriteBoundsOverlay : Overlay
public sealed class SpriteBoundsOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
@@ -84,10 +84,16 @@ namespace Robust.Client.GameObjects
foreach (var sprite in comp.SpriteTree.QueryAabb(localAABB))
{
var worldPos = _entityManager.GetComponent<TransformComponent>(sprite.Owner).WorldPosition;
var bounds = sprite.CalculateBoundingBox(worldPos);
var (worldPos, worldRot) = _entityManager.GetComponent<TransformComponent>(sprite.Owner).GetWorldPositionRotation();
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot);
// Get scaled down bounds used to indicate the "south" of a sprite.
var localBound = bounds.Box;
var smallLocal = localBound.Scale(0.2f).Translated(-new Vector2(0f, localBound.Extents.Y));
var southIndicator = new Box2Rotated(smallLocal, bounds.Rotation, bounds.Origin);
handle.DrawRect(bounds, Color.Red.WithAlpha(0.2f));
handle.DrawRect(bounds.Scale(0.2f).Translated(-new Vector2(0f, bounds.Extents.Y)), Color.Blue.WithAlpha(0.5f));
handle.DrawRect(southIndicator, Color.Blue.WithAlpha(0.5f));
}
}
}

View File

@@ -20,6 +20,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using RSIDirection = Robust.Client.Graphics.RSI.State.Direction;
namespace Robust.Client.GameObjects
{
@@ -31,6 +32,8 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[Dependency] private readonly IEntityManager entities = default!;
[Dependency] private readonly IReflectionManager reflection = default!;
[Dependency] private readonly IEyeManager eyeManager = default!;
[DataField("visible")]
private bool _visible = true;
@@ -72,7 +75,11 @@ namespace Robust.Client.GameObjects
public Vector2 Scale
{
get => scale;
set => scale = value;
set
{
scale = value;
UpdateLocalMatrix();
}
}
[DataField("rotation")]
@@ -83,7 +90,11 @@ namespace Robust.Client.GameObjects
public Angle Rotation
{
get => rotation;
set => rotation = value;
set
{
rotation = value;
UpdateLocalMatrix();
}
}
[DataField("offset")]
@@ -97,12 +108,18 @@ namespace Robust.Client.GameObjects
public Vector2 Offset
{
get => offset;
set => offset = value;
set
{
offset = value;
UpdateLocalMatrix();
}
}
[DataField("color")]
private Color color = Color.White;
public Matrix3 LocalMatrix = Matrix3.Identity;
[Animatable]
[ViewVariables(VVAccess.ReadWrite)]
public Color Color
@@ -134,113 +151,7 @@ namespace Robust.Client.GameObjects
Layers.Clear();
foreach (var layerDatum in value)
{
var anyTextureAttempted = false;
var layer = new Layer(this);
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
if (IoCManager.Resolve<IResourceCache>().TryGetResource(path, out RSIResource? resource))
{
layer.RSI = resource.RSI;
}
else
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
layerDatum.State);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (IoCManager.Resolve<IPrototypeManager>().TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist.",
layerDatum.Shader);
}
}
layer.Color = layerDatum.Color;
layer.Rotation = layerDatum.Rotation;
layer._offset = layerDatum.Offset;
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
layer.Scale = layerDatum.Scale;
Layers.Add(layer);
if (layerDatum.MapKeys != null)
{
var index = Layers.Count - 1;
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (LayerMap.ContainsKey(key))
{
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
LayerMap.Add(key, index);
}
}
AddLayer(layerDatum);
}
_layerMapShared = true;
@@ -336,11 +247,13 @@ namespace Robust.Client.GameObjects
void ISerializationHooks.AfterDeserialization()
{
IoCManager.InjectDependencies(this);
{
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
if(IoCManager.Resolve<IResourceCache>().TryGetResource(rsiPath, out RSIResource? resource))
if(resourceCache.TryGetResource(rsiPath, out RSIResource? resource))
{
BaseRSI = resource.RSI;
}
@@ -373,6 +286,8 @@ namespace Robust.Client.GameObjects
LayerMap.Clear();
LayerDatums = layerDatums;
}
UpdateLocalMatrix();
}
/// <summary>
@@ -390,6 +305,7 @@ namespace Robust.Client.GameObjects
offset = other.offset;
rotation = other.rotation;
scale = other.scale;
UpdateLocalMatrix();
drawDepth = other.drawDepth;
_screenLock = other._screenLock;
_overrideDirection = other._overrideDirection;
@@ -415,9 +331,14 @@ namespace Robust.Client.GameObjects
RenderOrder = other.RenderOrder;
}
internal void UpdateLocalMatrix()
{
LocalMatrix = Matrix3.CreateTransform(in offset, in rotation, in scale);
}
public Matrix3 GetLocalMatrix()
{
return Matrix3.CreateTransform(in offset, in rotation, in scale);
return LocalMatrix;
}
/// <inheritdoc />
@@ -462,14 +383,17 @@ namespace Robust.Client.GameObjects
_layerMapShared = false;
}
public void LayerMapReserveBlank(object key)
public int LayerMapReserveBlank(object key)
{
if (LayerMapTryGet(key, out var _))
if (LayerMapTryGet(key, out var index))
{
return;
return index;
}
LayerMapSet(key, AddBlankLayer());
index = AddBlankLayer();
LayerMapSet(key, index);
return index;
}
public int AddBlankLayer(int? newIndex = null)
@@ -478,6 +402,19 @@ namespace Robust.Client.GameObjects
return AddLayer(layer, newIndex);
}
/// <summary>
/// Add a new layer based on some <see cref="PrototypeLayerData"/>.
/// </summary>
public int AddLayer(PrototypeLayerData layerDatum, int? newIndex = null)
{
var layer = new Layer(this);
var index = AddLayer(layer, newIndex);
LayerSetData(index, layerDatum);
return index;
}
public int AddLayer(string texturePath, int? newIndex = null)
{
return AddLayer(new ResourcePath(texturePath), newIndex);
@@ -653,7 +590,144 @@ namespace Robust.Client.GameObjects
RemoveLayer(layer);
}
public void LayerSetShader(int layer, ShaderInstance? shader)
/// <summary>
/// Fills in a layer's values using some <see cref="PrototypeLayerData"/>.
/// </summary>
public void LayerSetData(int index, PrototypeLayerData layerDatum)
{
if (Layers.Count <= index)
{
Logger.ErrorS(LogCategory, "Layer with index '{0}' does not exist, cannot set layer data! Trace:\n{1}",
index, Environment.StackTrace);
return;
}
var layer = Layers[index];
var anyTextureAttempted = false;
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
if (resourceCache.TryGetResource(path, out RSIResource? resource))
{
layer.RSI = resource.RSI;
}
else
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
layerDatum.State);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
resourceCache.GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.ShaderPrototype = layerDatum.Shader;
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist.",
layerDatum.Shader);
}
}
if (layerDatum.MapKeys != null)
{
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (reflection.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (LayerMap.TryGetValue(key, out var mappedIndex))
{
if (mappedIndex != index)
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
_layerMapEnsurePrivate();
LayerMap[key] = index;
}
}
layer.Color = layerDatum.Color;
layer._rotation = layerDatum.Rotation;
layer._offset = layerDatum.Offset;
layer._scale = layerDatum.Scale;
layer.UpdateLocalMatrix();
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
}
public void LayerSetData(object layerKey, PrototypeLayerData data)
{
if (!LayerMapTryGet(layerKey, out var layer))
{
Logger.ErrorS(LogCategory, "Layer with key '{0}' does not exist, cannot set shader! Trace:\n{1}",
layerKey, Environment.StackTrace);
return;
}
LayerSetData(layer, data);
}
public void LayerSetShader(int layer, ShaderInstance? shader, string? prototype = null)
{
if (Layers.Count <= layer)
{
@@ -664,9 +738,10 @@ namespace Robust.Client.GameObjects
var theLayer = Layers[layer];
theLayer.Shader = shader;
theLayer.ShaderPrototype = prototype;
}
public void LayerSetShader(object layerKey, ShaderInstance shader)
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
{
if (!LayerMapTryGet(layerKey, out var layer))
{
@@ -675,7 +750,7 @@ namespace Robust.Client.GameObjects
return;
}
LayerSetShader(layer, shader);
LayerSetShader(layer, shader, prototype);
}
public void LayerSetShader(int layer, string shaderName)
@@ -684,10 +759,12 @@ namespace Robust.Client.GameObjects
{
Logger.ErrorS(LogCategory, "Shader prototype '{0}' does not exist. Trace:\n{1}", shaderName,
Environment.StackTrace);
LayerSetShader(layer, null, null);
return;
}
// This will set the shader to null if it does not exist.
LayerSetShader(layer, prototype?.Instance());
LayerSetShader(layer, prototype.Instance(), shaderName);
}
public void LayerSetShader(object layerKey, string shaderName)
@@ -1148,7 +1225,7 @@ namespace Robust.Client.GameObjects
return;
}
Layers[layer].SetOffset(layerOffset);
Layers[layer].Offset = layerOffset;
}
public void LayerSetOffset(object layerKey, Vector2 layerOffset)
@@ -1230,25 +1307,6 @@ namespace Robust.Client.GameObjects
RenderInternal(drawingHandle, eyeRotation, worldRotation, worldPosition, overrideDir);
}
private void CalcModelMatrix(int numDirs, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
{
Angle angle;
if (_screenLock)
{
// Negate the eye rotation in the model matrix, so that later when the view matrix is applied the
// sprite will be locked upright to the screen
angle = new Angle(-eyeRotation.Theta);
}
else
{
angle = CalcRectWorldAngle(worldRotation + eyeRotation, numDirs) - eyeRotation;
}
var sWorldRotation = angle;
modelMatrix = Matrix3.CreateTransform(in worldPosition, in sWorldRotation);
}
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
{
// Reduce the angles to fix math shenanigans
@@ -1257,56 +1315,16 @@ namespace Robust.Client.GameObjects
if (worldRotation.Theta < 0)
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
// sprite matrix, WITHOUT offset.
// offset is applied after sprite numDirs snapping/rotation correction
// --> apply at same time as layer offset
var spriteMatrix = Matrix3.CreateTransform(Vector2.Zero, rotation, scale);
// worldRotation + eyeRotation should be the angle of the entity on-screen. If no-rot is enabled this is just set to zero.
// However, at some point later the eye-matrix is applied separately, so we subtract -eye rotation for now:
var entityMatrix = Matrix3.CreateTransform(worldPosition, NoRotation ? -eyeRotation : worldRotation);
Matrix3.Multiply(ref LocalMatrix, ref entityMatrix, out var transform);
var angle = worldRotation + eyeRotation; // angle on-screen. Used to decide the direction of 4/8 directional RSIs
foreach (var layer in Layers)
{
if (!layer.Visible)
{
continue;
}
var numDirs = GetLayerDirectionCount(layer);
var layerRotation = worldRotation + layer.Rotation;
var layerPosition = worldPosition + layerRotation.RotateVec(layer._offset + offset);
CalcModelMatrix(numDirs, eyeRotation, layerRotation, layerPosition, out var modelMatrix);
Matrix3.Multiply(ref spriteMatrix, ref modelMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
RenderLayer(drawingHandle, layer, eyeRotation, layerRotation, overrideDirection);
}
}
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection)
{
var texture = GetRenderTexture(layer, worldRotation + eyeRotation, overrideDirection);
if (layer.Shader != null)
{
drawingHandle.UseShader(layer.Shader);
}
var layerColor = color * layer.Color;
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter);
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(position, textureSize);
// TODO: Implement layer-specific rotation and scale.
// Apply these directly to the box.
// Oh and when you do update Layer.LocalToLayer so content doesn't break.
// handle.Modulate changes the color
// drawingHandle.SetTransform() is set above, turning the quad into local space vertices
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
if (layer.Shader != null)
{
drawingHandle.UseShader(null);
layer.Render(drawingHandle, ref transform, angle, overrideDirection);
}
}
@@ -1342,27 +1360,6 @@ namespace Robust.Client.GameObjects
};
}
private Texture GetRenderTexture(Layer layer, Angle worldRotation, Direction? overrideDirection)
{
var texture = layer.Texture;
if (layer.State.IsValid)
{
// Pull texture from RSI state instead.
var rsi = layer.RSI ?? BaseRSI;
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
{
state = GetFallbackState(resourceCache);
}
var layerSpecificDir = layer.EffectiveDirection(state, worldRotation, overrideDirection);
texture = state.GetFrame(layerSpecificDir, layer.AnimationFrame);
}
texture ??= resourceCache.GetFallback<TextureResource>().Texture;
return texture;
}
public void FrameUpdate(float delta)
{
foreach (var t in Layers)
@@ -1417,12 +1414,14 @@ namespace Robust.Client.GameObjects
Visible = thestate.Visible;
DrawDepth = thestate.DrawDepth;
Scale = thestate.Scale;
Rotation = thestate.Rotation;
Offset = thestate.Offset;
scale = thestate.Scale;
rotation = thestate.Rotation;
offset = thestate.Offset;
UpdateLocalMatrix();
Color = thestate.Color;
RenderOrder = thestate.RenderOrder;
if (thestate.BaseRsiPath != null && BaseRSI != null)
{
if (resourceCache.TryGetResource<RSIResource>(TextureRoot / thestate.BaseRsiPath, out var res))
@@ -1438,43 +1437,8 @@ namespace Robust.Client.GameObjects
}
}
// Maybe optimize this to NOT full clear.
Layers.Clear();
for (var i = 0; i < thestate.Layers.Count; i++)
{
var netlayer = thestate.Layers[i];
var layer = new Layer(this)
{
// These are easy so do them here.
Scale = netlayer.Scale,
Rotation = netlayer.Rotation,
Visible = netlayer.Visible,
Color = netlayer.Color
};
Layers.Add(layer);
// Using the public API to handle errors.
// Probably slow as crap.
// Who am I kidding, DEFINITELY.
if (netlayer.Shader != null)
{
LayerSetShader(i, netlayer.Shader);
}
if (netlayer.RsiPath != null)
{
LayerSetRSI(i, netlayer.RsiPath);
}
if (netlayer.TexturePath != null)
{
LayerSetTexture(i, netlayer.TexturePath);
}
else if (netlayer.State != null)
{
LayerSetState(i, netlayer.State);
}
}
// Maybe optimize this to NOT fully clear the layers. (see LayerDatums setter function)
LayerDatums = thestate.Layers;
}
private void QueueUpdateIsInert()
@@ -1553,35 +1517,61 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc/>
public Box2 CalculateBoundingBox(Vector2 worldPos)
public Box2Rotated CalculateRotatedBoundingBox(Vector2 worldPosition, Angle worldRotation, IEye? eye = null)
{
// fast check for empty sprites
if (Layers.Count == 0)
return new Box2(worldPos, worldPos);
if (!Visible || Layers.Count == 0)
{
return new Box2Rotated(new Box2(worldPosition, worldPosition), Angle.Zero, worldPosition);
}
// We need to modify world rotation so that it lies between 0 and 2pi.
// This matters for 4 or 8 directional sprites deciding which quadrant (octant?) they lie in.
// the 0->2pi convention is set by the sprite-rendering code that selects the layers.
// See RenderInternal().
worldRotation = worldRotation.Reduced();
if (worldRotation.Theta < 0)
worldRotation = new Angle(worldRotation.Theta + Math.Tau);
eye ??= eyeManager.CurrentEye;
// Need relative angle on screen for determining the sprite rsi direction.
Angle relativeRotation = NoRotation
? Angle.Zero
: worldRotation + eye.Rotation;
// we need to calculate bounding box taking into account all nested layers
// because layers can have offsets, scale or rotation we need to calculate a new BB
// based on lowest bottomLeft and hightest topRight points from all layers
var box = Layers[0].CalculateBoundingBox();
// because layers can have offsets, scale or rotation, we need to calculate a new BB
// based on lowest bottomLeft and highest topRight points from all layers
var box = Layers[0].CalculateBoundingBox(relativeRotation);
for (int i = 1; i < Layers.Count; i++)
{
var layer = Layers[i];
var layerBB = layer.CalculateBoundingBox();
if (!layer.Visible) continue;
var layerBB = layer.CalculateBoundingBox(relativeRotation);
box = box.Union(layerBB);
}
// apply sprite transformations and calculate sprite bounding box
// we can optimize it a bit, if sprite doesn't have rotation
var spriteBox = box.Scale(Scale);
var spriteHasRotation = !Rotation.EqualsApprox(Angle.Zero);
var spriteBB = spriteHasRotation ?
new Box2Rotated(spriteBox, Rotation).CalcBoundingBox() : spriteBox;
// Next, what we do is take the box2 and apply the sprite's transform, and then the entity's transform. We
// could do this via Matrix3.TransformBox, but that only yields bounding boxes. So instead we manually
// transform our box by the combination of these matrices:
// move it all to world transform system (with sprite offset)
var worldBB = spriteBB.Translated(Offset + worldPos);
return worldBB;
if (Scale != Vector2.One)
box = box.Scale(Scale);
var adjustedOffset = NoRotation
? (-eye.Rotation).RotateVec(Offset)
: worldRotation.RotateVec(Offset);
Vector2 position = adjustedOffset + worldPosition;
Angle finalRotation = NoRotation
? Rotation - eye.Rotation
: Rotation + worldRotation;
return new Box2Rotated(box.Translated(position), finalRotation, position);
}
internal void UpdateBounds()
@@ -1615,10 +1605,11 @@ namespace Robust.Client.GameObjects
Flip = 3,
}
public class Layer : ISpriteLayer
public sealed class Layer : ISpriteLayer, ISerializationHooks
{
[ViewVariables] private readonly SpriteComponent _parent;
[ViewVariables] public string? ShaderPrototype;
[ViewVariables] public ShaderInstance? Shader;
[ViewVariables] public Texture? Texture;
@@ -1628,11 +1619,37 @@ namespace Robust.Client.GameObjects
[ViewVariables] public float AnimationTime;
[ViewVariables] public int AnimationFrame;
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Scale { get; set; } = Vector2.One;
public Matrix3 LocalMatrix = Matrix3.Identity;
[ViewVariables(VVAccess.ReadWrite)]
public Angle Rotation { get; set; }
public Vector2 Scale
{
get => _scale;
set
{
if (_scale.EqualsApprox(value)) return;
_scale = value;
UpdateLocalMatrix();
_parent.UpdateBounds();
}
}
internal Vector2 _scale = Vector2.One;
[ViewVariables(VVAccess.ReadWrite)]
public Angle Rotation
{
get => _rotation;
set
{
if (_rotation.EqualsApprox(value)) return;
_rotation = value;
UpdateLocalMatrix();
_parent.UpdateBounds();
}
}
internal Angle _rotation = Angle.Zero;
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible = true;
@@ -1652,6 +1669,7 @@ namespace Robust.Client.GameObjects
if (_offset.EqualsApprox(value)) return;
_offset = value;
UpdateLocalMatrix();
_parent.UpdateBounds();
}
}
@@ -1675,6 +1693,7 @@ namespace Robust.Client.GameObjects
if (toClone.Shader != null)
{
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
ShaderPrototype = toClone.ShaderPrototype;
}
Texture = toClone.Texture;
RSI = toClone.RSI;
@@ -1682,14 +1701,26 @@ namespace Robust.Client.GameObjects
AnimationTimeLeft = toClone.AnimationTimeLeft;
AnimationTime = toClone.AnimationTime;
AnimationFrame = toClone.AnimationFrame;
Scale = toClone.Scale;
Rotation = toClone.Rotation;
_scale = toClone.Scale;
_rotation = toClone.Rotation;
_offset = toClone.Offset;
UpdateLocalMatrix();
Visible = toClone.Visible;
Color = toClone.Color;
DirOffset = toClone.DirOffset;
AutoAnimated = toClone.AutoAnimated;
}
void ISerializationHooks.AfterDeserialization()
{
UpdateLocalMatrix();
}
internal void UpdateLocalMatrix()
{
LocalMatrix = Matrix3.CreateTransform(in _offset, in _rotation, in _scale);
}
RSI? ISpriteLayer.Rsi { get => RSI; set => SetRsi(value); }
RSI.StateId ISpriteLayer.RsiState { get => State; set => SetState(value); }
Texture? ISpriteLayer.Texture { get => Texture; set => SetTexture(value); }
@@ -1701,7 +1732,7 @@ namespace Robust.Client.GameObjects
Color = Color,
Rotation = Rotation,
Scale = Scale,
//todo Shader = Shader,
Shader = ShaderPrototype,
State = State.Name,
Visible = Visible,
RsiPath = RSI?.Path?.ToString(),
@@ -1730,7 +1761,7 @@ namespace Robust.Client.GameObjects
set => SetAutoAnimated(value);
}
public RSI.State.Direction EffectiveDirection(Angle worldRotation)
public RSIDirection EffectiveDirection(Angle worldRotation)
{
if (State == default)
{
@@ -1751,22 +1782,16 @@ namespace Robust.Client.GameObjects
return default;
}
public Vector2 LocalToLayer(Vector2 localPos)
{
// TODO: scale & rotation for layers is currently unimplemented.
return localPos;
}
public RSI.State.Direction EffectiveDirection(RSI.State state, Angle worldRotation,
public RSIDirection EffectiveDirection(RSI.State state, Angle worldRotation,
Direction? overrideDirection)
{
if (state.Directions == RSI.State.DirectionType.Dir1)
{
return RSI.State.Direction.South;
return RSIDirection.South;
}
else
{
RSI.State.Direction dir;
RSIDirection dir;
if (overrideDirection != null)
{
dir = overrideDirection.Value.Convert(state.Directions);
@@ -1898,11 +1923,6 @@ namespace Robust.Client.GameObjects
_parent.QueueUpdateIsInert();
}
public void SetOffset(Vector2 offset)
{
Offset = offset;
}
/// <inheritdoc/>
public Vector2i PixelSize
{
@@ -1923,10 +1943,121 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc/>
public Box2 CalculateBoundingBox()
public Box2 CalculateBoundingBox(Angle angle)
{
// TODO: scale & rotation for layers is currently unimplemented.
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
// Other than some special cases for simple layers, this will basically just apply the matrix obtained
// via GetLayerDrawMatrix() to this layer's bounding box.
var rsiState = GetActualState();
var dir = (rsiState == null || rsiState.Directions == RSI.State.DirectionType.Dir1)
? RSIDirection.South
: angle.ToRsiDirection(rsiState.Directions);
// special case for simple layers. The vast majority of layers are like this.
if (_rotation == Angle.Zero)
{
var textureSize = PixelSize / EyeManager.PixelsPerMeter;
// this switch block is basically an explicit version of the `rsiDirectionMatrix` in `GetLayerDrawMatrix()`.
var box = dir switch
{
// No rotation:
RSIDirection.South or RSIDirection.North => Box2.CenteredAround(Offset, textureSize),
// rotate 90 degrees:
RSIDirection.East or RSIDirection.West => Box2.CenteredAround(Offset, (textureSize.Y, textureSize.X)),
// rotated 45 degrees (any 45 degree rotated rectangle has a square bounding box with sides of length (x+y)/sqrt(2) )
_ => Box2.CenteredAround(Offset, Vector2.One * (textureSize.X + textureSize.Y) / MathF.Sqrt(2))
};
return _scale == Vector2.One ? box : box.Scale(_scale);
}
// Welp we have some non-zero _rotation, so lets just apply the generalized layer transform and get the bounding box from where;
GetLayerDrawMatrix(dir, out var layerDrawMatrix);
return layerDrawMatrix.TransformBox(Box2.CentredAroundZero(PixelSize / EyeManager.PixelsPerMeter));
}
internal RSI.State? GetActualState()
{
if (!State.IsValid)
return null;
// Pull texture from RSI state
var rsi = RSI ?? _parent.BaseRSI;
if (rsi == null || !rsi.TryGetState(State, out var rsiState))
{
rsiState = GetFallbackState(_parent.resourceCache);
}
return rsiState;
}
/// <summary>
/// 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)
{
if (_parent.NoRotation)
layerDrawMatrix = LocalMatrix;
else
{
var rsiDirectionMatrix = Matrix3.CreateTransform(Vector2.Zero, -dir.Convert().ToAngle());
Matrix3.Multiply(ref rsiDirectionMatrix, ref LocalMatrix, out layerDrawMatrix);
}
}
internal void Render(DrawingHandleWorld drawingHandle, ref Matrix3 spriteMatrix, Angle angle, Direction? overrideDirection)
{
if (!Visible)
return;
var rsiState = GetActualState();
var dir = (rsiState == null || rsiState.Directions == RSI.State.DirectionType.Dir1)
? RSIDirection.South
: angle.ToRsiDirection(rsiState.Directions);
// Set the drawing transform for this layer
GetLayerDrawMatrix(dir, out var layerMatrix);
Matrix3.Multiply(ref layerMatrix, ref spriteMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
// 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 && rsiState != null)
dir = overrideDirection.Value.Convert(rsiState.Directions);
dir = dir.OffsetRsiDir(DirOffset);
// Get the correct directional texture from the state, and draw it!
var texture = GetRenderTexture(rsiState, dir);
RenderTexture(drawingHandle, texture);
}
private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture)
{
if (Shader != null)
drawingHandle.UseShader(Shader);
var layerColor = _parent.color * Color;
var position = -(Vector2)texture.Size / (2f * EyeManager.PixelsPerMeter);
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(position, textureSize);
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
if (Shader != null)
drawingHandle.UseShader(null);
}
private Texture GetRenderTexture(RSI.State? state, RSIDirection dir)
{
if (state == null)
return Texture ?? _parent.resourceCache.GetFallback<TextureResource>().Texture;
return state.GetFrame(dir, AnimationFrame);
}
}
@@ -2034,6 +2165,7 @@ namespace Robust.Client.GameObjects
return results;
}
[Obsolete("Use SpriteSystem")]
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
var icon = IconComponent.GetPrototypeIcon(prototype, resourceCache);

View File

@@ -11,7 +11,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[ComponentReference(typeof(SharedUserInterfaceComponent))]
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
public sealed class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;

View File

@@ -62,6 +62,12 @@ namespace Robust.Client.GameObjects
Play(component, animation, key);
}
public void Play(EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key)
{
component ??= EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
Play(component, animation, key);
}
/// <summary>
/// Start playing an animation.
/// </summary>
@@ -79,6 +85,14 @@ namespace Robust.Client.GameObjects
component.PlayingAnimations.ContainsKey(key);
}
public bool HasRunningAnimation(EntityUid uid, AnimationPlayerComponent? component, string key)
{
if (component == null)
TryComp(uid, out component);
return component != null && component.PlayingAnimations.ContainsKey(key);
}
public bool HasRunningAnimation(AnimationPlayerComponent component, string key)
{
return component.PlayingAnimations.ContainsKey(key);
@@ -88,6 +102,18 @@ namespace Robust.Client.GameObjects
{
component.PlayingAnimations.Remove(key);
}
public void Stop(EntityUid uid, string key)
{
if (!TryComp<AnimationPlayerComponent>(uid, out var player)) return;
player.PlayingAnimations.Remove(key);
}
public void Stop(EntityUid uid, AnimationPlayerComponent? component, string key)
{
if (!Resolve(uid, ref component, false)) return;
component.PlayingAnimations.Remove(key);
}
}
public sealed class AnimationCompletedEvent : EntityEventArgs

View File

@@ -1,17 +1,95 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
internal sealed class AppearanceSystem : EntitySystem
public sealed class AppearanceSystem : SharedAppearanceSystem
{
private readonly Queue<ClientAppearanceComponent> _queuedUpdates = new();
public void EnqueueUpdate(ClientAppearanceComponent component)
public override void Initialize()
{
_queuedUpdates.Enqueue(component);
base.Initialize();
SubscribeLocalEvent<ClientAppearanceComponent, ComponentInit>(OnAppearanceInit);
SubscribeLocalEvent<ClientAppearanceComponent, ComponentHandleState>(OnAppearanceHandleState);
}
private void OnAppearanceInit(EntityUid uid, ClientAppearanceComponent component, ComponentInit args)
{
foreach (var visual in component.Visualizers)
{
visual.InitializeEntity(uid);
}
MarkDirty(component);
}
private void OnAppearanceHandleState(EntityUid uid, ClientAppearanceComponent component, ref ComponentHandleState args)
{
if (args.Current is not AppearanceComponentState actualState)
return;
var stateDiff = component.AppearanceData.Count != actualState.Data.Count;
if (!stateDiff)
{
foreach (var (key, value) in component.AppearanceData)
{
if (!actualState.Data.TryGetValue(key, out var stateValue) ||
!value.Equals(stateValue))
{
stateDiff = true;
break;
}
}
}
if (!stateDiff) return;
component.AppearanceData = CloneAppearanceData(actualState.Data);
MarkDirty(component);
}
/// <summary>
/// Take in an appearance data dictionary and attempt to clone it.
/// </summary>
/// <remarks>
/// As some appearance data values are not simple value-type objects, this is not just a shallow clone.
/// </remarks>
private Dictionary<object, object> CloneAppearanceData(Dictionary<object, object> data)
{
Dictionary<object, object> newDict = new(data.Count);
foreach (var (key, value) in data)
{
if (value.GetType().IsValueType)
newDict[key] = value;
else if (value is ICloneable cloneable)
newDict[key] = cloneable.Clone();
else
throw new NotSupportedException("Invalid object in appearance data dictionary. Appearance data must be cloneable");
}
return newDict;
}
public override void MarkDirty(AppearanceComponent component)
{
if (component.AppearanceDirty)
return;
_queuedUpdates.Enqueue((ClientAppearanceComponent) component);
component.AppearanceDirty = true;
}
internal void UnmarkDirty(ClientAppearanceComponent component)
{
component.AppearanceDirty = false;
}
public override void FrameUpdate(float frameTime)
@@ -22,7 +100,7 @@ namespace Robust.Client.GameObjects
continue;
OnChangeData(appearance.Owner, appearance);
appearance.UnmarkDirty();
UnmarkDirty(appearance);
}
}
@@ -30,10 +108,30 @@ namespace Robust.Client.GameObjects
{
if (!Resolve(uid, ref appearanceComponent, false)) return;
var ev = new AppearanceChangeEvent
{
Component = appearanceComponent,
AppearanceData = appearanceComponent.AppearanceData,
};
// Give it AppearanceData so we can still keep the friend attribute on the component.
EntityManager.EventBus.RaiseLocalEvent(uid, ref ev);
// Eventually visualizers would be nuked and we'd just make them components instead.
foreach (var visualizer in appearanceComponent.Visualizers)
{
visualizer.OnChangeData(appearanceComponent);
}
}
}
/// <summary>
/// Raised whenever the appearance data for an entity changes.
/// </summary>
[ByRefEvent]
public struct AppearanceChangeEvent
{
public AppearanceComponent Component;
public IReadOnlyDictionary<object, object> AppearanceData;
}
}

View File

@@ -17,7 +17,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public class AudioSystem : SharedAudioSystem, IAudioSystem
public sealed class AudioSystem : SharedAudioSystem, IAudioSystem
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -420,7 +420,7 @@ namespace Robust.Client.GameObjects
source.IsLooping = audioParams.Value.Loop;
}
private class PlayingStream : IPlayingAudioStream
private sealed class PlayingStream : IPlayingAudioStream
{
public uint? NetIdentifier;
public IClydeAudioSource Source = default!;

View File

@@ -72,10 +72,13 @@ namespace Robust.Client.GameObjects
var sender = ev.Sender;
if (EntityManager.EntityExists(sender) &&
EntityManager.TryGetComponent(sender, out ClientOccluderComponent? iconSmooth)
&& iconSmooth.Running)
&& iconSmooth.Initialized)
{
var grid1 = _mapManager.GetGrid(EntityManager.GetComponent<TransformComponent>(sender).GridID);
var coords = EntityManager.GetComponent<TransformComponent>(sender).Coordinates;
var xform = EntityManager.GetComponent<TransformComponent>(sender);
if (!_mapManager.TryGetGrid(xform.GridID, out var grid1))
return;
var coords = xform.Coordinates;
_dirtyEntities.Enqueue(sender);
AddValidEntities(grid1.GetInDir(coords, Direction.North));
@@ -85,7 +88,7 @@ namespace Robust.Client.GameObjects
}
// Entity is no longer valid, update around the last position it was at.
if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out var grid))
else if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out var grid))
{
var pos = ev.LastPosition.Value.pos;

View File

@@ -5,12 +5,14 @@ using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Serialization;
using static Robust.Shared.Containers.ContainerManagerComponent;
namespace Robust.Client.GameObjects
{
public class ContainerSystem : SharedContainerSystem
public sealed class ContainerSystem : SharedContainerSystem
{
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
@@ -59,7 +61,7 @@ namespace Robust.Client.GameObjects
{
if (!cast.ContainerSet.Any(data => data.Id == id))
{
container.EmptyContainer(true);
container.EmptyContainer(true, entMan: EntityManager);
container.Shutdown();
toDelete ??= new List<string>();
toDelete.Add(id);
@@ -131,12 +133,51 @@ namespace Robust.Client.GameObjects
continue;
}
// 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
// insert operation. This means it will then be reset to the shadow realm, causing it to be ejected
// from the container. It would then subsequently be parented to the container without ever being
// re-inserted, leading to the client seeing what should be hidden entities attached to
// containers/players.
if (Transform(entity).MapID == MapId.Nullspace)
{
AddExpectedEntity(entity, container);
continue;
}
if (!container.ContainedEntities.Contains(entity))
container.Insert(entity);
}
}
}
protected override void HandleParentChanged(ref EntParentChangedMessage message)
{
base.HandleParentChanged(ref message);
// If an entity warped in from null-space (i.e., re-entered PVS) and got attached to a container, do the same checks as for newly initialized entities.
if (message.OldParent != null && message.OldParent.Value.IsValid())
return;
if (!ExpectedEntities.TryGetValue(message.Entity, out var container))
return;
if (Transform(message.Entity).ParentUid != container.Owner)
{
// This container is expecting an entity... but it got parented to some other entity???
// Ah well, the sever should send a new container state that updates expected entities so just ignore it for now.
return;
}
RemoveExpectedEntity(message.Entity);
if (container.Deleted)
return;
container.Insert(message.Entity);
}
private IContainer ContainerFactory(ContainerManagerComponent component, string containerType, string id)
{
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
@@ -169,68 +210,101 @@ namespace Robust.Client.GameObjects
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 (EntityManager.Deleted(toUpdate))
{
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)
{
// TODO: Since we are recursing down,
// we could cache ShowContents data here to speed it up for children.
// Am lazy though.
UpdateEntity(entity);
// Recursively go up parents and containers to see whether both sprites and lights need to be occluded
// Could maybe optimise this more by checking nearest parent that has sprite / light and whether it's container
// occluded but this probably isn't a big perf issue.
var xform = xformQuery.GetComponent(entity);
var parent = xform.ParentUid;
var child = entity;
var spriteOccluded = false;
var lightOccluded = false;
foreach (var child in EntityManager.GetComponent<TransformComponent>(entity).Children)
while (parent.IsValid() && !spriteOccluded && !lightOccluded)
{
UpdateEntityRecursively(child.Owner);
var parentXform = xformQuery.GetComponent(parent);
if (TryComp<ContainerManagerComponent>(parent, out var manager) && manager.TryGetContainer(child, out var container))
{
spriteOccluded = spriteOccluded || !container.ShowContents;
lightOccluded = lightOccluded || container.OccludesLight;
}
child = parent;
parent = parentXform.ParentUid;
}
// Alright so
// This is the CBT bit.
// The issue is we need to go through the children and re-check whether they are or are not contained.
// if they are contained then the occlusion values may need updating for all those children
UpdateEntity(entity, xform, xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
}
private void UpdateEntity(EntityUid entity)
private void UpdateEntity(
EntityUid entity,
TransformComponent xform,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery,
bool spriteOccluded,
bool lightOccluded)
{
if (EntityManager.TryGetComponent(entity, out SpriteComponent? sprite))
if (spriteQuery.TryGetComponent(entity, out var sprite))
{
sprite.ContainerOccluded = false;
// We have to recursively scan for containers upwards in case of nested containers.
var tempParent = entity;
while (tempParent.TryGetContainer(out var container))
{
if (!container.ShowContents)
{
sprite.ContainerOccluded = true;
break;
}
tempParent = container.Owner;
}
sprite.ContainerOccluded = spriteOccluded;
}
if (EntityManager.TryGetComponent(entity, out PointLightComponent? light))
if (pointQuery.TryGetComponent(entity, out var light))
{
light.ContainerOccluded = false;
light.ContainerOccluded = lightOccluded;
}
// We have to recursively scan for containers upwards in case of nested containers.
var tempParent = entity;
while (tempParent.TryGetContainer(out var container))
var childEnumerator = xform.ChildEnumerator;
// Try to avoid TryComp if we already know stuff is occluded.
if ((!spriteOccluded || !lightOccluded) && TryComp<ContainerManagerComponent>(entity, out var manager))
{
while (childEnumerator.MoveNext(out var child))
{
if (container.OccludesLight)
// Thank god it's by value and not by ref.
var childSpriteOccluded = spriteOccluded;
var childLightOccluded = lightOccluded;
// We already know either sprite or light is not occluding so need to check container.
if (manager.TryGetContainer(child.Value, out var container))
{
light.ContainerOccluded = true;
break;
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
childLightOccluded = childLightOccluded || container.OccludesLight;
}
tempParent = container.Owner;
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, childSpriteOccluded, childLightOccluded);
}
}
else
{
while (childEnumerator.MoveNext(out var child))
{
UpdateEntity(child.Value, xformQuery.GetComponent(child.Value), xformQuery, pointQuery, spriteQuery, spriteOccluded, lightOccluded);
}
}
}

View File

@@ -25,7 +25,7 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
_lightOverlay = new DebugLightOverlay(
IoCManager.Resolve<IEntityLookup>(),
EntitySystem.Get<EntityLookupSystem>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>(),
Get<RenderingTreeSystem>());
@@ -44,7 +44,7 @@ namespace Robust.Client.GameObjects
private sealed class DebugLightOverlay : Overlay
{
private IEntityLookup _lookup;
private EntityLookupSystem _lookup;
private IEyeManager _eyeManager;
private IMapManager _mapManager;
@@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
public DebugLightOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
{
_lookup = lookup;
_eyeManager = eyeManager;
@@ -72,7 +72,7 @@ namespace Robust.Client.GameObjects
{
foreach (var light in tree.LightTree)
{
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
var aabb = _lookup.GetWorldAABB(light.Owner);
if (!aabb.Intersects(worldBounds)) continue;
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));

View File

@@ -15,7 +15,7 @@ using Robust.Shared.Enums;
namespace Robust.Client.GameObjects
{
public class EffectSystem : EntitySystem
public sealed class EffectSystem : EntitySystem
{
[Dependency] private readonly IGameTiming gameTiming = default!;
[Dependency] private readonly IResourceCache resourceCache = default!;
@@ -94,7 +94,7 @@ namespace Robust.Client.GameObjects
}
}
private class Effect
private sealed class Effect
{
/// <summary>
/// Effect Sprite
@@ -323,7 +323,7 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
private readonly ShaderInstance _unshadedShader;
private readonly EffectSystem _owner;

View File

@@ -20,7 +20,7 @@ namespace Robust.Client.GameObjects
/// Updates the position of every Eye every frame, so that the camera follows the player around.
/// </summary>
[UsedImplicitly]
public class EyeUpdateSystem : EntitySystem
public sealed class EyeUpdateSystem : EntitySystem
{
/// <inheritdoc />
public override void Initialize()

View File

@@ -10,7 +10,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
public class GridChunkBoundsDebugSystem : EntitySystem
public sealed class GridChunkBoundsDebugSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;

View File

@@ -4,11 +4,15 @@ using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
@@ -16,11 +20,13 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Client-side processing of all input commands through the simulation.
/// </summary>
public class InputSystem : SharedInputSystem
public sealed class InputSystem : SharedInputSystem
{
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientGameStateManager _stateManager = default!;
[Dependency] private readonly IConsoleHost _conHost = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private readonly IPlayerCommandStates _cmdStates = new PlayerCommandStates();
@@ -108,6 +114,43 @@ namespace Robust.Client.GameObjects
public override void Initialize()
{
SubscribeLocalEvent<PlayerAttachSysMessage>(OnAttachedEntityChanged);
_conHost.RegisterCommand("incmd",
"Inserts an input command into the simulation",
"incmd <KeyFunction> <d|u KeyState> <wxPos> <wyPos>",
GenerateInputCommand);
}
public override void Shutdown()
{
base.Shutdown();
_conHost.UnregisterCommand("incmd");
}
private void GenerateInputCommand(IConsoleShell shell, string argstr, string[] args)
{
var localPlayer = _playerManager.LocalPlayer;
if(localPlayer is null)
return;
var pent = localPlayer.ControlledEntity;
if(pent is null)
return;
BoundKeyFunction keyFunction = new BoundKeyFunction(args[0]);
BoundKeyState state = args[1] == "u" ? BoundKeyState.Up: BoundKeyState.Down;
var pxform = Transform(pent.Value);
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
var coords = EntityCoordinates.FromMap(EntityManager, pent.Value, new MapCoordinates(wPos, pxform.MapID));
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
var message = new FullInputCmdMessage(_timing.CurTick, _timing.TickFraction, funcId, state,
coords, new ScreenCoordinates(0, 0, default), EntityUid.Invalid);
HandleInputCommand(localPlayer.Session, keyFunction, message);
}
private void OnAttachedEntityChanged(PlayerAttachSysMessage message)
@@ -163,7 +206,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Entity system message that is raised when the player changes attached entities.
/// </summary>
public class PlayerAttachSysMessage : EntityEventArgs
public sealed class PlayerAttachSysMessage : EntityEventArgs
{
/// <summary>
/// New entity the player is attached to.
@@ -180,7 +223,7 @@ namespace Robust.Client.GameObjects
}
}
public class PlayerAttachedEvent : EntityEventArgs
public sealed class PlayerAttachedEvent : EntityEventArgs
{
public PlayerAttachedEvent(EntityUid entity)
{
@@ -190,7 +233,7 @@ namespace Robust.Client.GameObjects
public EntityUid Entity { get; }
}
public class PlayerDetachedEvent : EntityEventArgs
public sealed class PlayerDetachedEvent : EntityEventArgs
{
public PlayerDetachedEvent(EntityUid entity)
{

View File

@@ -4,7 +4,7 @@ using Robust.Shared.IoC;
namespace Robust.Client.GameObjects
{
public class MidiSystem : EntitySystem
public sealed class MidiSystem : EntitySystem
{
[Dependency] private readonly IMidiManager _midiManager = default!;

View File

@@ -113,10 +113,18 @@ namespace Robust.Client.GameObjects
private void AnythingMoved(ref MoveEvent args)
{
AnythingMovedSubHandler(args.Sender);
var pointQuery = EntityManager.GetEntityQuery<PointLightComponent>();
var spriteQuery = EntityManager.GetEntityQuery<SpriteComponent>();
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
AnythingMovedSubHandler(args.Sender, xformQuery, pointQuery, spriteQuery);
}
private void AnythingMovedSubHandler(EntityUid uid, TransformComponent? xform = null)
private void AnythingMovedSubHandler(
EntityUid uid,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<PointLightComponent> pointQuery,
EntityQuery<SpriteComponent> spriteQuery)
{
// To avoid doing redundant updates (and we don't need to update a grid's children ever)
if (!_checkedChildren.Add(uid) || EntityManager.HasComponent<RenderingTreeComponent>(uid)) return;
@@ -125,17 +133,19 @@ namespace Robust.Client.GameObjects
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
// (Struct-based events ok though)
// Ironically this was lagging the GC lolz
if (EntityManager.TryGetComponent(uid, out SpriteComponent? sprite))
if (spriteQuery.TryGetComponent(uid, out var sprite))
QueueSpriteUpdate(sprite);
if (EntityManager.TryGetComponent(uid, out PointLightComponent? light))
if (pointQuery.TryGetComponent(uid, out var light))
QueueLightUpdate(light);
if (!Resolve(uid, ref xform)) return;
if (!xformQuery.TryGetComponent(uid, out var xform)) return;
foreach (var child in xform.ChildEntities)
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
AnythingMovedSubHandler(child);
AnythingMovedSubHandler(child.Value, xformQuery, pointQuery, spriteQuery);
}
}
@@ -248,23 +258,21 @@ namespace Robust.Client.GameObjects
EntityManager.EnsureComponent<RenderingTreeComponent>(_mapManager.GetGrid(gridId).GridEntityId);
}
// TODO: Pass in TransformComponent directly: mainly interested in getting this shit working atm.
private RenderingTreeComponent? GetRenderTree(EntityUid entity)
private RenderingTreeComponent? GetRenderTree(EntityUid entity, EntityQuery<TransformComponent> xforms)
{
var lookups = EntityManager.GetEntityQuery<RenderingTreeComponent>();
if (!EntityManager.EntityExists(entity) ||
!EntityManager.TryGetComponent(entity, out TransformComponent? xform) ||
!xforms.TryGetComponent(entity, out var xform) ||
xform.MapID == MapId.Nullspace ||
EntityManager.HasComponent<RenderingTreeComponent>(entity)) return null;
lookups.HasComponent(entity)) return null;
var parent = xform.ParentUid;
while (true)
while (parent.IsValid())
{
if (!parent.IsValid())
break;
if (EntityManager.TryGetComponent(parent, out RenderingTreeComponent? comp)) return comp;
parent = EntityManager.GetComponent<TransformComponent>(parent).ParentUid;
if (lookups.TryGetComponent(parent, out var comp)) return comp;
parent = xforms.GetComponent(parent).ParentUid;
}
return null;
@@ -279,6 +287,8 @@ namespace Robust.Client.GameObjects
{
_checkedChildren.Clear();
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
foreach (var sprite in _spriteQueue)
{
sprite.TreeUpdateQueued = false;
@@ -289,9 +299,9 @@ namespace Robust.Client.GameObjects
}
var oldMapTree = sprite.RenderTree;
var newMapTree = GetRenderTree(sprite.Owner);
var newMapTree = GetRenderTree(sprite.Owner, xforms);
// TODO: Temp PVS guard
var xform = EntityManager.GetComponent<TransformComponent>(sprite.Owner);
var xform = xforms.GetComponent(sprite.Owner);
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
@@ -300,7 +310,7 @@ namespace Robust.Client.GameObjects
continue;
}
var aabb = SpriteAabbFunc(sprite, worldPos, worldRot);
var aabb = SpriteAabbFunc(sprite, worldPos, worldRot, xforms);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
@@ -327,9 +337,9 @@ namespace Robust.Client.GameObjects
}
var oldMapTree = light.RenderTree;
var newMapTree = GetRenderTree(light.Owner);
var newMapTree = GetRenderTree(light.Owner, xforms);
// TODO: Temp PVS guard
var worldPos = EntityManager.GetComponent<TransformComponent>(light.Owner).WorldPosition;
var worldPos = xforms.GetComponent(light.Owner).WorldPosition;
if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y))
{
@@ -344,7 +354,7 @@ namespace Robust.Client.GameObjects
Logger.WarningS(LoggerSawmill, $"Light radius for {light.Owner} set above max radius of {MaxLightRadius}. This may lead to pop-in.");
}
var aabb = LightAabbFunc(light, worldPos);
var aabb = LightAabbFunc(light, worldPos, xforms);
// If we're on a new map then clear the old one.
if (oldMapTree != newMapTree)
@@ -366,69 +376,40 @@ namespace Robust.Client.GameObjects
private Box2 SpriteAabbFunc(in SpriteComponent value)
{
var xform = EntityManager.GetComponent<TransformComponent>(value.Owner);
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
var xform = xforms.GetComponent(value.Owner);
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
var tree = GetRenderTree(value.Owner);
if (tree == null)
{
return bounds.CalcBoundingBox();
}
else
{
return EntityManager.GetComponent<TransformComponent>(tree.Owner).InvWorldMatrix.TransformBox(bounds);
}
return SpriteAabbFunc(value, worldPos, worldRot, xforms);
}
private Box2 LightAabbFunc(in PointLightComponent value)
{
var worldPos = EntityManager.GetComponent<TransformComponent>(value.Owner).WorldPosition;
var tree = GetRenderTree(value.Owner);
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
var worldPos = xforms.GetComponent(value.Owner).WorldPosition;
var tree = GetRenderTree(value.Owner, xforms);
var boxSize = value.Radius * 2;
Vector2 localPos;
if (tree == null)
{
localPos = worldPos;
}
else
{
// TODO: Need a way to just cache this InvWorldMatrix
localPos = EntityManager.GetComponent<TransformComponent>(tree.Owner).InvWorldMatrix.Transform(worldPos);
}
var localPos = tree == null ? worldPos : xforms.GetComponent(tree.Owner).InvWorldMatrix.Transform(worldPos);
return Box2.CenteredAround(localPos, (boxSize, boxSize));
}
private Box2 SpriteAabbFunc(SpriteComponent value, Vector2 worldPos, Angle worldRot)
private Box2 SpriteAabbFunc(SpriteComponent value, Vector2 worldPos, Angle worldRot, EntityQuery<TransformComponent> xforms)
{
var bounds = new Box2Rotated(value.CalculateBoundingBox(worldPos), worldRot, worldPos);
var tree = GetRenderTree(value.Owner);
var bounds = value.CalculateRotatedBoundingBox(worldPos, worldRot);
var tree = GetRenderTree(value.Owner, xforms);
if (tree == null)
{
return bounds.CalcBoundingBox();
}
else
{
return EntityManager.GetComponent<TransformComponent>(tree.Owner).InvWorldMatrix.TransformBox(bounds);
}
return tree == null ? bounds.CalcBoundingBox() : xforms.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(bounds);
}
private Box2 LightAabbFunc(PointLightComponent value, Vector2 worldPos)
private Box2 LightAabbFunc(PointLightComponent value, Vector2 worldPos, EntityQuery<TransformComponent> xforms)
{
// Lights are circles so don't need entity's rotation
var tree = GetRenderTree(value.Owner);
var tree = GetRenderTree(value.Owner, xforms);
var boxSize = value.Radius * 2;
Vector2 localPos;
if (tree == null)
{
localPos = worldPos;
} else
{
localPos = EntityManager.GetComponent<TransformComponent>(tree.Owner).InvWorldMatrix.Transform(worldPos);
}
var localPos = tree == null ? worldPos : xforms.GetComponent(tree.Owner).InvWorldMatrix.Transform(worldPos);
return Box2.CenteredAround(localPos, (boxSize, boxSize));
}
}

View File

@@ -0,0 +1,84 @@
using System;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects;
public sealed partial class SpriteSystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Pure]
public Texture Frame0(SpriteSpecifier specifier)
{
return RsiStateLike(specifier).Default;
}
[Pure]
public IRsiStateLike RsiStateLike(SpriteSpecifier specifier)
{
switch (specifier)
{
case SpriteSpecifier.Texture tex:
return tex.GetTexture(_resourceCache);
case SpriteSpecifier.Rsi rsi:
return GetState(rsi);
case SpriteSpecifier.EntityPrototype prototypeIcon:
if (!_proto.TryIndex<EntityPrototype>(prototypeIcon.EntityPrototypeId, out var prototype))
{
Logger.Error("Failed to load PrototypeIcon {0}", prototypeIcon.EntityPrototypeId);
return SpriteComponent.GetFallbackState(_resourceCache);
}
return SpriteComponent.GetPrototypeIcon(prototype, _resourceCache);
default:
throw new NotSupportedException();
}
}
[Pure]
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
var icon = IconComponent.GetPrototypeIcon(prototype, _resourceCache);
if (icon != null) return icon;
if (!prototype.Components.ContainsKey("Sprite"))
{
return SpriteComponent.GetFallbackState(resourceCache);
}
var dummy = Spawn(prototype.ID, MapCoordinates.Nullspace);
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
var result = spriteComponent.Icon ?? SpriteComponent.GetFallbackState(resourceCache);
Del(dummy);
return result;
}
[Pure]
public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier)
{
if (_resourceCache.TryGetResource<RSIResource>(
SharedSpriteComponent.TextureRoot / rsiSpecifier.RsiPath,
out var theRsi) &&
theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
{
return state;
}
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);
return SpriteComponent.GetFallbackState(_resourceCache);
}
}

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects
/// Updates the layer animation for every visible sprite.
/// </summary>
[UsedImplicitly]
public class SpriteSystem : EntitySystem
public sealed partial class SpriteSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly RenderingTreeSystem _treeSystem = default!;
@@ -53,9 +53,11 @@ namespace Robust.Client.GameObjects
return;
}
var xforms = EntityManager.GetEntityQuery<TransformComponent>();
foreach (var comp in _treeSystem.GetRenderTrees(currentMap, pvsBounds))
{
var bounds = EntityManager.GetComponent<TransformComponent>(comp.Owner).InvWorldMatrix.TransformBox(pvsBounds);
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(pvsBounds);
comp.SpriteTree.QueryAabb(ref frameTime, (ref float state, in SpriteComponent value) =>
{

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
public class VelocityDebugSystem : EntitySystem
public sealed class VelocityDebugSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;

View File

@@ -0,0 +1,20 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects;
/// <summary>
/// An abstract entity system inheritor for systems that deal with
/// appearance data, replacing <see cref="AppearanceVisualizer"/>.
/// </summary>
public abstract class VisualizerSystem<T> : EntitySystem
where T: Component
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<T, AppearanceChangeEvent>(OnAppearanceChange);
}
protected virtual void OnAppearanceChange(EntityUid uid, T component, ref AppearanceChangeEvent args) {}
}

View File

@@ -7,7 +7,6 @@ using System.Linq;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.Player;
using Robust.Client.Timing;
using Robust.Shared;
@@ -29,7 +28,7 @@ namespace Robust.Client.GameStates
{
/// <inheritdoc />
[UsedImplicitly]
public class ClientGameStateManager : IClientGameStateManager
public sealed class ClientGameStateManager : IClientGameStateManager
{
private GameStateProcessor _processor = default!;
@@ -46,11 +45,10 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly IClientNetManager _network = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClientMapManager _mapManager = default!;
[Dependency] private readonly INetworkedMapManager _mapManager = default!;
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly INetConfigurationManager _config = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
@@ -60,6 +58,8 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
#endif
private ISawmill _sawmill = default!;
/// <inheritdoc />
public int MinBufferSize => _processor.MinBufferSize;
@@ -87,6 +87,7 @@ namespace Robust.Client.GameStates
/// <inheritdoc />
public void Initialize()
{
_sawmill = Logger.GetSawmill(CVars.NetPredict.Name);
_processor = new GameStateProcessor(_timing);
_network.RegisterNetMessage<MsgState>(HandleStateMessage);
@@ -144,7 +145,7 @@ namespace Robust.Client.GameStates
_pendingInputs.Enqueue(message);
_inputManager.NetworkBindMap.TryGetKeyFunction(message.InputFunctionId, out var boundFunc);
Logger.DebugS(CVars.NetPredict.Name,
_sawmill.Debug(
$"CL> SENT tick={_timing.CurTick}, sub={_timing.TickFraction}, seq={_nextInputCmdSeq}, func={boundFunc.FunctionName}, state={message.State}");
_nextInputCmdSeq++;
}
@@ -238,7 +239,7 @@ namespace Robust.Client.GameStates
if (_lastProcessedSeq < curState.LastProcessedInput)
{
Logger.DebugS(CVars.NetPredict.Name, $"SV> RCV tick={_timing.CurTick}, seq={_lastProcessedSeq}");
_sawmill.Debug($"SV> RCV tick={_timing.CurTick}, seq={_lastProcessedSeq}");
_lastProcessedSeq = curState.LastProcessedInput;
}
}
@@ -257,8 +258,7 @@ namespace Robust.Client.GameStates
var inCmd = _pendingInputs.Dequeue();
_inputManager.NetworkBindMap.TryGetKeyFunction(inCmd.InputFunctionId, out var boundFunc);
Logger.DebugS(CVars.NetPredict.Name,
$"SV> seq={inCmd.InputSequence}, func={boundFunc.FunctionName}, state={inCmd.State}");
_sawmill.Debug($"SV> seq={inCmd.InputSequence}, func={boundFunc.FunctionName}, state={inCmd.State}");
}
while (_pendingSystemMessages.Count > 0 && _pendingSystemMessages.Peek().sequence <= _lastProcessedSeq)
@@ -274,7 +274,7 @@ namespace Robust.Client.GameStates
if (_pendingInputs.Count > 0)
{
Logger.DebugS(CVars.NetPredict.Name, "CL> Predicted:");
_sawmill.Debug("CL> Predicted:");
}
var pendingInputEnumerator = _pendingInputs.GetEnumerator();
@@ -299,7 +299,7 @@ namespace Robust.Client.GameStates
_inputManager.NetworkBindMap.TryGetKeyFunction(inputCmd.InputFunctionId, out var boundFunc);
Logger.DebugS(CVars.NetPredict.Name,
_sawmill.Debug(
$" seq={inputCmd.InputSequence}, sub={inputCmd.SubTick}, dTick={tick}, func={boundFunc.FunctionName}, " +
$"state={inputCmd.State}");
@@ -331,8 +331,6 @@ namespace Robust.Client.GameStates
}
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
_lookup.Update();
}
private void ResetPredictedEntities(GameTick curTick)
@@ -347,7 +345,11 @@ namespace Robust.Client.GameStates
continue;
}
Logger.DebugS(CVars.NetPredict.Name, $"Entity {entity} was made dirty.");
// Check log level first to avoid the string alloc.
if (_sawmill.Level <= LogLevel.Debug)
{
_sawmill.Debug($"Entity {entity} was made dirty.");
}
if (!_processor.TryGetLastServerStates(entity, out var last))
{
@@ -365,7 +367,7 @@ namespace Robust.Client.GameStates
continue;
}
Logger.DebugS(CVars.NetPredict.Name, $" And also its component {comp.GetType()}");
_sawmill.Debug($" And also its component {comp.GetType()}");
// TODO: Handle interpolation.
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
@@ -632,7 +634,7 @@ namespace Robust.Client.GameStates
}
}
public class GameStateAppliedArgs : EventArgs
public sealed class GameStateAppliedArgs : EventArgs
{
public GameState AppliedState { get; }

View File

@@ -10,7 +10,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.GameStates
{
/// <inheritdoc />
internal class GameStateProcessor : IGameStateProcessor
internal sealed class GameStateProcessor : IGameStateProcessor
{
private readonly IGameTiming _timing;

View File

@@ -19,7 +19,7 @@ namespace Robust.Client.GameStates
/// A network entity report that lists all entities as they are updated through game states.
/// https://developer.valvesoftware.com/wiki/Networking_Entities#cl_entityreport
/// </summary>
class NetEntityOverlay : Overlay
sealed class NetEntityOverlay : Overlay
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;
@@ -244,7 +244,7 @@ namespace Robust.Client.GameStates
}
}
private class NetEntityReportCommand : IConsoleCommand
private sealed class NetEntityReportCommand : IConsoleCommand
{
public string Command => "net_entityreport";
public string Help => "net_entityreport <0|1>";

View File

@@ -16,7 +16,7 @@ namespace Robust.Client.GameStates
/// <summary>
/// Visual debug overlay for the network diagnostic graph.
/// </summary>
internal class NetGraphOverlay : Overlay
internal sealed class NetGraphOverlay : Overlay
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;
@@ -239,7 +239,7 @@ namespace Robust.Client.GameStates
base.DisposeBehavior();
}
private class NetShowGraphCommand : IConsoleCommand
private sealed class NetShowGraphCommand : IConsoleCommand
{
public string Command => "net_graph";
public string Help => "net_graph <0|1>";
@@ -275,7 +275,7 @@ namespace Robust.Client.GameStates
}
}
private class NetWatchEntCommand : IConsoleCommand
private sealed class NetWatchEntCommand : IConsoleCommand
{
public string Command => "net_watchent";
public string Help => "net_watchent <0|EntityUid>";

View File

@@ -11,7 +11,7 @@ using Robust.Shared.Timing;
namespace Robust.Client.GameStates
{
internal class NetInterpOverlay : Overlay
internal sealed class NetInterpOverlay : Overlay
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
@@ -38,7 +38,7 @@ namespace Robust.Client.GameStates
var transform = _entityManager.GetComponent<TransformComponent>(boundingBox.Owner);
// if not on the same map, continue
if (transform.MapID != _eyeManager.CurrentMap || boundingBox.Owner.IsInContainer())
if (transform.MapID != _eyeManager.CurrentMap || boundingBox.Owner.IsInContainer(_entityManager))
continue;
// This entity isn't lerping, no need to draw debug info for it
@@ -65,7 +65,7 @@ namespace Robust.Client.GameStates
}
}
private class NetShowInterpCommand : IConsoleCommand
private sealed class NetShowInterpCommand : IConsoleCommand
{
public string Command => "net_draw_interp";
public string Help => "net_draw_interp <0|1>";

View File

@@ -18,7 +18,7 @@ using Vector2 = Robust.Shared.Maths.Vector2;
namespace Robust.Client.Graphics.Audio
{
internal partial class ClydeAudio : IClydeAudio, IClydeAudioInternal
internal sealed partial class ClydeAudio : IClydeAudio, IClydeAudioInternal
{
private ALDevice _openALDevice;
private ALContext _openALContext;
@@ -224,7 +224,7 @@ namespace Robust.Client.Graphics.Audio
_openALSawmill.Error("Failed to generate source. Too many simultaneous audio streams?");
return null;
}
// ReSharper disable once PossibleInvalidOperationException
// TODO: This really shouldn't be indexing based on the ClydeHandle...
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[(int) stream.ClydeHandle!.Value.Value].BufferHandle);

View File

@@ -18,6 +18,7 @@ namespace Robust.Client.Graphics.Audio
/// <summary>
/// Hey look, it's ClydeAudio.AudioSource's evil twin brother!
/// </summary>
[Virtual]
internal class DummyAudioSource : IClydeAudioSource
{
public static DummyAudioSource Instance { get; } = new();

View File

@@ -7,6 +7,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.Graphics
{
/// <inheritdoc />
[Virtual]
public class Eye : IEye
{
private Vector2 _scale = Vector2.One/2f;

View File

@@ -153,7 +153,7 @@ namespace Robust.Client.Graphics
}
}
public class CurrentEyeChangedEvent : EntityEventArgs
public sealed class CurrentEyeChangedEvent : EntityEventArgs
{
public IEye? Old { get; }
public IEye New { get; }

View File

@@ -3,7 +3,7 @@
/// <summary>
/// A fixed eye is an eye which is fixed to one point, its position.
/// </summary>
public class FixedEye : Eye
public sealed class FixedEye : Eye
{
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
@@ -18,8 +18,8 @@ namespace Robust.Client.Graphics.Clyde
private readonly Dictionary<GridId, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
new();
private int _verticesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
private int _indicesPerChunk(IMapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye)
{
@@ -78,7 +78,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void _updateChunkMesh(IMapGrid grid, IMapChunk chunk)
private void _updateChunkMesh(IMapGrid grid, MapChunk chunk)
{
var data = _mapChunkData[grid.Index];
@@ -91,25 +91,36 @@ namespace Robust.Client.Graphics.Clyde
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
var i = 0;
foreach (var tile in chunk)
var cSz = grid.ChunkSize;
var cScaled = chunk.Indices * cSz;
for (ushort x = 0; x < cSz; x++)
{
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.Tile);
if (regionMaybe == null)
for (ushort y = 0; y < cSz; y++)
{
continue;
var tile = chunk.GetTile(x, y);
if (tile.IsEmpty)
continue;
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile.TypeId);
if (regionMaybe == null)
{
continue;
}
var region = regionMaybe.Value;
var gx = x + cScaled.X;
var gy = y + cScaled.Y;
var vIdx = i * 4;
vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, region.Left, region.Bottom);
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, region.Right, region.Bottom);
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, region.Right, region.Top);
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, region.Left, region.Top);
var nIdx = i * GetQuadBatchIndexCount();
var tIdx = (ushort)(i * 4);
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
i += 1;
}
var region = regionMaybe.Value;
var vIdx = i * 4;
vertexBuffer[vIdx + 0] = new Vertex2D(tile.X, tile.Y, region.Left, region.Bottom);
vertexBuffer[vIdx + 1] = new Vertex2D(tile.X + 1, tile.Y, region.Right, region.Bottom);
vertexBuffer[vIdx + 2] = new Vertex2D(tile.X + 1, tile.Y + 1, region.Right, region.Top);
vertexBuffer[vIdx + 3] = new Vertex2D(tile.X, tile.Y + 1, region.Left, region.Top);
var nIdx = i * GetQuadBatchIndexCount();
var tIdx = (ushort) (i * 4);
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
i += 1;
}
GL.BindVertexArray(datum.VAO);
@@ -122,7 +133,7 @@ namespace Robust.Client.Graphics.Clyde
datum.TileCount = i;
}
private MapChunkData _initChunkBuffers(IMapGrid grid, IMapChunk chunk)
private MapChunkData _initChunkBuffers(IMapGrid grid, MapChunk chunk)
{
var vao = GenVertexArray();
BindVertexArray(vao);
@@ -159,7 +170,7 @@ namespace Robust.Client.Graphics.Clyde
return datum;
}
private bool _isChunkDirty(IMapGrid grid, IMapChunk chunk)
private bool _isChunkDirty(IMapGrid grid, MapChunk chunk)
{
var data = _mapChunkData[grid.Index];
return !data.TryGetValue(chunk.Indices, out var datum) || datum.Dirty;
@@ -214,7 +225,7 @@ namespace Robust.Client.Graphics.Clyde
_mapChunkData.Remove(gridId);
}
private class MapChunkData
private sealed class MapChunkData
{
public bool Dirty;
public readonly uint VAO;

View File

@@ -24,11 +24,17 @@ namespace Robust.Client.Graphics.Clyde
{
public ClydeDebugLayers DebugLayers { get; set; }
private readonly RefList<(SpriteComponent sprite, Matrix3 worldMatrix, Angle worldRotation, float yWorldPos)>
private readonly RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRotation, Box2 spriteScreenBB)>
_drawingSpriteList
=
new();
// TODO allow this scale to be passed with PostShader as variable
/// <summary>
/// Some shaders that enlarge the final sprite, like emission or highlight effects, need to use a slightly larger render target.
/// </summary>
public static float PostShadeScale = 1.25f;
public void Render()
{
CheckTransferringScreenshots();
@@ -217,9 +223,8 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
var screenSize = viewport.Size;
eye.GetViewMatrix(out var eyeMatrix, eye.Scale);
ProcessSpriteEntities(mapId, eyeMatrix, worldBounds, _drawingSpriteList);
ProcessSpriteEntities(mapId, viewport, eye, worldBounds, _drawingSpriteList);
var worldOverlays = new List<Overlay>();
@@ -276,30 +281,12 @@ namespace Robust.Client.Graphics.Clyde
break;
}
var matrix = entry.worldMatrix;
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
RenderTexture? entityPostRenderTarget = null;
Vector2i roundedPos = default;
if (entry.sprite.PostShader != null)
{
// calculate world bounding box
var spriteBB = entry.sprite.CalculateBoundingBox(worldPosition);
var spriteLB = spriteBB.BottomLeft;
var spriteRT = spriteBB.TopRight;
// finally we can calculate screen bounding in pixels
var screenLB = viewport.WorldToLocal(spriteLB);
var screenRT = viewport.WorldToLocal(spriteRT);
// we need to scale RT a for effects like emission or highlight
// scale can be passed with PostShader as variable in future
var postShadeScale = 1.25f;
var screenSpriteSize = (Vector2i) ((screenRT - screenLB) * postShadeScale).Rounded();
// Rotate the vector by the eye angle, otherwise the bounding box will be incorrect
screenSpriteSize = (Vector2i) eye.Rotation.RotateVec(screenSpriteSize).Rounded();
screenSpriteSize.Y = -screenSpriteSize.Y;
// get the size of the sprite on screen, scaled slightly to allow for shaders that increase the final sprite size.
var screenSpriteSize = (Vector2i) (entry.spriteScreenBB.Size * PostShadeScale).Rounded();
// I'm not 100% sure why it works, but without it post-shader
// can be lower or upper by 1px than original sprite depending on sprite rotation or scale
@@ -322,16 +309,14 @@ namespace Robust.Client.Graphics.Clyde
// Calculate viewport so that the entity thinks it's drawing to the same position,
// which is necessary for light application,
// but it's ACTUALLY drawing into the center of the render target.
var spritePos = spriteBB.Center;
var screenPos = viewport.WorldToLocal(spritePos);
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
roundedPos = (Vector2i) entry.spriteScreenBB.Center;
var flippedPos = new Vector2i(roundedPos.X, screenSize.Y - roundedPos.Y);
flippedPos -= entityPostRenderTarget.Size / 2;
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
}
}
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in worldPosition);
entry.sprite.Render(_renderHandle.DrawingHandleWorld, eye.Rotation, in entry.worldRotation, in entry.worldPos);
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
{
@@ -369,28 +354,37 @@ namespace Robust.Client.Graphics.Clyde
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void ProcessSpriteEntities(MapId map, Matrix3 eyeMatrix, Box2Rotated worldBounds,
RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> list)
private void ProcessSpriteEntities(MapId map, Viewport view, IEye eye, Box2Rotated worldBounds,
RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> list)
{
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
// Construct a matrix equivalent for Viewport.WorldToLocal()
eye.GetViewMatrix(out var viewMatrix, view.RenderScale);
var uiProjmatrix = Matrix3.Identity;
uiProjmatrix.R0C0 = EyeManager.PixelsPerMeter;
uiProjmatrix.R1C1 = -EyeManager.PixelsPerMeter;
uiProjmatrix.R0C2 = view.Size.X / 2f;
uiProjmatrix.R1C2 = view.Size.Y / 2f;
var worldToLocal = viewMatrix * uiProjmatrix;
foreach (var comp in _entitySystemManager.GetEntitySystem<RenderingTreeSystem>().GetRenderTrees(map, worldBounds))
{
var bounds = _entityManager.GetComponent<TransformComponent>(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
comp.SpriteTree.QueryAabb(ref list, (
ref RefList<(SpriteComponent sprite, Matrix3 matrix, Angle worldRot, float yWorldPos)> state,
ref RefList<(SpriteComponent sprite, Vector2 worldPos, Angle worldRot, Box2 spriteScreenBB)> state,
in SpriteComponent value) =>
{
var entity = value.Owner;
var transform = _entityManager.GetComponent<TransformComponent>(entity);
var transform = xforms.GetComponent(entity);
ref var entry = ref state.AllocAdd();
entry.sprite = value;
entry.worldRot = transform.WorldRotation;
entry.matrix = transform.WorldMatrix;
var eyePos = eyeMatrix.Transform(new Vector2(entry.matrix.R0C2, entry.matrix.R1C2));
// Didn't use the bounds from the query as that has to be re-calculated (and is probably more expensive than this).
var bounds = value.CalculateBoundingBox(eyePos);
entry.yWorldPos = eyePos.Y - bounds.Extents.Y;
(entry.worldPos, entry.worldRot) = transform.GetWorldPositionRotation();
var spriteWorldBB = value.CalculateRotatedBoundingBox(entry.worldPos, entry.worldRot, eye);
entry.spriteScreenBB = worldToLocal.TransformBox(spriteWorldBB);
return true;
}, bounds, true);

View File

@@ -62,7 +62,7 @@ namespace Robust.Client.Graphics.Clyde
PixelInternalFormat.Srgb8Alpha8 => 4,
PixelInternalFormat.R11fG11fB10f => 4,
PixelInternalFormat.R32f => 4,
PixelInternalFormat.Rg32f => 4,
PixelInternalFormat.Rg32f => 8,
PixelInternalFormat.R8 => 1,
_ => 0
};

View File

@@ -514,13 +514,14 @@ namespace Robust.Client.Graphics.Clyde
var enlargedBounds = worldAABB.Enlarged(renderingTreeSystem.MaxLightRadius);
// Use worldbounds for this one as we only care if the light intersects our actual bounds
var state = (this, worldAABB, count: 0);
var state = (this, worldAABB, count: 0, shadowCastingCount: 0);
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var comp in renderingTreeSystem.GetRenderTrees(map, enlargedBounds))
{
var bounds = _entityManager.GetComponent<TransformComponent>(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
var bounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(worldBounds);
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldAABB, int count) state, in PointLightComponent light) =>
comp.LightTree.QueryAabb(ref state, (ref (Clyde clyde, Box2 worldAABB, int count, int shadowCastingCount) state, in PointLightComponent light) =>
{
if (state.count >= LightsToRenderListSize)
{
@@ -528,7 +529,7 @@ namespace Robust.Client.Graphics.Clyde
return false;
}
var transform = _entityManager.GetComponent<TransformComponent>(light.Owner);
var transform = xforms.GetComponent(light.Owner);
if (float.IsNaN(transform.LocalPosition.X) || float.IsNaN(transform.LocalPosition.Y)) return true;
@@ -542,6 +543,9 @@ namespace Robust.Client.Graphics.Clyde
return true;
}
// If the light is a shadow casting light, keep a separate track of that
if (light.CastShadows) state.shadowCastingCount++;
float distanceSquared = (state.worldAABB.Center - lightPos).LengthSquared;
state.clyde._lightsToRenderList[state.count++] = (light, lightPos, distanceSquared);
@@ -549,17 +553,29 @@ namespace Robust.Client.Graphics.Clyde
}, bounds);
}
if (state.count > _maxLightsPerScene)
if (state.shadowCastingCount > _maxLightsPerScene)
{
// There are too many lights to fit in the scene.
// There are too many lights casting shadows to fit in the scene.
// This check must occur before occluder expansion, or else bad things happen.
// Sort lights by distance.
// First, partition the array based on whether the lights are shadow casting or not
// (non shadow casting lights should be the first partition, shadow casting lights the second)
Array.Sort(_lightsToRenderList, 0, state.count, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
{
if (x.light.CastShadows && !y.light.CastShadows) return 1;
else if (!x.light.CastShadows && y.light.CastShadows) return -1;
else return 0;
}));
// Next, sort just the shadow casting lights by distance.
Array.Sort(_lightsToRenderList, state.count - state.shadowCastingCount, state.shadowCastingCount, Comparer<(PointLightComponent light, Vector2 pos, float distanceSquared)>.Create((x, y) =>
{
return x.distanceSquared.CompareTo(y.distanceSquared);
}));
// Then effectively delete the furthest lights.
state.count = _maxLightsPerScene;
// Then effectively delete the furthest lights, by setting the end of the array to exclude N
// number of shadow casting lights (where N is the number above the max number per scene.)
state.count -= state.shadowCastingCount - _maxLightsPerScene;
}
// When culling occluders later, we can't just remove any occluders outside the worldBounds.
@@ -871,21 +887,24 @@ namespace Robust.Client.Graphics.Clyde
var ii = 0;
var imi = 0;
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var comp in occluderSystem.GetOccluderTrees(map, expandedBounds))
{
var treeBounds = _entityManager.GetComponent<TransformComponent>(comp.Owner).InvWorldMatrix.TransformBox(expandedBounds);
var treeBounds = xforms.GetComponent(comp.Owner).InvWorldMatrix.TransformBox(expandedBounds);
comp.Tree.QueryAabb((in OccluderComponent sOccluder) =>
{
var occluder = (ClientOccluderComponent)sOccluder;
var transform = _entityManager.GetComponent<TransformComponent>(occluder.Owner);
if (!occluder.Enabled)
var transform = xforms.GetComponent(sOccluder.Owner);
if (!sOccluder.Enabled)
{
return true;
}
var occluder = (ClientOccluderComponent)sOccluder;
var worldTransform = transform.WorldMatrix;
var box = occluder.BoundingBox;
var box = sOccluder.BoundingBox;
var tl = worldTransform.Transform(box.TopLeft);
var tr = worldTransform.Transform(box.TopRight);

View File

@@ -994,9 +994,9 @@ namespace Robust.Client.Graphics.Clyde
private sealed class SpriteDrawingOrderComparer : IComparer<int>
{
private readonly RefList<(SpriteComponent, Matrix3, Angle, float)> _drawList;
private readonly RefList<(SpriteComponent, Vector2, Angle, Box2)> _drawList;
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Matrix3, Angle, float)> drawList)
public SpriteDrawingOrderComparer(RefList<(SpriteComponent, Vector2, Angle, Box2)> drawList)
{
_drawList = drawList;
}
@@ -1019,7 +1019,8 @@ namespace Robust.Client.Graphics.Clyde
return cmp;
}
cmp = _drawList[y].Item4.CompareTo(_drawList[x].Item4);
// compare the top of the sprite's BB for y-sorting. Because screen coordinates are flipped, the "top" of the BB is actually the "bottom".
cmp = _drawList[x].Item4.Top.CompareTo(_drawList[y].Item4.Top);
if (cmp != 0)
{

View File

@@ -34,7 +34,7 @@ namespace Robust.Client.Graphics.Clyde
private readonly ConcurrentQueue<ClydeHandle> _deadShaderInstances = new();
private class LoadedShader
private sealed class LoadedShader
{
public GLShaderProgram Program = default!;
public bool HasLighting = true;
@@ -42,7 +42,7 @@ namespace Robust.Client.Graphics.Clyde
public string? Name;
}
private class LoadedShaderInstance
private sealed class LoadedShaderInstance
{
public ClydeHandle ShaderHandle;

View File

@@ -144,7 +144,7 @@ namespace Robust.Client.Graphics.Clyde
if (Eye == null)
return default;
var eye = (IEye) Eye;
var eye = Eye;
var newPoint = point;
eye.GetViewMatrix(out var viewMatrix, RenderScale);

View File

@@ -246,7 +246,7 @@ namespace Robust.Client.Graphics.Clyde
// Nada.
}
private class DummyCursor : ICursor
private sealed class DummyCursor : ICursor
{
public void Dispose()
{
@@ -254,6 +254,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
[Virtual]
private class DummyAudioSource : IClydeAudioSource
{
public static DummyAudioSource Instance { get; } = new();

View File

@@ -11,6 +11,7 @@ namespace Robust.Client.Graphics.Clyde
/// <summary>
/// Represents an OpenGL buffer object.
/// </summary>
[Virtual]
private class GLBuffer
{
private readonly Clyde _clyde;
@@ -178,7 +179,7 @@ namespace Robust.Client.Graphics.Clyde
/// Subtype of buffers so that we can have a generic constructor.
/// Functionally equivalent to <see cref="GLBuffer"/> otherwise.
/// </summary>
private class GLBuffer<T> : GLBuffer where T : unmanaged
private sealed class GLBuffer<T> : GLBuffer where T : unmanaged
{
public GLBuffer(Clyde clyde, BufferTarget type, BufferUsageHint usage, Span<T> initialize,
string? name = null)

View File

@@ -4,7 +4,7 @@ namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
private class GLShader
private sealed class GLShader
{
private readonly Clyde _clyde;

View File

@@ -14,7 +14,7 @@ namespace Robust.Client.Graphics.Clyde
/// You've been warned:
/// using things like <see cref="SetUniformTexture" /> if this buffer isn't bound WILL mess things up!
/// </summary>
private class GLShaderProgram
private sealed class GLShaderProgram
{
private readonly sbyte?[] _uniformIntCache = new sbyte?[UniCount];
private readonly Dictionary<string, int> _uniformCache = new();

View File

@@ -20,7 +20,7 @@ namespace Robust.Client.Graphics.Clyde
/// <summary>
/// Represents some set of uniforms that can be backed by a uniform buffer or by regular uniforms.
/// </summary>
private class GLUniformBuffer<T> where T : unmanaged, IAppliableUniformSet
private sealed class GLUniformBuffer<T> where T : unmanaged, IAppliableUniformSet
{
private readonly Clyde _clyde;
private readonly int _index;

View File

@@ -6,6 +6,7 @@ namespace Robust.Client.Graphics.Clyde
{
[Serializable]
[PublicAPI]
[Virtual]
internal class ShaderCompilationException : Exception
{
public ShaderCompilationException()

View File

@@ -104,6 +104,7 @@ namespace Robust.Client.Graphics.Clyde
}
[Serializable]
[Virtual]
public class GlfwException : Exception
{
public GlfwException()

View File

@@ -2,7 +2,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{
public class StyleBoxEmpty : StyleBox
public sealed class StyleBoxEmpty : StyleBox
{
protected override void DoDraw(DrawingHandleScreen handle, UIBox2 box)
{

View File

@@ -3,7 +3,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{
public class StyleBoxFlat : StyleBox
public sealed class StyleBoxFlat : StyleBox
{
public Color BackgroundColor { get; set; }
public Color BorderColor { get; set; }

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