Compare commits

...

33 Commits

Author SHA1 Message Date
Acruid
f7e8178736 Added new ComponentEvents system in IEventBus. (#1601)
* Added new ComponentEventBus, combined it with IEventBus.

* Removed all traces of IEntity from ComponentDependencies.
Removed IEntityManager dependency from ComponentManager.

* Added entity create/delete events to IEntityManager.

* ComponentEvents now use EntitySystemMessages instead of their custom ComponentEvent class.

* Component events are now just overloads of entity events.

* Removed obsolete EntitySystemMessage, now everything uses the base EntityEventArgs.

* Add a bool argument for if the message should be broadcast as well as directed.
Fix ordering and init issues of events in EntityManager.

* Changed names from Component/Entity events to Directed/Broadcast.

* Fix bugs and unit tests.
2021-03-09 11:02:24 -08:00
Pieter-Jan Briers
31f921e4aa Use ProfileOptimization to speed up startup. 2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
aa1c25637c Allow disabling nvidia optimus via env var. 2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
71f2c48463 Call GC.Collect after game init.
Cleans up any gen 2 garbage from init and the stutter shouldn't be the end of the world.
2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
d65f4ca898 RSI & texture preloading.
All RSIs and textures are now loaded ahead of time in client startup. This is well threaded and is extremely fast.
2021-03-09 12:29:59 +01:00
Pieter-Jan Briers
b35568ffe5 Disable path case checks by default.
The idea was that these are Task.Run'd so don't influence performance. That was before we started threading the hell out of startup.

We're getting more stuff like YAML linting now which should hopefully be able to catch 99% of this. And louder because it was always just a warning before.
2021-03-09 12:29:59 +01:00
Acruid
a0d241e551 Removes some things that should not have been in the last PR. 2021-03-09 02:06:13 -08:00
GraniteSidewalk
33a6934582 Large number of changes to Shaders and Overlays (#1387)
* AAAAAAAAAAAAA

* Organization

* Still doesnt work

* Formatting

* It works!!

* More changes to everything

* Beginning of changes to overlays

* Makes the overlay manager GUID based (also it was very messy, still messy but i fixed some of it)

* Stencils are easy

* Questionable changes to overlays

* Minor change to HLR

* Fixed duplicate overlays when calling some commands (Like showbb)

* Fixes misleading message

* Adds a variety of worldspaces for overlays to choose from

* Caching

* Address reviews

* Merging pains

* ah.

* ahhhhh

* minor overlaymanager changes

* Work

* fix

* Merge??

* Fixes null errors

* Force update

* Delete whatever the fuck this is?

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-03-09 01:52:16 -08:00
metalgearsloth
f237a8bbbc Optimise static body sleeping (#1618)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 19:47:51 +11:00
Pieter-Jan Briers
4bc775c01c RSI loader improvements:
1. Stop using NJsonSchema, it didn't do anything useful.
2. Use System.Text.Json instead of Newtonsoft.Json.
3. General cleanup of the code, using arrays instead of lists, etc...
2021-03-08 11:18:19 +01:00
Pieter-Jan Briers
93b4d81505 Optimize ImageSharp blitting. 2021-03-08 11:15:33 +01:00
Pieter-Jan Briers
0afb85a09e Fix some missing re-pooling of ImageSharp images. 2021-03-08 09:45:22 +01:00
Metal Gear Sloth
7b9315cea4 Significantly lower physics speedcap 2021-03-08 15:46:50 +11:00
Metal Gear Sloth
dc3af45096 Fix anchored message 2021-03-08 15:00:08 +11:00
metalgearsloth
00ce0179ae Allow kinematic controllers to have an impulse applied (#1612)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-08 12:10:20 +11:00
ShadowCommander
81947ba3d8 Fix buckling (#1611) 2021-03-07 15:25:11 -08:00
DrSmugleaf
49327279d0 Fix nullability errors in physics ContactHead code (#1609) 2021-03-07 23:15:55 +01:00
Metal Gear Sloth
0936cf3c7f Fix CanCollide serialization 2021-03-08 04:06:21 +11:00
Metal Gear Sloth
43b75a69c2 Fix contact overlap 2021-03-08 03:48:30 +11:00
metalgearsloth
c17c8d7a11 Physics (#1605)
* Physics worlds

* Paul's a good boy

* Build working

* Ingame and not lagging to hell

* Why didn't you commit ahhhhh

* Hard collisions working

* Solver parity

* Decent broadphase work done

* BroadPhase outline done

* BroadPhase working

* waiting for pvs

* Fix static PVS AABB

* Stop static bodies from awakening

* Optimise a bunch of stuff

* Even more broadphase stuff

* I'm fucking stupid

* Optimise fixture updates

* Collision solver start

* Building

* A is for Argumentative

* Fix contact caching island flags

* Circle shapes actually workeded

* Damping

* DS2 consumables only

* Slightly more stable

* Even slightlier more stablier

* VV your heart out

* Initial joint support

* 90% of joints I just wanted to push as I'd scream if I lost progress

* JOINT PURGATORY

* Joints barely functional lmao

* Okay these joints slightly more functional

* Remove station FrictionJoint

* Also that

* Some Box2D ports

* Cleanup mass

* Edge shape

* Active contacts

* Fix active contacts

* Optimise active contacts even more

* Boxes be stacking

* I would die for smug oh my fucking god

* In which everything is fixed

* Distance joints working LETS GO

* Remove frequency on distancejoint

* Fix some stuff and break joints

* Crashing fixed mehbeh

* ICollideSpecial and more resilience

* auto-clear

* showbb vera

* Slap that TODO in there

* Fix restartround crash

* Random fixes

* Fix fixture networking

* Add intersection method for broadphase

* Fix contacts

* Licenses done

* Optimisations

* Fix wall clips

* Config caching for island

* allocations optimisations

* Optimise casts

* Optimise events queue for physics

* Contact manager optimisations

* Optimise controllers

* Sloth joint or something idk

* Controller graph

* Remove content cvar

* Random cleanup

* Finally remove VirtualController

* Manifold structs again

* Optimise this absolute retardation

* Optimise

* fix license

* Cleanup physics interface

* AHHHHHHHHHHHHH

* Fix collisions again

* snivybus

* Fix potential nasty manifold bug

* Tests go snivy

* Disable prediction for now

* Spans

* Fix ShapeTypes

* fixes

* ch ch changeesss

* Kinematic idea

* Prevent static bodies from waking

* Pass WorldAABB to MoveEvent

* Fix collisions

* manifold structs fucking WOOORRKKKINNGGG

* Better pushing

* Fix merge ickies

* Optimise MoveEvents

* Use event for collisions performance

* Fix content tests

* Do not research tests

* Fix most conflicts

* Paul's trying to kill me

* Maybe collisions work idk

* Make us whole again

* Smug is also trying to kill me

* nani

* shitty collisions

* Settling

* Do not research collisions

* SHIP IT

* Fix joints

* PVS moment

* Fix other assert

* Fix locker collisions

* serializable sleeptime

* Aether2D contacts

* Physics is no longer crashing (and burning)

* Add to the TODO list

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-08 03:19:01 +11:00
Paul
223fd8126f copy fix 2021-03-06 14:47:44 +01:00
Paul
1d5559be4a makes typevalidator its own interface 2021-03-05 15:02:03 +01:00
Paul
0b749ff8bb makes prototypeinheritance opt in 2021-03-05 11:13:00 +01:00
Paul
069fa89fcb adds Try variants to FirstOrNull & FirstOrDefault
fixes ientity serialization when loading the map
2021-03-05 10:07:18 +01:00
Paul Ritter
80f9f24243 Serialization v3 aka constant suffering (#1606)
* oops

* fixes serialization il

* copytest

* typo & misc fixes

* 139 moment

* boxing

* mesa dum

* stuff

* goodbye bad friend

* last commit before the big (4) rewrite

* adds datanodes

* kills yamlobjserializer in favor of the new system

* adds more serializers, actually implements them & removes most of the last of the old system

* changed yamlfieldattribute namespace

* adds back iselfserialize

* refactors consts&flags

* renames everything to data(field/definition)

* adds afterserialization

* help

* dataclassgen

* fuggen help me mannen

* Fix most errors on content

* Fix engine errors except map loader

* maploader & misc fix

* misc fixes

* thing

* help

* refactors datanodes

* help me mannen

* Separate ITypeSerializer into reader and writer

* Convert all type serializers

* priority

* adds alot

* il fixes

* adds robustgen

* argh

* adds array & enum serialization

* fixes dataclasses

* adds vec2i / misc fixes

* fixes inheritance

* a very notcursed todo

* fixes some custom dataclasses

* push dis

* Remove data classes

* boutta box

* yes

* Add angle and regex serializer tests

* Make TypeSerializerTest abstract

* sets up ioc etc

* remove pushinheritance

* fixes

* Merge fixes, fix yaml hot reloading

* General fixes2

* Make enum serialization ignore case

* Fix the tag not being copied in data nodes

* Fix not properly serializing flag enums

* Fix component serialization on startup

* Implement ValueDataNode ToString

* Serialization IL fixes, fix return and string equality

* Remove async from prototype manager

* Make serializing unsupported node as enum exception more descriptive

* Fix serv3 tryread casting to serializer instead of reader

* Add constructor for invalid node type exception

* Temporary fix for SERV3: Turn populate delegate into regular code

* Fix not copying the data of non primitive types

* Fix not using the data definition found in copying

* Make ISerializationHooks require explicit implementations

* Add test for serialization inheritance

* Improve IsOverridenIn method

* Fix error message when a data definition is null

* Add method to cast a read value in Serv3Manager

* Rename IServ3Manager to ISerializationManager

* Rename usages of serv3manager, add generic copy method

* Fix IL copy method lookup

* Rename old usages of serv3manager

* Add ITypeCopier

* resistance is futile

* we will conquer this codebase

* Add copy method to all serializers

* Make primitive mismatch error message more descriptive

* bing bong im going to freacking heck

* oopsie moment

* hello are you interested in my wares

* does generic serializers under new architecture

* Convert every non generic serializer to the new format, general fixes

* Update usgaes of generic serializers, cleanup

* does some pushinheritance logic

* finishes pushinheritance FRAMEWORK

* shed

* Add box2, color and component registry serializer tests

* Create more deserialized types and store prototypes with their deserialized results

* Fixes and serializer updates

* Add serialization manager extensions

* adds pushinheritance

* Update all prototypes to have a parent and have consistent id/parent properties

* Fix grammar component serialization

* Add generic serializer tests

* thonk

* Add array serializer test

* Replace logger warning calls with exceptions

* fixes

* Move redundant methods to serialization manager extensions, cleanup

* Add array serialization

* fixes context

* more fixes

* argh

* inheritance

* this should do it

* fixes

* adds copiers & fixes some stuff

* copiers use context v1

* finishing copy context

* more context fixes

* Test fixes

* funky maps

* Fix server user interface component serialization

* Fix value tuple serialization

* Add copying for value types and arrays. Fix copy internal for primitives, enums and strings

* fixes

* fixes more stuff

* yes

* Make abstract/interface skips debugs instead of warnings

* Fix typo

* Make some dictionaries readonly

* Add checks for the serialization manager initializing and already being initialized

* Add base type required and usage for MeansDataDefinition and ImplicitDataDefinitionForInheritorsAttribute

* copy by ref

* Fix exception wording

* Update data field required summary with the new forbidden docs

* Use extension in map loader

* wanna erp

* Change serializing to not use il temporarily

* Make writing work with nullable types

* pushing

* check

* cuddling slaps HARD

* Add serialization priority test

* important fix

* a serialization thing

* serializer moment

* Add validation for some type serializers

* adds context

* moar context

* fixes

* Do the thing for appearance

* yoo lmao

* push haha pp

* Temporarily make copy delegate regular c# code

* Create deserialized component registry to handle not inheriting conflicting references

* YAML LINTER BABY

* ayes

* Fix sprite component norot not being default true like in latest master

* Remove redundant todos

* Add summary doc to every ISerializationManager method

* icon fixes

* Add skip hook argument to readers and copiers

* Merge fixes

* Fix ordering of arguments in read and copy reflection call

* Fix user interface components deserialization

* pew pew

* i am going to HECK

* Add MustUseReturnValue to copy-over methods

* Make serialization log calls use the same sawmill

* gamin

* Fix doc errors in ISerializationManager.cs

* goodbye brave soldier

* fixes

* WIP merge fixes and entity serialization

* aaaaaaaaaaaaaaa

* aaaaaaaaaaaaaaa

* adds inheritancebehaviour

* test/datafield fixes

* forgot that one

* adds more verbose validation

* This fixes the YAML hot reloading

* Replace yield break with Enumerable.Empty

* adds copiers

* aaaaaaaaaaaaa

* array fix
priority fix
misc fixes

* fix(?)

* fix.

* funny map serialization (wip)

* funny map serialization (wip)

* Add TODO

* adds proper info the validation

* Make yaml linter 5 times faster (~80% less execution time)

* Improves the error message for missing fields in the linter

* Include component name in unknown component type error node

* adds alwaysrelevant usa

* fixes mapsaving

* moved surpressor to analyzers proj

* warning cleanup & moves surpressor

* removes old msbuild targets

* Revert "Make yaml linter 5 times faster (~80% less execution time)"

This reverts commit 2ee4cc2c26.

* Add serialization to RobustServerSimulation and mock reflection methods
Fixes container tests

* Fix nullability warnings

* Improve yaml linter message feedback

* oops moment

* Add IEquatable, IComparable, ToString and operators to DataPosition
Rename it to NodeMark
Make it a readonly struct

* Remove try catch from enum parsing

* Make dependency management in serialization less bad

* Make dependencies an argument instead of a property on the serialization manager

* Clean up type serializers

* Improve validation messages and resourc epath checking

* Fix sprite error message

* reached perfection

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Vera Aguilera Puerto <zddm@outlook.es>
2021-03-04 15:59:14 -08:00
Pieter-Jan Briers
93018c9843 Silence localization warnings on client again. 2021-03-03 16:02:30 +01:00
Pieter-Jan Briers
e2675271d0 Parallelize assembly sandbox checking harder. 2021-03-03 16:02:12 +01:00
Pieter-Jan Briers
d1f7edecef Use Directory.EnumerateFiles in PathHelpers.GetFiles.
Significant improvement in startup time.
2021-03-03 10:52:05 +01:00
Pieter-Jan Briers
b5a3c0b988 Do not load files under Locale/ not ending with .ftl.
Will ignore stuff like .DS_Store/.directory/thumbs.db
2021-03-02 21:22:44 +01:00
Acruid
06e62b031a SoundSystem (#1604)
* Adds the SoundSystem static proxy class for the AudioSystem.
Added a shared IAudioSystem interface for the future.

* Moved ConnectedClient property from IPlayerSession down to ICommonSession.

* Connected up the SoundSystem to the client/server AudioSystems.

* Converted client calls over to the new system.

* Marked the old serverside functions to play sound obsolete, use the new ones from the IAudioSystem.

* Added ISharedPlayerManager to the IoC registration.
2021-03-01 20:22:28 -08:00
Acruid
24707b7385 Shared Containers (#1579)
* Added a basic server simulation framework for help with tests.

* Moved as much as possible to Robust.Shared/Containers.
Moved ContainerSlot from content to engine.

* Moved ClientContainer to shared.

* Merged client/server ContainerManagerComponents into a single shared version.

* ContainerManagerComponent is now implicitly registered with the attributes.

* Migrated to 2021 serialization technology.

* Existing Unit Tests work.

* More tests coverage.
Fixed bug with transferring items between containers.

* Container Type info is now sent over the network.

* Merge client/server container systems.

* Code cleanup.

* Attempted to fix dictionary serialization.
Logs warning when trying to check if an unknown GridId is paused.

* Remove OldCode.
2021-03-01 15:19:59 -08:00
Pieter-Jan Briers
ab95f39f9f Localize SS14Window 2021-03-01 00:45:36 +01:00
Pieter-Jan Briers
cdd38abab5 Fix two shutdown crashes by removing IDisposable managers. 2021-02-28 23:10:03 +01:00
396 changed files with 24967 additions and 10286 deletions

View File

@@ -0,0 +1 @@
ss14window-placeholder-title = Exemplary Window Title Here

View File

@@ -0,0 +1,4 @@
- type: entity
name: blank entity
id: BlankEntity
abstract: true

View File

@@ -0,0 +1,10 @@
using Microsoft.CodeAnalysis;
namespace Robust.Generators
{
public static class Diagnostics
{
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Generators;
namespace Robust.Analyzers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class MeansImplicitAssigmentSuppressor : DiagnosticSuppressor
{
const string MeansImplicitAssignmentAttribute = "Robust.Shared.MeansImplicitAssignmentAttribute";
public override void ReportSuppressions(SuppressionAnalysisContext context)
{
var implAttr = context.Compilation.GetTypeByMetadataName(MeansImplicitAssignmentAttribute);
foreach (var reportedDiagnostic in context.ReportedDiagnostics)
{
if(reportedDiagnostic.Id != Diagnostics.MeansImplicitAssignment.SuppressedDiagnosticId) continue;
var node = reportedDiagnostic.Location.SourceTree?.GetRoot(context.CancellationToken).FindNode(reportedDiagnostic.Location.SourceSpan);
if (node == null) continue;
var symbol = context.GetSemanticModel(reportedDiagnostic.Location.SourceTree).GetDeclaredSymbol(node);
if (symbol == null || !symbol.GetAttributes().Any(a =>
a.AttributeClass?.GetAttributes().Any(attr =>
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, implAttr)) == true))
{
continue;
}
context.ReportSuppression(Suppression.Create(
Diagnostics.MeansImplicitAssignment,
reportedDiagnostic));
}
}
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(Diagnostics.MeansImplicitAssignment);
}
}

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.Animations
/// <seealso cref="AnimationPlayerComponent"/>
public sealed class Animation
{
public readonly List<AnimationTrack> AnimationTracks = new();
public List<AnimationTrack> AnimationTracks { get; private set; } = new();
public TimeSpan Length { get; set; }
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using Robust.Client.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.Player;
namespace Robust.Client.Animations
{
@@ -14,7 +15,7 @@ namespace Robust.Client.Animations
/// <summary>
/// A list of key frames for when to fire flicks.
/// </summary>
public readonly List<KeyFrame> KeyFrames = new();
public List<KeyFrame> KeyFrames { get; private set; } = new();
public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
{
@@ -36,8 +37,7 @@ namespace Robust.Client.Animations
var keyFrame = KeyFrames[keyFrameIndex];
EntitySystem.Get<AudioSystem>()
.Play(keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
SoundSystem.Play(Filter.Local(), keyFrame.Resource, entity, keyFrame.AudioParamsFunc.Invoke());
}
return (keyFrameIndex, playingTime);

View File

@@ -10,7 +10,7 @@ namespace Robust.Client.Animations
/// </summary>
public abstract class AnimationTrackProperty : AnimationTrack
{
public readonly List<KeyFrame> KeyFrames = new();
public List<KeyFrame> KeyFrames { get; protected set; } = new();
/// <summary>
/// How to interpolate values when between two keyframes.

View File

@@ -15,7 +15,7 @@ namespace Robust.Client.Animations
/// <summary>
/// A list of key frames for when to fire flicks.
/// </summary>
public readonly List<KeyFrame> KeyFrames = new();
public List<KeyFrame> KeyFrames { get; private set; } = new();
// TODO: Should this layer key be per keyframe maybe?
/// <summary>

View File

@@ -13,6 +13,7 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Utility;
using Logger = Robust.Shared.Log.Logger;
@@ -63,14 +64,18 @@ namespace Robust.Client.Audio.Midi
bool IsAvailable { get; }
public int OcclusionCollisionMask { get; set; }
void Shutdown();
}
internal class MidiManager : IDisposable, IMidiManager
internal class MidiManager : IMidiManager
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
public bool IsAvailable
{
get
@@ -154,6 +159,7 @@ namespace Robust.Client.Audio.Midi
_midiThread = new Thread(ThreadUpdate);
_midiThread.Start();
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
FluidsynthInitialized = true;
}
@@ -298,7 +304,7 @@ namespace Robust.Client.Audio.Midi
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = IoCManager.Resolve<IPhysicsManager>().IntersectRayPenetration(
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
@@ -352,7 +358,7 @@ namespace Robust.Client.Audio.Midi
}
}
public void Dispose()
public void Shutdown()
{
_alive = false;
_midiThread?.Join();

View File

@@ -24,8 +24,11 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
namespace Robust.Client
{
@@ -53,6 +56,7 @@ namespace Robust.Client
IoCManager.Register<IClientGameStateManager, ClientGameStateManager>();
IoCManager.Register<IBaseClient, BaseClient>();
IoCManager.Register<IPlayerManager, PlayerManager>();
IoCManager.Register<ISharedPlayerManager, PlayerManager>();
IoCManager.Register<IStateManager, StateManager>();
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
@@ -99,7 +103,6 @@ namespace Robust.Client
IoCManager.Register<IViewVariablesManagerInternal, ViewVariablesManager>();
IoCManager.Register<IClientConGroupController, ClientConGroupController>();
IoCManager.Register<IScriptClient, ScriptClient>();
//IoCManager.Register<IXamlCompiler, XamlCompiler>();
}
}
}

View File

@@ -12,7 +12,6 @@ using Robust.Client.Input;
using Robust.Client.Debugging;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;

View File

@@ -0,0 +1,41 @@
using Robust.Client.Debugging;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
public sealed class PhysicsOverlayCommands : IConsoleCommand
{
public string Command => "physics";
public string Description => $"{Command} <contactnormals / contactpoints / shapes>";
public string Help => $"{Command} <overlay>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine($"Invalid number of args supplied");
return;
}
var system = EntitySystem.Get<DebugPhysicsSystem>();
switch (args[0])
{
case "contactnormals":
system.Flags ^= PhysicsDebugFlags.ContactNormals;
break;
case "contactpoints":
system.Flags ^= PhysicsDebugFlags.ContactPoints;
break;
case "shapes":
system.Flags ^= PhysicsDebugFlags.Shapes;
break;
default:
shell.WriteLine($"{args[0]} is not a recognised overlay");
return;
}
return;
}
}
}

View File

@@ -0,0 +1,17 @@
using Robust.Client.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
namespace Robust.Client.Console.Commands
{
public class VelocitiesCommand : IConsoleCommand
{
public string Command => "showvelocities";
public string Description => "Displays your angular and linear velocities";
public string Help => $"{Command}";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
EntitySystem.Get<VelocityDebugSystem>().Enabled ^= true;
}
}
}

View File

@@ -1,12 +1,16 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Prototypes;
namespace Robust.Client.Debugging
@@ -38,14 +42,14 @@ namespace Robust.Client.Debugging
_debugColliders = value;
if (value)
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
{
_overlayManager.AddOverlay(new PhysicsOverlay(_componentManager, _eyeManager,
_prototypeManager, _inputManager, _physicsManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(PhysicsOverlay));
_overlayManager.RemoveOverlay<PhysicsOverlay>();
}
}
}
@@ -63,13 +67,13 @@ namespace Robust.Client.Debugging
_debugPositions = value;
if (value)
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(EntityPositionOverlay));
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
}
}
}
@@ -88,8 +92,8 @@ namespace Robust.Client.Debugging
private Vector2 _hoverStartScreen = Vector2.Zero;
private List<IPhysBody> _hoverBodies = new();
public PhysicsOverlay(IComponentManager compMan, IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IPhysicsManager physicsManager)
: base(nameof(PhysicsOverlay))
{
_componentManager = compMan;
_eyeManager = eyeMan;
@@ -136,7 +140,7 @@ namespace Robust.Client.Debugging
row++;
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
row++;
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {((IPhysicsComponent)body).Anchored}");
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
row++;
}
@@ -158,19 +162,22 @@ namespace Robust.Client.Debugging
var mapId = _eyeManager.CurrentMap;
foreach (var physBody in _physicsManager.GetCollidingEntities(mapId, viewport))
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
{
// all entities have a TransformComponent
var transform = physBody.Entity.Transform;
var worldBox = physBody.WorldAABB;
var worldBox = physBody.GetWorldAABB();
if (worldBox.IsEmpty()) continue;
var colorEdge = Color.Red.WithAlpha(0.33f);
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
foreach (var shape in physBody.PhysicsShapes)
foreach (var fixture in physBody.Fixtures)
{
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, physBody.SleepAccumulator / (float) physBody.SleepThreshold);
var shape = fixture.Shape;
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
}
if (worldBox.Contains(mouseWorldPos))
@@ -233,6 +240,16 @@ namespace Robust.Client.Debugging
_handle.DrawCircle(origin, radius, color);
}
public override void DrawPolygonShape(Vector2[] vertices, in Color color)
{
_handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, vertices, color);
}
public override void DrawLine(Vector2 start, Vector2 end, in Color color)
{
_handle.DrawLine(start, end, color);
}
public override void SetTransform(in Matrix3 transform)
{
_handle.SetTransform(transform);
@@ -249,7 +266,7 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager) : base(nameof(EntityPositionOverlay))
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
{
_entityManager = entityManager;
_eyeManager = eyeManager;

View File

@@ -1,10 +1,11 @@
using Robust.Shared.IoC;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.Network;
namespace Robust.Client.Debugging
@@ -38,13 +39,13 @@ namespace Robust.Client.Debugging
_debugDrawRays = value;
if (value)
if (value && !_overlayManager.HasOverlay<DebugDrawRayOverlay>())
{
_overlayManager.AddOverlay(new DebugDrawRayOverlay(this));
}
else
{
_overlayManager.RemoveOverlay(nameof(DebugDrawRayOverlay));
_overlayManager.RemoveOverlay<DebugDrawRayOverlay>();
}
}
}
@@ -81,7 +82,7 @@ namespace Robust.Client.Debugging
private readonly DebugDrawingManager _owner;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugDrawRayOverlay(DebugDrawingManager owner) : base(nameof(DebugDrawRayOverlay))
public DebugDrawRayOverlay(DebugDrawingManager owner)
{
_owner = owner;
}

View File

@@ -0,0 +1,164 @@
/*
* Farseer Physics Engine:
* Copyright (c) 2012 Ian Qvist
*
* Original source Box2D:
* Copyright (c) 2006-2011 Erin Catto http://www.box2d.org
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
/* Heavily inspired by Farseer */
using System;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
namespace Robust.Client.Debugging
{
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
{
/*
* Used for debugging shapes, controllers, joints, contacts
*/
private const int MaxContactPoints = 2048;
internal int PointCount;
internal ContactPoint[] _points = new ContactPoint[MaxContactPoints];
public PhysicsDebugFlags Flags
{
get => _flags;
set
{
if (value == _flags) return;
if (_flags == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
if (value == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
_flags = value;
}
}
private PhysicsDebugFlags _flags;
public override void HandlePreSolve(Contact contact, in Manifold oldManifold)
{
if ((Flags & PhysicsDebugFlags.ContactPoints) != 0)
{
Manifold manifold = contact.Manifold;
if (manifold.PointCount == 0)
return;
Fixture fixtureA = contact.FixtureA!;
PointState[] state1, state2;
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
Span<Vector2> points = stackalloc Vector2[2];
Vector2 normal;
contact.GetWorldManifold(out normal, points);
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
{
if (fixtureA == null)
_points[i] = new ContactPoint();
ContactPoint cp = _points[PointCount];
cp.Position = points[i];
cp.Normal = normal;
cp.State = state2[i];
_points[PointCount] = cp;
++PointCount;
}
}
}
internal struct ContactPoint
{
public Vector2 Normal;
public Vector2 Position;
public PointState State;
}
}
[Flags]
internal enum PhysicsDebugFlags : byte
{
None = 0,
ContactPoints = 1 << 0,
ContactNormals = 1 << 1,
Shapes = 1 << 2,
}
internal sealed class PhysicsDebugOverlay : Overlay
{
private DebugPhysicsSystem _physics = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsDebugOverlay(DebugPhysicsSystem system)
{
_physics = system;
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
{
if (_physics.Flags == PhysicsDebugFlags.None) return;
var worldHandle = (DrawingHandleWorld) handle;
if ((_physics.Flags & PhysicsDebugFlags.Shapes) != 0)
{
// Port DebugDrawing over.
}
if ((_physics.Flags & PhysicsDebugFlags.ContactPoints) != 0)
{
const float axisScale = 0.3f;
for (int i = 0; i < _physics.PointCount; ++i)
{
DebugPhysicsSystem.ContactPoint point = _physics._points[i];
if (point.State == PointState.Add)
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 243, 77));
else if (point.State == PointState.Persist)
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 77, 77));
if ((_physics.Flags & PhysicsDebugFlags.ContactNormals) != 0)
{
Vector2 p1 = point.Position;
Vector2 p2 = p1 + point.Normal * axisScale;
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 102));
}
}
_physics.PointCount = 0;
}
}
}
}

View File

@@ -1,7 +1,8 @@
using System;
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Robust.Client.Audio.Midi;
using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
@@ -26,6 +27,7 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -43,7 +45,7 @@ namespace Robust.Client
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
@@ -61,6 +63,7 @@ namespace Robust.Client
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
[Dependency] private readonly IAuthManager _authManager = default!;
[Dependency] private readonly IMidiManager _midiManager = default!;
private CommandLineArgs? _commandLineArgs;
private bool _disableAssemblyLoadContext;
@@ -116,6 +119,8 @@ namespace Robust.Client
_configurationManager.OverrideConVars(_commandLineArgs.CVars);
}
ProfileOptSetup.Setup(_configurationManager);
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
@@ -152,16 +157,19 @@ namespace Robust.Client
_configurationManager.LoadCVarsFromAssembly(loadedModule);
}
IoCManager.Resolve<ISerializationManager>().Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
_resourceCache.PreloadTextures();
_userInterfaceManager.Initialize();
_networkManager.Initialize(false);
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_serializer.Initialize();
_inputManager.Initialize();
_consoleHost.Initialize();
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
@@ -183,6 +191,8 @@ namespace Robust.Client
_authManager.LoadFromEnv();
GC.Collect();
_clyde.Ready();
if ((_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
@@ -318,7 +328,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.Error;
#if DEBUG_ONLY_FCE_INFO
#if DEBUG_ONLY_FCE_LOG
@@ -377,6 +387,8 @@ namespace Robust.Client
private void Cleanup()
{
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
}

View File

@@ -17,7 +17,6 @@ namespace Robust.Client
RegisterReflection();
}
internal static void RegisterReflection()
{
// Gets a handle to the shared and the current (client) dll.

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
@@ -23,7 +24,12 @@ namespace Robust.Client.GameObjects
Register<PhysicsComponent>();
RegisterReference<PhysicsComponent, IPhysBody>();
RegisterReference<PhysicsComponent, IPhysicsComponent>();
Register<CollisionWakeComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
RegisterIgnore("KeyBindingInput");
Register<InputComponent>();
@@ -49,14 +55,10 @@ namespace Robust.Client.GameObjects
Register<AnimationPlayerComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<TimerComponent>();
#if DEBUG
Register<DebugExceptionOnAddComponent>();
Register<DebugExceptionExposeDataComponent>();
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
#endif

View File

@@ -45,12 +45,12 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message)
public void SendSystemNetworkMessage(EntityEventArgs message)
{
SendSystemNetworkMessage(message, default(uint));
}
public void SendSystemNetworkMessage(EntitySystemMessage message, uint sequence)
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
{
var msg = _networkManager.CreateNetMessage<MsgEntity>();
msg.Type = EntityMessageType.SystemMessage;
@@ -62,7 +62,7 @@ namespace Robust.Client.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel channel)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel channel)
{
throw new NotSupportedException();
}

View File

@@ -4,8 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
@@ -15,13 +14,11 @@ namespace Robust.Client.GameObjects
{
[ViewVariables]
private Dictionary<object, object> data = new();
[ViewVariables]
[DataField("visuals")]
internal List<AppearanceVisualizer> Visualizers = new();
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
private static bool _didRegisterSerializer;
[ViewVariables]
private bool _appearanceDirty;
@@ -107,18 +104,6 @@ namespace Robust.Client.GameObjects
_appearanceDirty = false;
}
public override void ExposeData(ObjectSerializer serializer)
{
if (!_didRegisterSerializer)
{
YamlObjectSerializer.RegisterTypeSerializer(typeof(AppearanceVisualizer),
new VisualizerTypeSerializer(_reflectionManager));
_didRegisterSerializer = true;
}
serializer.DataFieldCached(ref Visualizers, "visuals", new List<AppearanceVisualizer>());
}
public override void Initialize()
{
base.Initialize();
@@ -131,78 +116,6 @@ namespace Robust.Client.GameObjects
MarkDirty();
}
class VisualizerTypeSerializer : YamlObjectSerializer.TypeSerializer
{
private readonly IReflectionManager _reflectionManager;
public VisualizerTypeSerializer(IReflectionManager reflectionManager)
{
_reflectionManager = reflectionManager;
}
public override object NodeToType(Type type, YamlNode node, YamlObjectSerializer serializer)
{
var mapping = (YamlMappingNode) node;
var nodeType = mapping.GetNode("type");
switch (nodeType.AsString())
{
case SpriteLayerToggle.NAME:
var keyString = mapping.GetNode("key").AsString();
object key;
if (_reflectionManager.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
var layer = mapping.GetNode("layer").AsInt();
return new SpriteLayerToggle(key, layer);
default:
var visType = _reflectionManager.LooseGetType(nodeType.AsString());
if (!typeof(AppearanceVisualizer).IsAssignableFrom(visType))
{
throw new InvalidOperationException();
}
var vis = (AppearanceVisualizer) Activator.CreateInstance(visType)!;
vis.LoadData(mapping);
return vis;
}
}
public override YamlNode TypeToNode(object obj, YamlObjectSerializer serializer)
{
switch (obj)
{
case SpriteLayerToggle spriteLayerToggle:
YamlScalarNode key;
if (spriteLayerToggle.Key is Enum)
{
var name = spriteLayerToggle.Key.GetType().FullName;
key = new YamlScalarNode($"{name}.{spriteLayerToggle.Key}");
}
else
{
key = new YamlScalarNode(spriteLayerToggle.Key.ToString());
}
return new YamlMappingNode
{
{new YamlScalarNode("type"), new YamlScalarNode(SpriteLayerToggle.NAME)},
{new YamlScalarNode("key"), key},
{new YamlScalarNode("layer"), new YamlScalarNode(spriteLayerToggle.SpriteLayer.ToString())},
};
default:
// TODO: A proper way to do serialization here.
// I can't use the ExposeData system here since that's specific to entity serializers.
return new YamlMappingNode();
}
}
}
internal class SpriteLayerToggle : AppearanceVisualizer
{
@@ -223,15 +136,9 @@ namespace Robust.Client.GameObjects
/// Handles the visualization of data inside of an appearance component.
/// Implementations of this class are NOT bound to a specific entity, they are flyweighted across multiple.
/// </summary>
[ImplicitDataDefinitionForInheritors]
public abstract class AppearanceVisualizer
{
/// <summary>
/// Load data from the prototype declaring this visualizer, to configure settings and such.
/// </summary>
public virtual void LoadData(YamlMappingNode node)
{
}
/// <summary>
/// Initializes an entity to be managed by this appearance controller.
/// DO NOT assume this is your only entity. Visualizers are shared.

View File

@@ -1,86 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
public sealed partial class ContainerManagerComponent
{
[DebuggerDisplay("ClientContainer {Owner.Uid}/{ID}")]
private sealed class ClientContainer : IContainer
{
public List<IEntity> Entities { get; } = new List<IEntity>();
public ClientContainer(string id, ContainerManagerComponent manager)
{
ID = id;
Manager = manager;
}
[ViewVariables] public IContainerManager Manager { get; }
[ViewVariables] public string ID { get; }
[ViewVariables] public IEntity Owner => Manager.Owner;
[ViewVariables] public bool Deleted { get; private set; }
[ViewVariables] public IReadOnlyList<IEntity> ContainedEntities => Entities;
[ViewVariables]
public bool ShowContents { get; set; }
[ViewVariables]
public bool OccludesLight { get; set; }
public bool CanInsert(IEntity toinsert)
{
return false;
}
public bool Insert(IEntity toinsert)
{
return false;
}
public bool CanRemove(IEntity toremove)
{
return false;
}
public bool Remove(IEntity toremove)
{
return false;
}
public void ForceRemove(IEntity toRemove)
{
throw new NotSupportedException("Cannot directly modify containers on the client");
}
public bool Contains(IEntity contained)
{
return Entities.Contains(contained);
}
public void DoInsert(IEntity entity)
{
Entities.Add(entity);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
}
public void DoRemove(IEntity entity)
{
Entities.Remove(entity);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
}
public void Shutdown()
{
Deleted = true;
}
}
public override void InternalContainerShutdown(IContainer container)
{
}
}
}

View File

@@ -1,168 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
public sealed partial class ContainerManagerComponent : SharedContainerManagerComponent
{
[ViewVariables]
private readonly Dictionary<string, ClientContainer> _containers = new();
public override T MakeContainer<T>(string id)
{
throw new NotSupportedException("Cannot modify containers on the client.");
}
public override bool Remove(IEntity entity)
{
// TODO: This will probably need relaxing if we want to predict things like inventories.
throw new NotSupportedException("Cannot modify containers on the client.");
}
protected override IEnumerable<IContainer> GetAllContainersImpl()
{
return _containers.Values.Where(c => !c.Deleted);
}
public override IContainer GetContainer(string id)
{
return _containers[id];
}
public override bool HasContainer(string id)
{
return _containers.ContainsKey(id);
}
public override bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container)
{
var ret = _containers.TryGetValue(id, out var cont);
container = cont!;
return ret;
}
/// <inheritdoc />
public override bool TryGetContainer(IEntity entity, [NotNullWhen(true)] out IContainer? container)
{
foreach (var contain in _containers.Values)
{
if (!contain.Deleted && contain.Contains(entity))
{
container = contain;
return true;
}
}
container = default;
return false;
}
public override bool ContainsEntity(IEntity entity)
{
foreach (var container in _containers.Values)
{
if (!container.Deleted && container.Contains(entity))
{
return true;
}
}
return false;
}
public override void ForceRemove(IEntity entity)
{
throw new NotSupportedException("Cannot modify containers on the client.");
}
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
{
if(!(curState is ContainerManagerComponentState cast))
return;
// Delete now-gone containers.
List<string>? toDelete = null;
foreach (var (id, container) in _containers)
{
if (!cast.Containers.ContainsKey(id))
{
container.Shutdown();
toDelete ??= new List<string>();
toDelete.Add(id);
}
}
if (toDelete != null)
{
foreach (var dead in toDelete)
{
_containers.Remove(dead);
}
}
// Add new containers and update existing contents.
foreach (var (id, data) in cast.Containers)
{
if (!_containers.TryGetValue(id, out var container))
{
container = new ClientContainer(id, this);
_containers.Add(id, container);
}
// sync show flag
container.ShowContents = data.ShowContents;
container.OccludesLight = data.OccludesLight;
// Remove gone entities.
List<IEntity>? toRemove = null;
foreach (var entity in container.Entities)
{
if (!data.ContainedEntities.Contains(entity.Uid))
{
toRemove ??= new List<IEntity>();
toRemove.Add(entity);
}
}
if (toRemove != null)
{
foreach (var goner in toRemove)
{
container.DoRemove(goner);
}
}
// Add new entities.
foreach (var uid in data.ContainedEntities)
{
var entity = Owner.EntityManager.GetEntity(uid);
if (!container.Entities.Contains(entity))
{
container.DoInsert(entity);
}
}
}
}
protected override void Shutdown()
{
base.Shutdown();
// On shutdown we won't get to process remove events in the containers so this has to be manually done.
foreach (var container in _containers.Values)
{
foreach (var containerEntity in container.Entities)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new UpdateContainerOcclusionMessage(containerEntity));
}
}
}
}
}

View File

@@ -3,7 +3,9 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
@@ -20,7 +22,9 @@ namespace Robust.Client.GameObjects
// Horrible hack to get around ordering issues.
private bool _setCurrentOnInitialize;
private bool _setDrawFovOnInitialize;
[DataField("drawFov")]
private bool _setDrawFovOnInitialize = true;
[DataField("zoom")]
private Vector2 _setZoomOnInitialize = Vector2.One/2f;
private Vector2 _offset = Vector2.Zero;
@@ -157,15 +161,6 @@ namespace Robust.Client.GameObjects
Current = false;
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref _setZoomOnInitialize, "zoom", Vector2.One/2f);
serializer.DataFieldCached(ref _setDrawFovOnInitialize, "drawFov", true);
}
/// <summary>
/// Updates the Eye of this entity with the transform position. This has to be called every frame to
/// keep the view following the entity.

View File

@@ -1,105 +1,51 @@
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.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
public class IconComponent : Component
public class IconComponent : Component, ISerializationHooks
{
public override string Name => "Icon";
public IDirectionalTextureProvider? Icon { get; private set; }
[Dependency] private readonly IResourceCache _resourceCache = default!;
[DataField("sprite")]
private ResourcePath? rsi;
[DataField("state")]
private string? stateID;
void ISerializationHooks.AfterDeserialization()
{
if (rsi != null && stateID != null)
{
Icon = new SpriteSpecifier.Rsi(rsi, stateID).Frame0();
}
}
public const string LogCategory = "go.comp.icon";
const string SerializationCache = "icon";
public override void ExposeData(ObjectSerializer serializer)
private static IRsiStateLike TextureForConfig(IconComponent compData, IResourceCache resourceCache)
{
base.ExposeData(serializer);
// TODO: Does this need writing?
if (serializer.Reading)
{
Icon = TextureForConfig(serializer, _resourceCache);
}
}
private static IRsiStateLike TextureForConfig(ObjectSerializer serializer, IResourceCache resourceCache)
{
DebugTools.Assert(serializer.Reading);
if (serializer.TryGetCacheData<IRsiStateLike>(SerializationCache, out var dirTex))
{
return dirTex;
}
var tex = serializer.ReadDataField<string?>("texture", null);
if (!string.IsNullOrWhiteSpace(tex))
{
dirTex = resourceCache.GetResource<TextureResource>(SpriteComponent.TextureRoot / tex).Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
RSI rsi;
var rsiPath = serializer.ReadDataField<string?>("sprite", null);
if (string.IsNullOrWhiteSpace(rsiPath))
{
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
var path = SpriteComponent.TextureRoot / rsiPath;
try
{
rsi = resourceCache.GetResource<RSIResource>(path).RSI;
}
catch
{
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
var stateId = serializer.ReadDataField<string?>("state", null);
if (string.IsNullOrWhiteSpace(stateId))
{
Logger.ErrorS(LogCategory, "No state specified.");
dirTex = resourceCache.GetFallback<TextureResource>().Texture;
serializer.SetCacheData(SerializationCache, dirTex);
return dirTex;
}
if (rsi.TryGetState(stateId, out var state))
{
serializer.SetCacheData(SerializationCache, state);
return state;
}
else
{
Logger.ErrorS(LogCategory, "State '{0}' does not exist on RSI.", stateId);
return resourceCache.GetFallback<TextureResource>().Texture;
}
return compData.Icon?.Default ?? resourceCache.GetFallback<TextureResource>().Texture;
}
public static IRsiStateLike? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
if (!prototype.Components.TryGetValue("Icon", out var mapping))
if (!prototype.Components.TryGetValue("Icon", out var compData))
{
return null;
}
return TextureForConfig(YamlObjectSerializer.NewReader(mapping), resourceCache);
return TextureForConfig((IconComponent)compData, resourceCache);
}
}
}

View File

@@ -1,6 +1,8 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Input;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
@@ -17,14 +19,7 @@ namespace Robust.Client.GameObjects
/// The context that will be made active for a client that attaches to this entity.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public string ContextName { get; set; } = default!;
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataReadWriteFunction("context", InputContextContainer.DefaultContextName, value => ContextName = value, () => ContextName);
}
[DataField("context")]
public string ContextName { get; set; } = InputContextContainer.DefaultContextName;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Animations;
@@ -7,13 +7,14 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(IPointLightComponent))]
public class PointLightComponent : Component, IPointLightComponent
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
@@ -134,16 +135,25 @@ namespace Robust.Client.GameObjects
}
}
private float _radius = 5;
[DataField("radius")]
private float _radius = 5f;
[DataField("nestedvisible")]
private bool _visibleNested = true;
private bool _lightOnParent;
[DataField("color")]
private Color _color = Color.White;
private Vector2 _offset;
[DataField("offset")]
private Vector2 _offset = Vector2.Zero;
[DataField("enabled")]
private bool _enabled = true;
[DataField("autoRot")]
private bool _maskAutoRotate;
private Angle _rotation;
private float _energy;
private float _softness;
[DataField("energy")]
private float _energy = 1f;
[DataField("softness")]
private float _softness = 1f;
[DataField("mask")]
private string? _maskPath;
/// <summary>
@@ -169,6 +179,14 @@ namespace Robust.Client.GameObjects
Mask = null;
}
void ISerializationHooks.AfterDeserialization()
{
if (_maskPath != null)
{
Mask = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(_maskPath);
}
}
public override void Initialize()
{
base.Initialize();
@@ -180,7 +198,7 @@ namespace Robust.Client.GameObjects
{
base.HandleMessage(message, component);
if ((message is ParentChangedMessage msg))
if (message is ParentChangedMessage msg)
{
HandleTransformParentChanged(msg);
}
@@ -204,19 +222,6 @@ namespace Robust.Client.GameObjects
}
}
public override void ExposeData(ObjectSerializer serializer)
{
serializer.DataFieldCached(ref _offset, "offset", Vector2.Zero);
serializer.DataFieldCached(ref _radius, "radius", 5f);
serializer.DataFieldCached(ref _color, "color", Color.White);
serializer.DataFieldCached(ref _enabled, "enabled", true);
serializer.DataFieldCached(ref _energy, "energy", 1f);
serializer.DataFieldCached(ref _softness, "softness", 1f);
serializer.DataFieldCached(ref _maskAutoRotate, "autoRot", false);
serializer.DataFieldCached(ref _visibleNested, "nestedvisible", true);
serializer.DataFieldCached(ref _maskPath, "mask", null);
}
public override void OnRemove()
{
base.OnRemove();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -17,6 +17,8 @@ using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -25,8 +27,12 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
namespace Robust.Client.GameObjects
{
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent,
IComponentDebug
IComponentDebug, ISerializationHooks
{
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[DataField("visible")]
private bool _visible = true;
[ViewVariables(VVAccess.ReadWrite)]
@@ -36,6 +42,7 @@ namespace Robust.Client.GameObjects
set => _visible = value;
}
[DataFieldWithConstant("drawdepth", typeof(DrawDepthTag))]
private int drawDepth = DrawDepthTag.Default;
/// <summary>
@@ -48,6 +55,7 @@ namespace Robust.Client.GameObjects
set => drawDepth = value;
}
[DataField("scale")]
private Vector2 scale = Vector2.One;
/// <summary>
@@ -61,7 +69,8 @@ namespace Robust.Client.GameObjects
set => scale = value;
}
private Angle rotation;
[DataField("rotation")]
private Angle rotation = Angle.Zero;
[Animatable]
[ViewVariables(VVAccess.ReadWrite)]
@@ -71,6 +80,7 @@ namespace Robust.Client.GameObjects
set => rotation = value;
}
[DataField("offset")]
private Vector2 offset = Vector2.Zero;
/// <summary>
@@ -84,6 +94,7 @@ namespace Robust.Client.GameObjects
set => offset = value;
}
[DataField("color")]
private Color color = Color.White;
[Animatable]
@@ -108,18 +119,152 @@ namespace Robust.Client.GameObjects
set => _directional = value;
}
[DataField("directional")]
private bool _directional = true;
[DataField("layerDatums")]
private List<PrototypeLayerData> LayerDatums
{
get
{
var layerDatums = new List<PrototypeLayerData>();
foreach (var layer in Layers)
{
layerDatums.Add(layer.ToPrototypeData());
}
return layerDatums;
}
set
{
if(value == null) return;
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;
try
{
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
}
catch
{
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;
// 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);
}
}
}
_layerMapShared = true;
UpdateIsInert();
}
}
private RSI? _baseRsi;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("rsi", priority: 2)]
public RSI? BaseRSI
{
get => _baseRsi;
set
{
_baseRsi = value;
if (Layers == null || value == null)
if (value == null)
{
return;
}
@@ -147,6 +292,12 @@ namespace Robust.Client.GameObjects
}
}
[DataField("sprite", readOnly: true)] private string? rsi;
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
[DataField("state", readOnly: true)] private string? state;
[DataField("texture", readOnly: true)] private string? texture;
[ViewVariables(VVAccess.ReadWrite)]
public bool ContainerOccluded { get; set; }
@@ -158,11 +309,7 @@ namespace Robust.Client.GameObjects
[ViewVariables] private Dictionary<object, int> LayerMap = new();
[ViewVariables] private bool _layerMapShared;
[ViewVariables] private List<Layer> Layers = default!;
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[Dependency] private readonly IReflectionManager reflectionManager = default!;
[ViewVariables] private List<Layer> Layers = new();
[ViewVariables(VVAccess.ReadWrite)] public uint RenderOrder { get; set; }
@@ -170,10 +317,9 @@ namespace Robust.Client.GameObjects
private static ShaderInstance? _defaultShader;
[ViewVariables]
private ShaderInstance? DefaultShader => _defaultShader ??
(_defaultShader = prototypes
.Index<ShaderPrototype>("shaded")
.Instance());
private ShaderInstance? DefaultShader => _defaultShader ??= prototypes
.Index<ShaderPrototype>("shaded")
.Instance();
public const string LogCategory = "go.comp.sprite";
const string LayerSerializationCache = "spritelayer";
@@ -181,6 +327,46 @@ namespace Robust.Client.GameObjects
[ViewVariables(VVAccess.ReadWrite)] public bool IsInert { get; private set; }
void ISerializationHooks.AfterDeserialization()
{
{
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
try
{
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
}
catch (Exception e)
{
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
}
if (layerDatums.Count == 0)
{
if (state != null || texture != null)
{
layerDatums.Insert(0, new PrototypeLayerData
{
TexturePath = string.IsNullOrWhiteSpace(texture) ? null : texture,
State = string.IsNullOrWhiteSpace(state) ? null : state,
Color = Color.White,
Scale = Vector2.One,
Visible = true,
});
state = null;
texture = null;
}
}
if (layerDatums.Count != 0)
{
LayerDatums = layerDatums;
}
}
/// <summary>
/// Update this sprite component to visibly match the current state of other at the time
/// this is called. Does not keep them perpetually in sync.
@@ -1005,9 +1191,14 @@ namespace Robust.Client.GameObjects
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
}
private bool _screenLock = false;
private Direction _overrideDirection = Direction.South;
private bool _enableOverrideDirection = false;
[DataField("noRot")]
private bool _screenLock = true;
[DataField("overrideDir")]
private Direction _overrideDirection = Direction.East;
[DataField("enableOverrideDir")]
private bool _enableOverrideDirection;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
@@ -1153,205 +1344,6 @@ namespace Robust.Client.GameObjects
return texture;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref scale, "scale", Vector2.One);
serializer.DataFieldCached(ref rotation, "rotation", Angle.Zero);
serializer.DataFieldCached(ref offset, "offset", Vector2.Zero);
serializer.DataFieldCached(ref drawDepth, "drawdepth", DrawDepthTag.Default,
WithFormat.Constants<DrawDepthTag>());
serializer.DataFieldCached(ref color, "color", Color.White);
serializer.DataFieldCached(ref _visible, "visible", true);
serializer.DataFieldCached(ref _directional, "directional", true); //TODO: Kill ME
serializer.DataFieldCached(ref _screenLock, "noRot", true);
serializer.DataFieldCached(ref _enableOverrideDirection, "enableOverrideDir", false);
serializer.DataFieldCached(ref _overrideDirection, "overrideDir", Direction.East);
// TODO: Writing?
if (!serializer.Reading)
{
return;
}
{
var rsi = serializer.ReadDataField<string?>("sprite", null);
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
try
{
BaseRSI = resourceCache.GetResource<RSIResource>(rsiPath).RSI;
}
catch (Exception e)
{
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
}
List<Layer> CloneLayers(List<Layer> source)
{
var clone = new List<Layer>(source.Count);
foreach (var layer in source)
{
clone.Add(new Layer(layer, this));
}
return clone;
}
if (serializer.TryGetCacheData<List<Layer>>(LayerSerializationCache, out var layers))
{
LayerMap = serializer.GetCacheData<Dictionary<object, int>>(LayerMapSerializationCache);
_layerMapShared = true;
Layers = CloneLayers(layers);
UpdateIsInert();
return;
}
layers = new List<Layer>();
var layerMap = new Dictionary<object, int>();
var layerData =
serializer.ReadDataField("layers", new List<PrototypeLayerData>());
if(layerData.Count == 0){
var baseState = serializer.ReadDataField<string?>("state", null);
var texturePath = serializer.ReadDataField<string?>("texture", null);
if (baseState != null || texturePath != null)
{
layerData.Insert(0, new PrototypeLayerData
{
TexturePath = string.IsNullOrWhiteSpace(texturePath) ? null : texturePath,
State = string.IsNullOrWhiteSpace(baseState) ? null : baseState,
Color = Color.White,
Scale = Vector2.One,
Visible = true,
});
}
}
foreach (var layerDatum in layerData)
{
var anyTextureAttempted = false;
var layer = new Layer(this);
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
try
{
layer.RSI = resourceCache.GetResource<RSIResource>(path).RSI;
}
catch
{
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. Prototype: '{0}'", Owner.Prototype?.ID);
}
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.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist. Prototype: '{1}'",
layerDatum.Shader, Owner.Prototype?.ID);
}
}
layer.Color = layerDatum.Color;
layer.Rotation = layerDatum.Rotation;
// 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 (reflectionManager.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);
}
}
}
Layers = layers;
LayerMap = layerMap;
_layerMapShared = true;
serializer.SetCacheData(LayerSerializationCache, CloneLayers(Layers));
serializer.SetCacheData(LayerMapSerializationCache, layerMap);
UpdateIsInert();
}
public override void OnRemove()
{
base.OnRemove();
@@ -1663,7 +1655,7 @@ namespace Robust.Client.GameObjects
Flip = 3,
}
private class Layer : ISpriteLayer
public class Layer : ISpriteLayer
{
[ViewVariables] private readonly SpriteComponent _parent;
@@ -1730,6 +1722,22 @@ namespace Robust.Client.GameObjects
RSI.StateId ISpriteLayer.RsiState { get => State; set => SetState(value); }
Texture? ISpriteLayer.Texture { get => Texture; set => SetTexture(value); }
public PrototypeLayerData ToPrototypeData()
{
return new PrototypeLayerData
{
Color = Color,
Rotation = Rotation,
Scale = Scale,
//todo Shader = Shader,
State = State.Name,
Visible = Visible,
RsiPath = RSI?.Path?.ToString(),
//todo TexturePath = Textur
//todo MapKeys
};
}
bool ISpriteLayer.Visible
{
get => Visible;
@@ -2000,7 +2008,6 @@ namespace Robust.Client.GameObjects
}
return state;
}
}
@@ -2051,7 +2058,6 @@ namespace Robust.Client.GameObjects
if (!anyTexture)
yield return resourceCache.GetFallback<TextureResource>().Texture;
}
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
@@ -2098,6 +2104,7 @@ namespace Robust.Client.GameObjects
public T AddComponent<T>() where T : Component, new()
{
var typeFactory = IoCManager.Resolve<IDynamicTypeFactoryInternal>();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var comp = (T) typeFactory.CreateInstanceUnchecked(typeof(T));
_components[typeof(T)] = comp;
comp.Owner = this;
@@ -2107,9 +2114,9 @@ namespace Robust.Client.GameObjects
_components[typeof(ISpriteComponent)] = comp;
}
if (Prototype != null && Prototype.Components.TryGetValue(comp.Name, out var node))
if (Prototype != null && Prototype.TryGetComponent<T>(comp.Name, out var node))
{
comp.ExposeData(YamlObjectSerializer.NewReader(node));
comp = serializationManager.Copy(node, comp)!;
}
return comp;

View File

@@ -6,41 +6,31 @@ using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Client.GameObjects
{
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent
public class ClientUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
private readonly Dictionary<object, BoundUserInterface> _openInterfaces =
new();
private Dictionary<object, PrototypeData> _interfaceData = default!;
#pragma warning disable 649
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
#pragma warning restore 649
private readonly Dictionary<object, PrototypeData> _interfaces = new();
public override void ExposeData(ObjectSerializer serializer)
[DataField("interfaces", readOnly: true)]
private List<PrototypeData> _interfaceData = new();
void ISerializationHooks.AfterDeserialization()
{
base.ExposeData(serializer);
_interfaces.Clear();
const string cache = "ui_cache";
if (serializer.TryGetCacheData<Dictionary<object, PrototypeData>>(cache, out var interfaceData))
foreach (var data in _interfaceData)
{
_interfaceData = interfaceData;
return;
_interfaces[data.UiKey] = data;
}
var data = serializer.ReadDataFieldCached("interfaces", new List<PrototypeData>());
interfaceData = new Dictionary<object, PrototypeData>();
foreach (var prototypeData in data)
{
interfaceData[prototypeData.UiKey] = prototypeData;
}
serializer.SetCacheData(cache, interfaceData);
_interfaceData = interfaceData;
}
public override void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel,
@@ -81,7 +71,7 @@ namespace Robust.Client.GameObjects
private void OpenInterface(BoundInterfaceMessageWrapMessage wrapped)
{
var data = _interfaceData[wrapped.UiKey];
var data = _interfaces[wrapped.UiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
var boundInterface = (BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[]{this, wrapped.UiKey});

View File

@@ -9,7 +9,7 @@ namespace Robust.Client.GameObjects
public static class EntityManagerExt
{
public static void RaisePredictiveEvent<T>(this IEntityManager entityManager, T msg)
where T : EntitySystemMessage
where T : EntityEventArgs
{
var localPlayer = IoCManager.Resolve<IPlayerManager>().LocalPlayer;
DebugTools.AssertNotNull(localPlayer);

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Client.Audio;
@@ -11,12 +10,14 @@ using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Client.GameObjects
{
[UsedImplicitly]
public class AudioSystem : EntitySystem
public class AudioSystem : EntitySystem, IAudioSystem
{
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -24,17 +25,21 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
private readonly List<PlayingStream> _playingClydeStreams = new();
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
public int OcclusionCollisionMask;
private readonly List<PlayingStream> _playingClydeStreams = new();
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<PlayAudioEntityMessage>(PlayAudioEntityHandler);
SubscribeNetworkEvent<PlayAudioGlobalMessage>(PlayAudioGlobalHandler);
SubscribeNetworkEvent<PlayAudioPositionalMessage>(PlayAudioPositionalHandler);
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
private void StopAudioMessageHandler(StopAudioMessageClient ev)
@@ -141,7 +146,7 @@ namespace Robust.Client.GameObjects
var occlusion = 0f;
if (sourceRelative.Length > 0)
{
occlusion = IoCManager.Resolve<IPhysicsManager>().IntersectRayPenetration(
occlusion = _broadPhaseSystem.IntersectRayPenetration(
pos.MapId,
new CollisionRay(
pos.Position,
@@ -176,7 +181,6 @@ namespace Robust.Client.GameObjects
{
stream.Source.Dispose();
stream.Done = true;
stream.DoPlaybackDone();
}
/// <summary>
@@ -335,92 +339,30 @@ namespace Robust.Client.GameObjects
{
Source.StopPlaying();
}
public event Action? PlaybackDone;
public void DoPlaybackDone()
{
PlaybackDone?.Invoke();
}
}
}
public interface IPlayingAudioStream
{
void Stop();
event Action PlaybackDone;
}
public static class AudioSystemExtensions
{
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this IEntity entity,
string filename,
AudioParams? audioParams,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(filename, entity, audioParams);
}
/// <summary>
/// Play an audio stream following an entity.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this IEntity entity,
AudioStream stream,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
/// <inheritdoc />
public int DefaultSoundRange => 25;
/// <inheritdoc />
public int OcclusionCollisionMask { get; set; }
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(stream, entity, audioParams);
return Play(filename, audioParams);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this EntityCoordinates coordinates,
string filename,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(filename, coordinates, audioParams);
return Play(filename, entity, audioParams);
}
/// <summary>
/// Play an audio stream at a static position.
/// </summary>
/// <param name="stream">The audio stream to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static IPlayingAudioStream? Play(
this EntityCoordinates coordinates,
AudioStream stream,
AudioParams? audioParams = null,
AudioSystem? audioSystem = null)
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
return audioSystem.Play(stream, coordinates, audioParams);
return Play(filename, coordinates, audioParams);
}
}
}

View File

@@ -4,7 +4,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
public class ContainerSystem : EntitySystem
public class ClientContainerSystem : ContainerSystem
{
private readonly HashSet<IEntity> _updateQueue = new();
@@ -91,14 +91,4 @@ namespace Robust.Client.GameObjects
}
}
}
internal readonly struct UpdateContainerOcclusionMessage
{
public UpdateContainerOcclusionMessage(IEntity entity)
{
Entity = entity;
}
public IEntity Entity { get; }
}
}

View File

@@ -106,7 +106,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Event raised by a <see cref="ClientOccluderComponent"/> when it needs to be recalculated.
/// </summary>
internal sealed class OccluderDirtyEvent : EntitySystemMessage
internal sealed class OccluderDirtyEvent : EntityEventArgs
{
public OccluderDirtyEvent(IEntity sender, (GridId grid, Vector2i pos)? lastPosition, SnapGridOffset offset)
{

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Enums;
namespace Robust.Client.GameObjects
{
@@ -42,7 +43,7 @@ namespace Robust.Client.GameObjects
{
base.Shutdown();
overlayManager.RemoveOverlay("EffectSystem");
overlayManager.RemoveOverlay(typeof(EffectOverlay));
}
public void CreateEffect(EffectSystemMessage message)
@@ -329,7 +330,6 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _unshadedShader;
@@ -337,8 +337,7 @@ namespace Robust.Client.GameObjects
private readonly IMapManager _mapManager;
private readonly IEntityManager _entityManager;
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager) : base(
"EffectSystem")
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager)
{
_owner = owner;
_unshadedShader = protoMan.Index<ShaderPrototype>("unshaded").Instance();

View File

@@ -157,7 +157,7 @@ namespace Robust.Client.GameObjects
/// <summary>
/// Entity system message that is raised when the player changes attached entities.
/// </summary>
public class PlayerAttachSysMessage : EntitySystemMessage
public class PlayerAttachSysMessage : EntityEventArgs
{
/// <summary>
/// New entity the player is attached to.

View File

@@ -0,0 +1,51 @@
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects
{
public class VelocityDebugSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
internal bool Enabled { get; set; }
private Label _label = default!;
public override void Initialize()
{
base.Initialize();
_label = new Label();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled)
{
_label.Visible = false;
return;
}
var player = _playerManager.LocalPlayer?.ControlledEntity;
if (player == null || !player.TryGetComponent(out PhysicsComponent? body))
{
_label.Visible = false;
return;
}
var screenPos = _eyeManager.WorldToScreen(player.Transform.WorldPosition);
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
_label.Visible = true;
_label.Text = $"Speed: {body.LinearVelocity.Length}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular:{body.AngularVelocity}";
}
}
}

View File

@@ -27,7 +27,7 @@ namespace Robust.Client.GameStates
private uint _nextInputCmdSeq = 1;
private readonly Queue<FullInputCmdMessage> _pendingInputs = new();
private readonly Queue<(uint sequence, GameTick sourceTick, EntitySystemMessage msg, object sessionMsg)>
private readonly Queue<(uint sequence, GameTick sourceTick, EntityEventArgs msg, object sessionMsg)>
_pendingSystemMessages
= new();
@@ -126,7 +126,7 @@ namespace Robust.Client.GameStates
_nextInputCmdSeq++;
}
public uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage
public uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs
{
if (!Predicting)
{
@@ -298,7 +298,7 @@ namespace Robust.Client.GameStates
// because the rest of the main loop will call into it with the target tick later,
// and it won't be a past prediction.
_entitySystemManager.Update((float) _timing.TickPeriod.TotalSeconds);
((IEntityEventBus) _entities.EventBus).ProcessEventQueue();
((IBroadcastEventBusInternal) _entities.EventBus).ProcessEventQueue();
}
}
}

View File

@@ -70,6 +70,6 @@ namespace Robust.Client.GameStates
/// <param name="message">Message being dispatched.</param>
void InputCommandDispatched(FullInputCmdMessage message);
uint SystemMessageDispatched<T>(T message) where T : EntitySystemMessage;
uint SystemMessageDispatched<T>(T message) where T : EntityEventArgs;
}
}

View File

@@ -3,6 +3,7 @@ using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -33,7 +34,7 @@ namespace Robust.Client.GameStates
private readonly int _lineHeight;
private readonly List<NetEntity> _netEnts = new();
public NetEntityOverlay() : base(nameof(NetEntityOverlay))
public NetEntityOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -179,11 +180,10 @@ namespace Robust.Client.GameStates
return Color.Green; // Entity in PVS, but not updated recently.
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
@@ -238,14 +238,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetEntityOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.AddOverlay(new NetEntityOverlay());
shell.WriteLine("Enabled network entity report overlay.");
}
else if(!bValue && overlayMan.HasOverlay(nameof(NetEntityOverlay)))
else if(!bValue && overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetEntityOverlay));
overlayMan.RemoveOverlay(typeof(NetEntityOverlay));
shell.WriteLine("Disabled network entity report overlay.");
}
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -34,7 +36,7 @@ namespace Robust.Client.GameStates
private readonly List<(GameTick Tick, int Payload, int lag, int interp)> _history = new(HistorySize+10);
public NetGraphOverlay() : base(nameof(NetGraphOverlay))
public NetGraphOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -142,11 +144,11 @@ namespace Robust.Client.GameStates
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
@@ -183,14 +185,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetGraphOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.AddOverlay(new NetGraphOverlay());
shell.WriteLine("Enabled network overlay.");
}
else if(overlayMan.HasOverlay(nameof(NetGraphOverlay)))
else if(overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetGraphOverlay));
overlayMan.RemoveOverlay(typeof(NetGraphOverlay));
shell.WriteLine("Disabled network overlay.");
}
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
@@ -5,6 +6,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using System;
using Robust.Shared.Timing;
namespace Robust.Client.GameStates
@@ -18,7 +20,7 @@ namespace Robust.Client.GameStates
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
public NetInterpOverlay() : base(nameof(NetInterpOverlay))
public NetInterpOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
@@ -29,7 +31,7 @@ namespace Robust.Client.GameStates
handle.UseShader(_shader);
var worldHandle = (DrawingHandleWorld) handle;
var viewport = _eyeManager.GetWorldViewport();
foreach (var boundingBox in _componentManager.EntityQuery<IPhysicsComponent>(true))
foreach (var boundingBox in _componentManager.EntityQuery<IPhysBody>(true))
{
// all entities have a TransformComponent
var transform = ((IComponent)boundingBox).Owner.Transform;
@@ -42,7 +44,7 @@ namespace Robust.Client.GameStates
if(transform.LerpDestination == null)
continue;
var aabb = ((IPhysBody)boundingBox).AABB;
var aabb = boundingBox.GetWorldAABB();
// if not on screen, or too small, continue
if (!aabb.Translated(transform.WorldPosition).Intersects(viewport) || aabb.IsEmpty())
@@ -85,14 +87,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if (bValue && !overlayMan.HasOverlay(nameof(NetInterpOverlay)))
if (bValue && !overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.AddOverlay(new NetInterpOverlay());
shell.WriteLine("Enabled network interp overlay.");
}
else if (overlayMan.HasOverlay(nameof(NetInterpOverlay)))
else if (overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.RemoveOverlay(nameof(NetInterpOverlay));
overlayMan.RemoveOverlay<NetInterpOverlay>();
shell.WriteLine("Disabled network interp overlay.");
}
}

View File

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

View File

@@ -12,7 +12,8 @@ namespace Robust.Client.Graphics.Clyde
static Clyde()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
RuntimeInformation.ProcessArchitecture == Architecture.X64)
RuntimeInformation.ProcessArchitecture == Architecture.X64 &&
Environment.GetEnvironmentVariable("ROBUST_INTEGRATED_GPU") != "1")
{
try
{

View File

@@ -7,6 +7,8 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Enums;
namespace Robust.Client.Graphics.Clyde
{
@@ -68,8 +70,7 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(OverlaySpace.ScreenSpaceBelowWorld);
_mainViewport.Eye = _eyeManager.CurrentEye;
RenderViewport(_mainViewport);
RenderViewport(_mainViewport); //Worldspace overlays are rendered here.
{
var handle = _renderHandle.DrawingHandleScreen;
var tex = _mainViewport.RenderTarget.Texture;
@@ -107,25 +108,67 @@ namespace Robust.Client.Graphics.Clyde
list.Add(overlay);
}
}
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list)
{
overlay.ClydeRender(_renderHandle, space);
}
FlushRenderQueue();
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list) {
if (overlay.RequestScreenTexture) {
FlushRenderQueue();
UpdateOverlayScreenTexture(space, _mainViewport.RenderTarget);
}
if (overlay.OverwriteTargetFrameBuffer()) {
ClearFramebuffer(default);
}
overlay.ClydeRender(_renderHandle, space);
FlushRenderQueue();
}
}
}
private void DrawEntitiesAndWorldOverlay(Viewport viewport, Box2 worldBounds)
private ClydeTexture? ScreenBufferTexture;
private GLHandle screenBufferHandle;
private Vector2 lastFrameSize;
/// <summary>
/// Sends SCREEN_TEXTURE to all overlays in the given OverlaySpace that request it.
/// </summary>
private bool UpdateOverlayScreenTexture(OverlaySpace space, RenderTexture texture) {
//This currently does NOT consider viewports and just grabs the current screen framebuffer. This will need to be improved upon in the future.
List<Overlay> oTargets = new List<Overlay>();
foreach (var overlay in _overlayManager.AllOverlays) {
if (overlay.RequestScreenTexture && overlay.Space == space) {
oTargets.Add(overlay);
}
}
if (oTargets.Count > 0 && ScreenBufferTexture != null) {
if (lastFrameSize != _framebufferSize) {
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Srgb8Alpha8, _framebufferSize.X, _framebufferSize.Y, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
}
lastFrameSize = _framebufferSize;
CopyRenderTextureToTexture(texture, ScreenBufferTexture);
foreach (Overlay overlay in oTargets) {
overlay.ScreenTexture = ScreenBufferTexture;
}
oTargets.Clear();
return true;
}
return false;
}
private void DrawEntities(Viewport viewport, Box2 worldBounds)
{
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
{
return;
}
RenderOverlays(OverlaySpace.WorldSpaceBelowEntities);
var screenSize = viewport.Size;
// So we could calculate the correct size of the entities based on the contents of their sprite...
@@ -267,14 +310,6 @@ namespace Robust.Client.Graphics.Clyde
_drawingSpriteList.Clear();
FlushRenderQueue();
// Cleanup remainders
foreach (var overlay in worldOverlays)
{
overlay.ClydeRender(_renderHandle, OverlaySpace.WorldSpace);
}
FlushRenderQueue();
}
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -368,12 +403,21 @@ namespace Robust.Client.Graphics.Clyde
// We will also render worldspace overlays here so we can do them under / above entities as necessary
using (DebugGroup("Entities"))
{
DrawEntitiesAndWorldOverlay(viewport, worldBounds);
DrawEntities(viewport, worldBounds);
}
RenderOverlays(OverlaySpace.WorldSpaceBelowFOV);
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
GL.Clear(ClearBufferMask.StencilBufferBit);
GL.Enable(EnableCap.StencilTest);
GL.StencilOp(OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Replace);
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
GL.StencilMask(0xFF);
ApplyFovToBuffer(viewport, eye);
GL.StencilMask(0x00);
GL.Disable(EnableCap.StencilTest);
}
}
@@ -401,6 +445,14 @@ namespace Robust.Client.Graphics.Clyde
viewport.WallBleedIntermediateRenderTarget2.Texture,
UIBox2.FromDimensions(Vector2.Zero, ScreenSize), new Color(1, 1, 1, 0.5f));
}
RenderOverlays(OverlaySpace.WorldSpace);
GL.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
GL.Disable(EnableCap.DepthTest);
RenderOverlays(OverlaySpace.WorldSpaceFOVStencil);
GL.Disable(EnableCap.StencilTest);
}
PopRenderStateFull(state);

View File

@@ -5,6 +5,7 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
using ES20 = OpenToolkit.Graphics.ES20;
namespace Robust.Client.Graphics.Clyde
@@ -31,6 +32,27 @@ namespace Robust.Client.Graphics.Clyde
CheckGlError();
GL.BindTexture(TextureTarget.Texture2D, glHandle.Handle);
CheckGlError();
GL.ActiveTexture(TextureUnit.Texture0);
}
private void CopyRenderTextureToTexture(RenderTexture source, ClydeTexture target) {
LoadedRenderTarget sourceLoaded = RtToLoaded(source);
bool pause = sourceLoaded != _currentBoundRenderTarget;
FullStoredRendererState? store = null;
if (pause) {
store = PushRenderStateFull();
BindRenderTargetFull(source);
CheckGlError();
}
GL.BindTexture(TextureTarget.Texture2D, _loadedTextures[target.TextureId].OpenGLObject.Handle);
CheckGlError();
GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, _framebufferSize.X, _framebufferSize.Y);
CheckGlError();
if (pause && store != null) {
PopRenderStateFull((FullStoredRendererState)store);
}
}
private static long EstPixelSize(PixelInternalFormat format)

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Buffers;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.GameObjects;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
using Robust.Shared.Map;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

View File

@@ -280,7 +280,7 @@ namespace Robust.Client.Graphics.Clyde
}
/// <summary>
/// Flush the render handle, processing and re-pooling all the command lists.
/// Flushes the render handle, processing and re-pooling all the command lists.
/// </summary>
private void FlushRenderQueue()
{
@@ -371,6 +371,7 @@ namespace Robust.Client.Graphics.Clyde
program.Use();
int textureUnitVal = 0;
// Assign shader parameters to uniform since they may be dirty.
foreach (var (name, value) in instance.Parameters)
{
@@ -413,6 +414,15 @@ namespace Robust.Client.Graphics.Clyde
case Matrix4 matrix4:
program.SetUniform(name, matrix4);
break;
case ClydeTexture clydeTexture:
//It's important to start at Texture6 here since DrawCommandBatch uses Texture0 and Texture1 immediately after calling this
//function! If passing in textures as uniforms ever stops working it might be since someone made it use all the way up to Texture6 too.
//Might change this in the future?
TextureUnit cTarget = TextureUnit.Texture6+textureUnitVal;
SetTexture(cTarget, ((ClydeTexture)clydeTexture).TextureId);
program.SetUniformTexture(name, cTarget);
textureUnitVal++;
break;
default:
throw new InvalidOperationException($"Unable to handle shader parameter {name}: {value}");
}

View File

@@ -1,10 +1,10 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.ResourceManagement;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using StencilOp = Robust.Client.Graphics.StencilOp;
@@ -428,7 +428,8 @@ namespace Robust.Client.Graphics.Clyde
private protected override void SetParameterImpl(string name, Texture value)
{
throw new NotImplementedException();
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetStencilOpImpl(StencilOp op)

View File

@@ -56,19 +56,19 @@ namespace Robust.Client.Graphics.Clyde
{
// Disable sRGB so stuff doesn't get interpreter wrong.
actualParams.Srgb = false;
var img = ApplyA8Swizzle((Image<A8>) (object) image);
using var img = ApplyA8Swizzle((Image<A8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
if (pixelType == typeof(L8) && !actualParams.Srgb)
{
var img = ApplyL8Swizzle((Image<L8>) (object) image);
using var img = ApplyL8Swizzle((Image<L8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
}
// Flip image because OpenGL reads images upside down.
var copy = FlipClone(image);
using var copy = FlipClone(image);
var texture = CreateBaseTextureInternal<T>(image.Width, image.Height, actualParams, name);

View File

@@ -315,6 +315,11 @@ namespace Robust.Client.Graphics.Clyde
UniformConstantsUBO = new GLUniformBuffer<UniformConstants>(this, BindingIndexUniformConstants, nameof(UniformConstantsUBO));
CreateMainViewport();
screenBufferHandle = new GLHandle(GL.GenTexture());
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
ApplySampleParameters(TextureSampleParameters.Default);
ScreenBufferTexture = GenTexture(screenBufferHandle, _framebufferSize, true, null, TexturePixelType.Rgba32);
}
private void CreateMainViewport()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.Graphics.OpenGL4;

View File

@@ -1,22 +1,29 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
[PublicAPI]
public interface IOverlayManager
{
void AddOverlay(Overlay overlay);
void RemoveOverlay(string id);
bool HasOverlay(string id);
bool AddOverlay(Overlay overlay);
Overlay GetOverlay(string id);
T GetOverlay<T>(string id) where T : Overlay;
bool RemoveOverlay(Overlay overlay);
bool RemoveOverlay(Type overlayClass);
bool RemoveOverlay<T>() where T : Overlay;
bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay);
bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay;
bool TryGetOverlay(Type overlayClass, out Overlay? overlay);
bool TryGetOverlay<T>(out T? overlay) where T : Overlay;
Overlay GetOverlay(Type overlayClass);
T GetOverlay<T>() where T : Overlay;
bool HasOverlay(Type overlayClass);
bool HasOverlay<T>() where T : Overlay;
IEnumerable<Overlay> AllOverlays { get; }
}

View File

@@ -1,65 +1,54 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using JetBrains.Annotations;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using System;
namespace Robust.Client.Graphics
{
/// <summary>
/// An overlay is used for fullscreen drawing in the game, for example parallax.
/// An overlay is used for fullscreen drawing in the game. This can range from drawing parallax to a full screen shader.
/// </summary>
[PublicAPI]
public abstract class Overlay
{
/// <summary>
/// The ID of this overlay. This is used to identify it inside the <see cref="IOverlayManager"/>.
/// Determines when this overlay is drawn in the rendering queue.
/// </summary>
public string ID { get; }
public virtual bool AlwaysDirty => false;
public bool IsDirty => AlwaysDirty || _isDirty;
public bool Drawing { get; private set; }
public virtual OverlaySpace Space => OverlaySpace.ScreenSpace;
/// <summary>
/// If set to true, <see cref="ScreenTexture"/> will be set to the current frame (at the moment before the overlay is rendered). This can be costly to performance, but
/// some shaders will require it as a passed in uniform to operate.
/// </summary>
public virtual bool RequestScreenTexture => false;
/// <summary>
/// If <see cref="RequestScreenTexture"> is true, then this will be set to the texture corresponding to the current frame. If false, it will always be null.
/// </summary>
public Texture? ScreenTexture = null;
/// <summary>
/// Overlays on the same OverlaySpace will be drawn from lowest ZIndex to highest ZIndex. As an example, ZIndex -1 will be drawn before ZIndex 2.
/// This value is 0 by default. Overlays with same ZIndex will be drawn in an random order.
/// </summary>
public int? ZIndex { get; set; }
protected IOverlayManager OverlayManager { get; }
public int? ZIndex { get; set; }
private bool Disposed = false;
public virtual bool SubHandlesUseMainShader { get; } = true;
private bool _isDirty = true;
private readonly List<DrawingHandleBase> TempHandles = new();
private bool Disposed;
protected Overlay(string id)
public Overlay()
{
OverlayManager = IoCManager.Resolve<IOverlayManager>();
ID = id;
}
public void Dispose()
{
if (Disposed)
{
return;
}
Dispose(true);
Disposed = true;
GC.SuppressFinalize(this);
}
~Overlay()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
/// <summary>
/// If this function returns true, the target framebuffer will be wiped before applying this overlay to it.
/// </summary>
public virtual bool OverwriteTargetFrameBuffer(){
return false;
}
/// <summary>
@@ -69,50 +58,34 @@ namespace Robust.Client.Graphics
/// <param name="currentSpace">Current space that is being drawn. This function is called for every space that was set up in initialization.</param>
protected abstract void Draw(DrawingHandleBase handle, OverlaySpace currentSpace);
public void Dirty()
{
_isDirty = true;
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
~Overlay() {
Dispose();
}
public void Dispose() {
if (Disposed)
return;
else
DisposeBehavior();
}
protected virtual void DisposeBehavior(){
Disposed = true;
GC.SuppressFinalize(this);
}
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
internal void ClydeRender(IRenderHandle renderHandle, OverlaySpace currentSpace)
{
DrawingHandleBase handle;
if (currentSpace == OverlaySpace.WorldSpace)
handle = renderHandle.DrawingHandleWorld;
else
if (currentSpace == OverlaySpace.ScreenSpace || currentSpace == OverlaySpace.ScreenSpaceBelowWorld)
handle = renderHandle.DrawingHandleScreen;
else
handle = renderHandle.DrawingHandleWorld;
Draw(handle, currentSpace);
}
}
/// <summary>
/// Determines in which canvas layers an overlay gets drawn.
/// </summary>
[Flags]
public enum OverlaySpace : byte
{
/// <summary>
/// Used for matching bit flags.
/// </summary>
None = 0b0000,
/// <summary>
/// This overlay will be drawn in the UI root, thus being in screen space.
/// </summary>
ScreenSpace = 0b0001,
/// <summary>
/// This overlay will be drawn in the world root, thus being in world space.
/// </summary>
WorldSpace = 0b0010,
/// <summary>
/// Drawn in screen coordinates, but behind the world.
/// </summary>
ScreenSpaceBelowWorld = 0b0100,
}
}

View File

@@ -1,13 +1,15 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
internal class OverlayManager : IOverlayManagerInternal
{
private readonly Dictionary<string, Overlay> _overlays = new();
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public void FrameUpdate(FrameEventArgs args)
{
@@ -17,59 +19,80 @@ namespace Robust.Client.Graphics
}
}
public void AddOverlay(Overlay overlay)
public bool AddOverlay(Overlay overlay)
{
if (_overlays.ContainsKey(overlay.ID))
{
throw new InvalidOperationException($"We already have an overlay with ID '{overlay.ID}'");
if(_overlays.ContainsKey(overlay.GetType()))
return false;
_overlays.Add(overlay.GetType(), overlay);
return true;
}
public bool RemoveOverlay(Type overlayClass)
{
if(!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("RemoveOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
_overlays.Add(overlay.ID, overlay);
return _overlays.Remove(overlayClass);
}
public Overlay GetOverlay(string id)
{
return _overlays[id];
public bool RemoveOverlay<T>() where T : Overlay{
return RemoveOverlay(typeof(T));
}
public T GetOverlay<T>(string id) where T : Overlay
{
return (T) GetOverlay(id);
public bool RemoveOverlay(Overlay overlay) {
return _overlays.Remove(overlay.GetType());
}
public bool HasOverlay(string id)
{
return _overlays.ContainsKey(id);
}
public void RemoveOverlay(string id)
public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay)
{
if (!_overlays.TryGetValue(id, out var overlay))
{
return;
overlay = null;
if (!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("TryGetOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
overlay.Dispose();
_overlays.Remove(id);
return _overlays.TryGetValue(overlayClass, out overlay);
}
public bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay)
{
return _overlays.TryGetValue(id, out overlay);
}
public bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay
{
if (_overlays.TryGetValue(id, out var value))
{
overlay = (T) value;
public bool TryGetOverlay<T>([NotNullWhen(true)] out T? overlay) where T : Overlay {
overlay = null;
if(_overlays.TryGetValue(typeof(T), out Overlay? toReturn)){
overlay = (T)toReturn;
return true;
}
overlay = default;
return false;
}
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public Overlay GetOverlay(Type overlayClass) {
return _overlays[overlayClass];
}
public T GetOverlay<T>() where T : Overlay {
return (T)_overlays[typeof(T)];
}
public bool HasOverlay(Type overlayClass) {
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
Logger.Error("HasOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return _overlays.Remove(overlayClass);
}
public bool HasOverlay<T>() where T : Overlay {
return _overlays.Remove(typeof(T));
}
}
}

View File

@@ -19,10 +19,10 @@ namespace Robust.Client.Graphics
public sealed class State : IRsiStateLike
{
// List of delays for the frame to reach the next frame.
private readonly float[] Delays;
public readonly float[] Delays;
// 2D array for the texture to use for each animation frame at each direction.
private readonly Texture[][] Icons;
public readonly Texture[][] Icons;
internal State(Vector2i size, StateId stateId, DirectionType direction, float[] delays, Texture[][] icons)
{

View File

@@ -1,47 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "RSI Image Format Validation Schema V1",
"description": "Robust Station Image",
"type": "object",
"definitions": {
"size": {
"type": "object",
"properties": {
"x": {"type": "integer", "minimum": 1},
"y": {"type": "integer", "minimum": 1}
},
"required": ["x","y"]
},
"directions": {
"type": "integer",
"enum": [1,4,8]
},
"state": {
"type": "object",
"properties": {
"name": {"type": "string"},
"flags": {"type": "object"}, //To be de-serialized as a Dictionary
"directions": {"$ref": "#/definitions/directions"},
"delays": {
"type": "array",
"minItems": 1,
"items": {
"type": "array",
"items": {"type": "number", "minimum": 0, "exclusiveMinimum": true} //number == float
}
}
},
"required": ["name"] //'delays' is marked as optional in the spec
}
},
"properties": {
"version": {"type": "integer", "minimum": 1, "maximum": 1},
"size": {"$ref": "#/definitions/size"},
"states": {
"type": "array",
"items": {"$ref": "#/definitions/state"},
"minItems": 1
}
},
"required": ["version","size","states"]
}

View File

@@ -1,23 +1,26 @@
using System;
using System.Collections.Generic;
using Robust.Client.ResourceManagement;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
namespace Robust.Client.Graphics
{
[Prototype("shader")]
public sealed class ShaderPrototype : IPrototype
public sealed class ShaderPrototype : IPrototype, ISerializationHooks
{
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
public string ID { get; private set; } = default!;
[ViewVariables]
[field: DataField("id", required: true)]
public string ID { get; } = default!;
private ShaderKind Kind;
@@ -31,11 +34,14 @@ namespace Robust.Client.Graphics
private ShaderInstance? _cachedInstance;
private bool _stencilEnabled;
private int _stencilRef;
private int _stencilReadMask = unchecked((int) uint.MaxValue);
private int _stencilWriteMask = unchecked((int) uint.MaxValue);
private StencilFunc _stencilFunc = StencilFunc.Always;
private StencilOp _stencilOp = StencilOp.Keep;
private int _stencilRef => StencilDataHolder?.StencilRef ?? 0;
private int _stencilReadMask => StencilDataHolder?.ReadMask ?? unchecked((int) uint.MaxValue);
private int _stencilWriteMask => StencilDataHolder?.WriteMask ?? unchecked((int) uint.MaxValue);
private StencilFunc _stencilFunc => StencilDataHolder?.StencilFunc ?? StencilFunc.Always;
private StencilOp _stencilOp => StencilDataHolder?.StencilOp ?? StencilOp.Keep;
[DataField("stencil")]
private StencilData? StencilDataHolder;
/// <summary>
/// Retrieves a ready-to-use instance of this shader.
@@ -64,12 +70,12 @@ namespace Robust.Client.Graphics
switch (Kind)
{
case ShaderKind.Source:
instance = _clyde.InstanceShader(Source!.ClydeHandle);
instance = IoCManager.Resolve<IClydeInternal>().InstanceShader(Source!.ClydeHandle);
_applyDefaultParameters(instance);
break;
case ShaderKind.Canvas:
instance = _clyde.InstanceShader(CompiledCanvasShader);
instance = IoCManager.Resolve<IClydeInternal>().InstanceShader(CompiledCanvasShader);
break;
default:
@@ -95,135 +101,108 @@ namespace Robust.Client.Graphics
return Instance().Duplicate();
}
public void LoadFrom(YamlMappingNode mapping)
{
ID = mapping.GetNode("id").ToString();
[DataField("kind", readOnly: true, required: true)] private string _rawKind = default!;
[DataField("path", readOnly: true)] private ResourcePath? path;
[DataField("params", readOnly: true)] private Dictionary<string, string>? paramMapping;
[DataField("light_mode", readOnly: true)] private string? rawMode;
[DataField("blend_mode", readOnly: true)] private string? rawBlendMode;
var kind = mapping.GetNode("kind").AsString();
switch (kind)
void ISerializationHooks.AfterDeserialization()
{
switch (_rawKind)
{
case "source":
Kind = ShaderKind.Source;
ReadSourceKind(mapping);
if (path == null) throw new InvalidOperationException();
Source = IoCManager.Resolve<IResourceCache>().GetResource<ShaderSourceResource>(path);
if (paramMapping != null)
{
ShaderParams = new Dictionary<string, object>();
foreach (var item in paramMapping!)
{
var name = item.Key;
if (!Source.ParsedShader.Uniforms.TryGetValue(name, out var uniformDefinition))
{
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, path);
continue;
}
var value = _parseUniformValue(item.Value, uniformDefinition.Type.Type);
ShaderParams.Add(name, value);
}
}
break;
case "canvas":
Kind = ShaderKind.Canvas;
ReadCanvasKind(mapping);
var source = "";
if(rawMode != null)
{
switch (rawMode)
{
case "normal":
break;
case "unshaded":
source += "light_mode unshaded;\n";
break;
default:
throw new InvalidOperationException($"Invalid light mode: '{rawMode}'");
}
}
if(rawBlendMode != null){
switch (rawBlendMode)
{
case "mix":
source += "blend_mode mix;\n";
break;
case "add":
source += "blend_mode add;\n";
break;
case "subtract":
source += "blend_mode subtract;\n";
break;
case "multiply":
source += "blend_mode multiply;\n";
break;
default:
throw new InvalidOperationException($"Invalid blend mode: '{rawBlendMode}'");
}
}
source += "void fragment() {\n COLOR = zTexture(UV);\n}";
var preset = ShaderParser.Parse(source, _resourceCache);
CompiledCanvasShader = IoCManager.Resolve<IClydeInternal>().LoadShader(preset, $"canvas_preset_{ID}");
break;
default:
throw new InvalidOperationException($"Invalid shader kind: '{kind}'");
throw new InvalidOperationException($"Invalid shader kind: '{_rawKind}'");
}
// Load stencil data.
if (mapping.TryGetNode("stencil", out YamlMappingNode? stencilData))
{
ReadStencilData(stencilData);
}
if (StencilDataHolder != null) _stencilEnabled = true;
}
private void ReadStencilData(YamlMappingNode stencilData)
[DataDefinition]
public class StencilData
{
_stencilEnabled = true;
[DataField("ref")] public int StencilRef;
if (stencilData.TryGetNode("ref", out var dataNode))
{
_stencilRef = dataNode.AsInt();
}
[DataField("op")] public StencilOp StencilOp;
if (stencilData.TryGetNode("op", out dataNode))
{
_stencilOp = dataNode.AsEnum<StencilOp>();
}
[DataField("func")] public StencilFunc StencilFunc;
if (stencilData.TryGetNode("func", out dataNode))
{
_stencilFunc = dataNode.AsEnum<StencilFunc>();
}
[DataField("readMask")] public int ReadMask = unchecked((int) uint.MaxValue);
if (stencilData.TryGetNode("readMask", out dataNode))
{
_stencilReadMask = dataNode.AsInt();
}
if (stencilData.TryGetNode("writeMask", out dataNode))
{
_stencilWriteMask = dataNode.AsInt();
}
}
private void ReadSourceKind(YamlMappingNode mapping)
{
var path = mapping.GetNode("path").AsResourcePath();
Source = _resourceCache.GetResource<ShaderSourceResource>(path);
if (mapping.TryGetNode<YamlMappingNode>("params", out var paramMapping))
{
ShaderParams = new Dictionary<string, object>();
foreach (var item in paramMapping)
{
var name = item.Key.AsString();
if (!Source.ParsedShader.Uniforms.TryGetValue(name, out var uniformDefinition))
{
Logger.ErrorS("shader", "Shader param '{0}' does not exist on shader '{1}'", name, path);
continue;
}
var value = _parseUniformValue(item.Value, uniformDefinition.Type.Type);
ShaderParams.Add(name, value);
}
}
}
private void ReadCanvasKind(YamlMappingNode mapping)
{
var source = "";
if (mapping.TryGetNode("light_mode", out var node))
{
switch (node.AsString())
{
case "normal":
break;
case "unshaded":
source += "light_mode unshaded;\n";
break;
default:
throw new InvalidOperationException($"Invalid light mode: '{node.AsString()}'");
}
}
if (mapping.TryGetNode("blend_mode", out node))
{
switch (node.AsString())
{
case "mix":
source += "blend_mode mix;\n";
break;
case "add":
source += "blend_mode add;\n";
break;
case "subtract":
source += "blend_mode subtract;\n";
break;
case "multiply":
source += "blend_mode multiply;\n";
break;
default:
throw new InvalidOperationException($"Invalid blend mode: '{node.AsString()}'");
}
}
source += "void fragment() {\n COLOR = zTexture(UV);\n}";
var preset = ShaderParser.Parse(source, _resourceCache);
CompiledCanvasShader = _clyde.LoadShader(preset, $"canvas_preset_{ID}");
[DataField("writeMask")] public int WriteMask = unchecked((int) uint.MaxValue);
}
private static object _parseUniformValue(YamlNode node, ShaderDataType dataType)

View File

@@ -19,6 +19,8 @@ using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.Core;
@@ -117,8 +119,8 @@ namespace Robust.Client.Input
public void SaveToUserData()
{
var mapping = new YamlMappingNode();
var ser = YamlObjectSerializer.NewWriter(mapping);
var mapping = new MappingDataNode();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var modifiedBindings = _modifiedKeyFunctions
.Select(p => _bindingsByFunction[p])
@@ -141,15 +143,13 @@ namespace Robust.Client.Input
.Where(p => _bindingsByFunction[p].Count == 0)
.ToArray();
var version = 1;
ser.DataField(ref version, "version", 1);
ser.DataField(ref modifiedBindings, "binds", Array.Empty<KeyBindingRegistration>());
ser.DataField(ref leaveEmpty, "leaveEmpty", Array.Empty<BoundKeyFunction>());
mapping.AddNode("version", new ValueDataNode("1"));
mapping.AddNode("binds", serializationManager.WriteValue(modifiedBindings));
mapping.AddNode("leaveEmpty", serializationManager.WriteValue(leaveEmpty));
var path = new ResourcePath(KeybindsPath);
using var writer = new StreamWriter(_resourceMan.UserData.Create(path));
var stream = new YamlStream {new(mapping)};
var stream = new YamlStream {new(mapping.ToMappingNode())};
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
}
@@ -416,12 +416,14 @@ namespace Robust.Client.Input
var mapping = (YamlMappingNode) yamlStream.Documents[0].RootNode;
var baseSerializer = YamlObjectSerializer.NewReader(mapping);
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var robustMapping = mapping.ToDataNode() as MappingDataNode;
if (robustMapping == null) throw new InvalidOperationException();
var foundBinds = baseSerializer.TryReadDataField<KeyBindingRegistration[]>("binds", out var baseKeyRegs);
if (foundBinds && baseKeyRegs != null && baseKeyRegs.Length > 0)
if (robustMapping.TryGetNode("binds", out var BaseKeyRegsNode))
{
var baseKeyRegs = serializationManager.ReadValueOrThrow<KeyBindingRegistration[]>(BaseKeyRegsNode);
foreach (var reg in baseKeyRegs)
{
if (!NetworkBindMap.FunctionExists(reg.Function.FunctionName))
@@ -447,11 +449,11 @@ namespace Robust.Client.Input
}
}
if (userData)
if (userData && robustMapping.TryGetNode("leaveEmpty", out var node))
{
var foundLeaveEmpty = baseSerializer.TryReadDataField<BoundKeyFunction[]>("leaveEmpty", out var leaveEmpty);
var leaveEmpty = serializationManager.ReadValueOrThrow<BoundKeyFunction[]>(node);
if (foundLeaveEmpty && leaveEmpty != null && leaveEmpty.Length > 0)
if (leaveEmpty.Length > 0)
{
// Adding to _modifiedKeyFunctions means that these keybinds won't be loaded from the base file.
// Because they've been explicitly cleared.

View File

@@ -1,33 +1,30 @@
using Robust.Shared.Input;
using Robust.Shared.Serialization;
using Robust.Shared.Input;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Client.Input
{
public struct KeyBindingRegistration : IExposeData
[DataDefinition]
public class KeyBindingRegistration
{
[DataField("function")]
public BoundKeyFunction Function;
public KeyBindingType Type;
[DataField("type")]
public KeyBindingType Type = KeyBindingType.State;
[DataField("key")]
public Keyboard.Key BaseKey;
[DataField("mod1")]
public Keyboard.Key Mod1;
[DataField("mod2")]
public Keyboard.Key Mod2;
[DataField("mod3")]
public Keyboard.Key Mod3;
[DataField("priority")]
public int Priority;
[DataField("canFocus")]
public bool CanFocus;
[DataField("canRepeat")]
public bool CanRepeat;
[DataField("allowSubCombs")]
public bool AllowSubCombs;
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Function, "function", default);
serializer.DataField(ref Type, "type", KeyBindingType.State);
serializer.DataField(ref BaseKey, "key", default);
serializer.DataField(ref Mod1, "mod1", default);
serializer.DataField(ref Mod2, "mod2", default);
serializer.DataField(ref Mod3, "mod3", default);
serializer.DataField(ref Priority, "priority", 0);
serializer.DataField(ref CanFocus, "canFocus", false);
serializer.DataField(ref CanRepeat, "canRepeat", false);
serializer.DataField(ref AllowSubCombs, "allowSubCombs", false);
}
}
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.Physics.Broadphase;
namespace Robust.Client.Physics
{
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
{
public override void Initialize()
{
base.Initialize();
UpdatesBefore.Add(typeof(PhysicsSystem));
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Timing;
namespace Robust.Client.Physics
{
internal sealed class PhysicsIslandCommand : IConsoleCommand
{
public string Command => "showislands";
public string Description => "Shows the current physics bodies involved in each physics island.";
public string Help => "showislands";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 0)
{
shell.WriteLine("This command doesn't take args!");
return;
}
EntitySystem.Get<DebugPhysicsIslandSystem>().Mode ^= DebugPhysicsIslandMode.Solve;
}
}
internal sealed class DebugPhysicsIslandSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
public DebugPhysicsIslandMode Mode { get; set; } = DebugPhysicsIslandMode.None;
/*
* Island solve debug:
* This will draw above every body involved in a particular island solve.
*/
public readonly Queue<(TimeSpan Time, List<IPhysBody> Bodies)> IslandSolve = new();
public const float SolveDuration = 0.1f;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<IslandSolveMessage>(HandleIslandSolveMessage);
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsIslandOverlay());
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
while (IslandSolve.TryPeek(out var solve))
{
if (solve.Time.TotalSeconds + SolveDuration > _gameTiming.CurTime.TotalSeconds)
{
IslandSolve.Dequeue();
}
else
{
break;
}
}
}
public override void Shutdown()
{
base.Shutdown();
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsIslandOverlay));
}
private void HandleIslandSolveMessage(IslandSolveMessage message)
{
if ((Mode & DebugPhysicsIslandMode.Solve) == 0x0) return;
IslandSolve.Enqueue((_gameTiming.CurTime, message.Bodies));
}
}
[Flags]
internal enum DebugPhysicsIslandMode : ushort
{
None = 0,
Solve = 1 << 0,
Contacts = 1 << 1,
}
internal sealed class PhysicsIslandOverlay : Overlay
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private DebugPhysicsIslandSystem _islandSystem = default!;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsIslandOverlay()
{
_islandSystem = EntitySystem.Get<DebugPhysicsIslandSystem>();
_eyeManager = IoCManager.Resolve<IEyeManager>();
_gameTiming = IoCManager.Resolve<IGameTiming>();
}
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
{
var worldHandle = (DrawingHandleWorld) handle;
DrawIslandSolve(worldHandle);
}
private void DrawIslandSolve(DrawingHandleWorld handle)
{
if ((_islandSystem.Mode & DebugPhysicsIslandMode.Solve) == 0x0) return;
var viewport = _eyeManager.GetWorldViewport();
foreach (var solve in _islandSystem.IslandSolve)
{
var ratio = (float) Math.Max(
(solve.Time.TotalSeconds + DebugPhysicsIslandSystem.SolveDuration -
_gameTiming.CurTime.TotalSeconds) / DebugPhysicsIslandSystem.SolveDuration, 0.0f);
if (ratio <= 0.0f) continue;
foreach (var body in solve.Bodies)
{
var worldAABB = body.GetWorldAABB();
if (!viewport.Intersects(worldAABB)) continue;
handle.DrawRect(worldAABB, Color.Green.WithAlpha(ratio * 0.5f));
}
}
}
}
}

View File

@@ -1,27 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Maths;
using Robust.Shared.Map;
using Robust.Shared.Network.Messages;
using Robust.Client.Graphics;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Client.Player;
using Robust.Shared.Input;
using Robust.Shared.Input.Binding;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Client.Placement
{
@@ -126,14 +124,6 @@ namespace Robust.Client.Placement
if (value != null)
{
PlacementOffset = value.PlacementOffset;
if (value.Components.ContainsKey("BoundingBox") && value.Components.ContainsKey("Physics"))
{
var map = value.Components["BoundingBox"];
var serializer = YamlObjectSerializer.NewReader(map);
serializer.DataField(ref _colliderAABB, "aabb", new Box2(0f, 0f, 0f, 0f));
return;
}
}
_colliderAABB = new Box2(0f, 0f, 0f, 0f);

View File

@@ -4,8 +4,10 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Utility;
namespace Robust.Client.Placement
@@ -230,7 +232,7 @@ namespace Robust.Client.Placement
bounds.Width,
bounds.Height);
return pManager.PhysicsManager.TryCollideRect(collisionBox, mapCoords.MapId);
return EntitySystem.Get<SharedBroadPhaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
}
protected Vector2 ScreenToWorld(Vector2 point)

View File

@@ -1,5 +1,7 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
namespace Robust.Client.Placement
{
public partial class PlacementManager
@@ -7,10 +9,9 @@ namespace Robust.Client.Placement
internal class PlacementOverlay : Overlay
{
private readonly PlacementManager _manager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PlacementOverlay(PlacementManager manager) : base("placement")
public PlacementOverlay(PlacementManager manager)
{
_manager = manager;
ZIndex = 100;

View File

@@ -207,7 +207,7 @@ namespace Robust.Client.Player
if (state.UserId == LocalPlayer!.UserId)
{
LocalPlayer.InternalSession = newSession;
newSession.ConnectedClient = _network.ServerChannel!;
// We just connected to the server, hurray!
LocalPlayer.SwitchState(SessionStatus.Connecting, newSession.Status);
}

View File

@@ -39,6 +39,9 @@ namespace Robust.Client.Player
/// <inheritdoc />
internal short Ping { get; set; }
/// <inheritdoc />
public INetChannel ConnectedClient { get; internal set; } = null!;
/// <inheritdoc />
short ICommonSession.Ping
{

View File

@@ -10,5 +10,6 @@ namespace Robust.Client.ResourceManagement
void RsiLoaded(RsiLoadedEventArgs eventArgs);
void MountLoaderApi(IFileApi api, string apiPrefix, ResourcePath? prefix=null);
void PreloadTextures();
}
}

View File

@@ -0,0 +1,187 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Robust.Client.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
namespace Robust.Client.ResourceManagement
{
internal partial class ResourceCache
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly ILogManager _logManager = default!;
public void PreloadTextures()
{
var sawmill = _logManager.GetSawmill("res.preload");
PreloadTextures(sawmill);
PreloadRsis(sawmill);
}
private void PreloadTextures(ISawmill sawmill)
{
sawmill.Debug("Preloading textures...");
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<TextureResource>();
var texList = ContentFindFiles("/Textures/")
// Skip PNG files inside RSIs.
.Where(p => p.Extension == "png" && !p.ToString().Contains(".rsi/") && !resList.ContainsKey(p))
.Select(p => new TextureResource.LoadStepData {Path = p})
.ToArray();
Parallel.ForEach(texList, data =>
{
try
{
TextureResource.LoadPreTexture(this, data);
}
catch (Exception e)
{
// Mark failed loads as bad and skip them in the next few stages.
// Avoids any silly array resizing or similar.
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
});
foreach (var data in texList)
{
if (data.Bad)
continue;
try
{
TextureResource.LoadTexture(_clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
var errors = 0;
foreach (var data in texList)
{
if (data.Bad)
{
errors += 1;
continue;
}
try
{
var texResource = new TextureResource();
texResource.LoadFinish(this, data);
resList[data.Path] = texResource;
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
errors += 1;
}
}
sawmill.Debug(
"Preloaded {CountLoaded} textures ({CountErrored} errored) in {LoadTime}",
texList.Length,
errors,
sw.Elapsed);
}
private void PreloadRsis(ISawmill sawmill)
{
var sw = Stopwatch.StartNew();
var resList = GetTypeDict<RSIResource>();
var rsiList = ContentFindFiles("/Textures/")
.Where(p => p.ToString().EndsWith(".rsi/meta.json"))
.Select(c => c.Directory)
.Where(p => !resList.ContainsKey(p))
.Select(p => new RSIResource.LoadStepData {Path = p})
.ToArray();
Parallel.ForEach(rsiList, data =>
{
try
{
RSIResource.LoadPreTexture(this, data);
}
catch (Exception e)
{
// Mark failed loads as bad and skip them in the next few stages.
// Avoids any silly array resizing or similar.
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
});
foreach (var data in rsiList)
{
if (data.Bad)
continue;
try
{
RSIResource.LoadTexture(_clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
Parallel.ForEach(rsiList, data =>
{
if (data.Bad)
return;
try
{
RSIResource.LoadPostTexture(data);
}
catch (Exception e)
{
data.Bad = true;
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
}
});
var errors = 0;
foreach (var data in rsiList)
{
if (data.Bad)
{
errors += 1;
continue;
}
try
{
var rsiRes = new RSIResource();
rsiRes.LoadFinish(this, data);
resList[data.Path] = rsiRes;
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
errors += 1;
}
}
sawmill.Debug(
"Preloaded {CountLoaded} RSIs ({CountErrored} errored) in {LoadTime}",
rsiList.Length,
errors,
sw.Elapsed);
}
}
}

View File

@@ -2,17 +2,13 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Log;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Newtonsoft.Json.Linq;
#if DEBUG
using NJsonSchema;
#endif
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -24,6 +20,14 @@ namespace Robust.Client.ResourceManagement
/// </summary>
public sealed class RSIResource : BaseResource
{
private static readonly float[] OneArray = {1};
private static readonly JsonSerializerOptions SerializerOptions =
new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
AllowTrailingCommas = true
};
public RSI RSI { get; private set; } = default!;
/// <summary>
@@ -38,64 +42,59 @@ namespace Robust.Client.ResourceManagement
public override void Load(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
var clyde = IoCManager.Resolve<IClyde>();
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
var loadStepData = new LoadStepData {Path = path};
LoadPreTexture(cache, loadStepData);
#if DEBUG
if (RSISchema != null)
{
var errors = RSISchema.Validate(manifestContents);
if (errors.Count != 0)
{
Logger.Error($"Unable to load RSI from '{path}', {errors.Count} errors:");
// Load atlas.
LoadTexture(clyde, loadStepData);
foreach (var error in errors)
{
Logger.Error("{0}", error.ToString());
}
LoadPostTexture(loadStepData);
LoadFinish(cache, loadStepData);
throw new RSILoadException($"{errors.Count} errors while loading RSI. See console.");
}
}
#endif
loadStepData.AtlasSheet.Dispose();
}
// Ok schema validated just fine.
var manifestJson = JObject.Parse(manifestContents);
internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData)
{
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString());
}
var toAtlas = new List<(Image<Rgba32> src, Texture[][] output, int[][] indices, Vector2i[][] offsets, int totalFrameCount)>();
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
{
var metadata = LoadRsiMetadata(cache, data.Path);
var metaData = ParseMetaData(manifestJson);
var frameSize = metaData.Size;
var rsi = new RSI(frameSize, path);
var stateCount = metadata.States.Length;
var toAtlas = new StateReg[stateCount];
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>();
var frameSize = metadata.Size;
var rsi = new RSI(frameSize, data.Path);
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>(stateCount);
// Do every state.
foreach (var stateObject in metaData.States)
for (var index = 0; index < metadata.States.Length; index++)
{
// Load image from disk.
var texPath = path / (stateObject.StateId + ".png");
var stream = cache.ContentFileRead(texPath);
Image<Rgba32> image;
using (stream)
{
image = Image.Load<Rgba32>(stream);
}
var sheetSize = new Vector2i(image.Width, image.Height);
ref var reg = ref toAtlas[index];
if (sheetSize.X % frameSize.X != 0 || sheetSize.Y % frameSize.Y != 0)
var stateObject = metadata.States[index];
// Load image from disk.
var texPath = data.Path / (stateObject.StateId + ".png");
using (var stream = cache.ContentFileRead(texPath))
{
reg.Src = Image.Load<Rgba32>(stream);
}
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
{
throw new RSILoadException("State image size is not a multiple of the icon size.");
}
// Load all frames into a list so we can operate on it more sanely.
var frameCount = stateObject.Delays.Sum(delayList => delayList.Length);
reg.TotalFrameCount = stateObject.Delays.Sum(delayList => delayList.Length);
var (foldedDelays, foldedIndices) = FoldDelays(stateObject.Delays);
@@ -108,29 +107,34 @@ namespace Robust.Client.ResourceManagement
callbackOffset[i] = new Vector2i[foldedIndices[0].Length];
}
var state = new RSI.State(frameSize, stateObject.StateId, stateObject.DirType, foldedDelays, textures);
reg.Output = textures;
reg.Indices = foldedIndices;
reg.Offsets = callbackOffset;
var state = new RSI.State(frameSize, stateObject.StateId, stateObject.DirType, foldedDelays,
textures);
rsi.AddState(state);
toAtlas.Add((image, textures, foldedIndices, callbackOffset, frameCount));
callbackOffsets[stateObject.StateId] = callbackOffset;
}
// Poorly hacked in texture atlas support here.
var totalFrameCount = toAtlas.Sum(p => p.totalFrameCount);
var totalFrameCount = toAtlas.Sum(p => p.TotalFrameCount);
// Generate atlas.
var dimensionX = (int) MathF.Ceiling(MathF.Sqrt(totalFrameCount));
var dimensionY = (int) MathF.Ceiling((float) totalFrameCount / dimensionX);
using var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheetIndex = 0;
foreach (var (src, _, _, _, frameCount) in toAtlas)
for (var index = 0; index < toAtlas.Length; index++)
{
ref var reg = ref toAtlas[index];
// Blit all the frames over.
for (var i = 0; i < frameCount; i++)
for (var i = 0; i < reg.TotalFrameCount; i++)
{
var srcWidth = (src.Width / frameSize.X);
var srcWidth = (reg.Src.Width / frameSize.X);
var srcColumn = i % srcWidth;
var srcRow = i / srcWidth;
var srcPos = (srcColumn * frameSize.X, srcRow * frameSize.Y);
@@ -141,30 +145,49 @@ namespace Robust.Client.ResourceManagement
var srcBox = UIBox2i.FromDimensions(srcPos, frameSize);
src.Blit(srcBox, sheet, sheetPos);
reg.Src.Blit(srcBox, sheet, sheetPos);
}
sheetIndex += frameCount;
sheetIndex += reg.TotalFrameCount;
}
// Load atlas.
var texture = Texture.LoadFromImage(sheet, path.ToString());
for (var i = 0; i < toAtlas.Length; i++)
{
ref var reg = ref toAtlas[i];
reg.Src.Dispose();
}
data.Rsi = rsi;
data.AtlasSheet = sheet;
data.AtlasList = toAtlas;
data.FrameSize = frameSize;
data.DimX = dimensionX;
data.CallbackOffsets = callbackOffsets;
}
internal static void LoadPostTexture(LoadStepData data)
{
var dimX = data.DimX;
var toAtlas = data.AtlasList;
var frameSize = data.FrameSize;
var texture = data.AtlasTexture;
var sheetOffset = 0;
foreach (var (_, output, indices, offsets, frameCount) in toAtlas)
for (var toAtlasIndex = 0; toAtlasIndex < toAtlas.Length; toAtlasIndex++)
{
for (var i = 0; i < indices.Length; i++)
ref var reg = ref toAtlas[toAtlasIndex];
for (var i = 0; i < reg.Indices.Length; i++)
{
var dirIndices = indices[i];
var dirOutput = output[i];
var dirOffsets = offsets[i];
var dirIndices = reg.Indices[i];
var dirOutput = reg.Output[i];
var dirOffsets = reg.Offsets[i];
for (var j = 0; j < dirIndices.Length; j++)
{
var index = sheetOffset + dirIndices[j];
var sheetColumn = index % dimensionX;
var sheetRow = index / dimensionX;
var sheetColumn = index % dimX;
var sheetRow = index / dimX;
var sheetPos = (sheetColumn * frameSize.X, sheetRow * frameSize.Y);
dirOffsets[j] = sheetPos;
@@ -172,22 +195,104 @@ namespace Robust.Client.ResourceManagement
}
}
sheetOffset += frameCount;
sheetOffset += reg.TotalFrameCount;
}
}
foreach (var (image, _, _, _, _) in toAtlas)
{
image.Dispose();
}
RSI = rsi;
internal void LoadFinish(IResourceCache cache, LoadStepData data)
{
RSI = data.Rsi;
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.RsiLoaded(new RsiLoadedEventArgs(path, this, sheet, callbackOffsets));
cacheInternal.RsiLoaded(new RsiLoadedEventArgs(data.Path, this, data.AtlasSheet, data.CallbackOffsets));
}
}
private static RsiMetadata LoadRsiMetadata(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
// Ok schema validated just fine.
var manifestJson = JsonSerializer.Deserialize<RsiJsonMetadata>(manifestContents, SerializerOptions);
if (manifestJson == null)
throw new RSILoadException("Manifest JSON was null!");
var size = manifestJson.Size;
var states = new StateMetadata[manifestJson.States.Length];
for (var stateI = 0; stateI < manifestJson.States.Length; stateI++)
{
var stateObject = manifestJson.States[stateI];
var stateName = stateObject.Name;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.Directions is { } dirVal)
{
dirValue = dirVal;
directions = dirVal switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.Delays != null)
{
delays = stateObject.Delays;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = OneArray;
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = OneArray;
}
}
states[stateI] = new StateMetadata(new RSI.StateId(stateName), directions, delays);
}
return new RsiMetadata(size, states);
}
/// <summary>
/// Folds a per-directional sets of animation delays
/// into an equivalent set of animation delays and indices that works for every direction.
@@ -336,109 +441,38 @@ namespace Robust.Client.ResourceManagement
return (floatDelays, arrayIndices);
}
internal static RsiMetadata ParseMetaData(JObject manifestJson)
internal sealed class LoadStepData
{
var size = manifestJson["size"]!.ToObject<Vector2i>();
var states = new List<StateMetadata>();
foreach (var stateObject in manifestJson["states"]!.Cast<JObject>())
{
var stateName = stateObject["name"]!.ToObject<string>()!;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.TryGetValue("directions", out var dirJToken))
{
dirValue= dirJToken.ToObject<int>();
directions = dirValue switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.TryGetValue("delays", out var delayToken))
{
delays = delayToken.ToObject<float[][]>()!;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = new float[] {1};
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = new float[] {1};
}
}
states.Add(new StateMetadata(new RSI.StateId(stateName), directions, delays));
}
return new RsiMetadata(size, states);
public bool Bad;
public ResourcePath Path = default!;
public Image<Rgba32> AtlasSheet = default!;
public int DimX;
public StateReg[] AtlasList = default!;
public Vector2i FrameSize;
public Dictionary<RSI.StateId, Vector2i[][]> CallbackOffsets = default!;
public Texture AtlasTexture = default!;
public RSI Rsi = default!;
}
#if DEBUG
private static readonly JsonSchema? RSISchema = GetSchema();
private static JsonSchema? GetSchema()
internal struct StateReg
{
try
{
string schema;
using (var schemaStream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Robust.Client.Graphics.RSI.RSISchema.json")!)
using (var schemaReader = new StreamReader(schemaStream))
{
schema = schemaReader.ReadToEnd();
}
return JsonSchema.FromJsonAsync(schema).Result;
}
catch (Exception e)
{
System.Console.WriteLine("Failed to load RSI JSON Schema!\n{0}", e);
return null;
}
public Image<Rgba32> Src;
public Texture[][] Output;
public int[][] Indices;
public Vector2i[][] Offsets;
public int TotalFrameCount;
}
#endif
internal sealed class RsiMetadata
{
public RsiMetadata(Vector2i size, List<StateMetadata> states)
public RsiMetadata(Vector2i size, StateMetadata[] states)
{
Size = size;
States = states;
}
public Vector2i Size { get; }
public List<StateMetadata> States { get; }
public StateMetadata[] States { get; }
}
internal sealed class StateMetadata
@@ -466,6 +500,17 @@ namespace Robust.Client.ResourceManagement
public float[][] Delays { get; }
}
// To be directly deserialized.
[UsedImplicitly]
private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States)
{
}
[UsedImplicitly]
private sealed record StateJsonMetadata(string Name, int? Directions, float[][]? Delays)
{
}
}
[Serializable]

View File

@@ -5,7 +5,7 @@ using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
namespace Robust.Client.ResourceManagement.ResourceTypes
namespace Robust.Client.ResourceManagement
{
/// <summary>
/// Loads the **source code** of a shader.

View File

@@ -1,7 +1,6 @@
using System.IO;
using Robust.Client.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -9,41 +8,50 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Client.ResourceManagement
{
public class TextureResource : BaseResource
public sealed class TextureResource : BaseResource
{
public const float ClickThreshold = 0.25f;
public override ResourcePath? Fallback => new("/Textures/noSprite.png");
public override ResourcePath Fallback => new("/Textures/noSprite.png");
public Texture Texture { get; private set; } = default!;
public override void Load(IResourceCache cache, ResourcePath path)
{
if (!cache.TryContentFileRead(path, out var stream))
{
throw new FileNotFoundException("Content file does not exist for texture");
}
var clyde = IoCManager.Resolve<IClyde>();
using (stream)
{
// Primarily for tracking down iCCP sRGB errors in the image files.
Logger.DebugS("res.tex", $"Loading texture {path}.");
var data = new LoadStepData {Path = path};
var loadParameters = _tryLoadTextureParameters(cache, path) ?? TextureLoadParameters.Default;
var manager = IoCManager.Resolve<IClyde>();
using var image = Image.Load<Rgba32>(stream);
Texture = manager.LoadTextureFromImage(image, path.ToString(), loadParameters);
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(path, image, this));
}
}
LoadPreTexture(cache, data);
LoadTexture(clyde, data);
LoadFinish(cache, data);
}
private static TextureLoadParameters? _tryLoadTextureParameters(IResourceCache cache, ResourcePath path)
internal static void LoadPreTexture(IResourceCache cache, LoadStepData data)
{
using (var stream = cache.ContentFileRead(data.Path))
{
data.Image = Image.Load<Rgba32>(stream);
}
data.LoadParameters = TryLoadTextureParameters(cache, data.Path) ?? TextureLoadParameters.Default;
}
internal static void LoadTexture(IClyde clyde, LoadStepData data)
{
data.Texture = clyde.LoadTextureFromImage(data.Image, data.Path.ToString(), data.LoadParameters);
}
internal void LoadFinish(IResourceCache cache, LoadStepData data)
{
Texture = data.Texture;
if (cache is IResourceCacheInternal cacheInternal)
{
cacheInternal.TextureLoaded(new TextureLoadedEventArgs(data.Path, data.Image, this));
}
data.Image.Dispose();
}
private static TextureLoadParameters? TryLoadTextureParameters(IResourceCache cache, ResourcePath path)
{
var metaPath = path.WithName(path.Filename + ".yml");
if (cache.TryContentFileRead(metaPath, out var stream))
@@ -70,6 +78,15 @@ namespace Robust.Client.ResourceManagement
return null;
}
internal sealed class LoadStepData
{
public ResourcePath Path = default!;
public Image<Rgba32> Image = default!;
public TextureLoadParameters LoadParameters;
public Texture Texture = default!;
public bool Bad;
}
// TODO: Due to a bug in Roslyn, NotNullIfNotNullAttribute doesn't work.
// So this can't work with both nullables and non-nullables at the same time.
// I decided to only have it work with non-nullables as such.

View File

@@ -14,7 +14,6 @@
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="NJsonSchema" Version="10.3.8" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
@@ -39,9 +38,6 @@
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Graphics\RSI\RSISchema.json" Condition="'$(Configuration)' == 'Debug'">
<LogicalName>Robust.Client.Graphics.RSI.RSISchema.json</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Graphics\Clyde\Shaders\*" />
</ItemGroup>

View File

@@ -0,0 +1,73 @@
using Robust.Client.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
namespace Robust.Client.Serialization
{
[TypeSerializer]
public class AppearanceVisualizerSerializer : ITypeSerializer<AppearanceVisualizer, MappingDataNode>
{
public DeserializationResult Read(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
{
if (!node.TryGetNode("type", out var typeNode))
throw new InvalidMappingException("No type specified for AppearanceVisualizer!");
if (typeNode is not ValueDataNode typeValueDataNode)
throw new InvalidMappingException("Type node not a value node for AppearanceVisualizer!");
var type = IoCManager.Resolve<IReflectionManager>()
.YamlTypeTagLookup(typeof(AppearanceVisualizer), typeValueDataNode.Value);
if (type == null)
throw new InvalidMappingException(
$"Invalid type {typeValueDataNode.Value} specified for AppearanceVisualizer!");
var newNode = (MappingDataNode)node.Copy();
newNode.RemoveNode("type");
return serializationManager.Read(type, newNode, context, skipHook);
}
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
IDependencyCollection dependencies,
ISerializationContext? context)
{
if (!node.TryGetNode("type", out var typeNode) || typeNode is not ValueDataNode valueNode)
{
return new ErrorNode(node, "Missing/Invalid type", true);
}
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
var type = reflectionManager.YamlTypeTagLookup(typeof(AppearanceVisualizer), valueNode.Value);
if (type == null)
{
return new ErrorNode(node, $"Failed to resolve type: {valueNode.Value}", true);
}
return serializationManager.ValidateNode(type, node.CopyCast<MappingDataNode>().RemoveNode("type"));
}
public DataNode Write(ISerializationManager serializationManager, AppearanceVisualizer value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
var mapping = serializationManager.WriteValueAs<MappingDataNode>(value.GetType(), value, alwaysWrite, context);
mapping.AddNode("type", new ValueDataNode(value.GetType().Name));
return mapping;
}
public AppearanceVisualizer Copy(ISerializationManager serializationManager, AppearanceVisualizer source,
AppearanceVisualizer target, bool skipHook, ISerializationContext? context = null)
{
return serializationManager.Copy(source, target, context)!;
}
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -67,6 +67,7 @@ namespace Robust.Client.UserInterface.CustomControls
Title = Loc.GetString("Entity Spawn Panel");
SetSize = (250, 300);
MinSize = (250, 200);

View File

@@ -4,7 +4,7 @@
<PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
<HBoxContainer>
<Label Margin="5 0 0 0" HorizontalExpand="true" Name="TitleLabel" StyleIdentifier="foo" ClipText="True"
Text="{Loc Exemplary Window Title Here}" VAlign="Center" StyleClasses="windowTitle" />
Text="{Loc 'ss14window-placeholder-title'}" VAlign="Center" StyleClasses="windowTitle" />
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton" VerticalAlignment="Center" />
</HBoxContainer>
</PanelContainer>

View File

@@ -42,19 +42,20 @@ namespace Robust.Client.Utility
{
var dstSpan = destination.GetPixelSpan();
var dstWidth = destination.Width;
var srcHeight = sourceRect.Height;
var srcWidth = sourceRect.Width;
var (ox, oy) = destinationOffset;
for (var y = 0; y < sourceRect.Height; y++)
for (var y = 0; y < srcHeight; y++)
{
var sourceRowOffset = sourceWidth * (y + sourceRect.Top) + sourceRect.Left;
var destRowOffset = dstWidth * (y + oy) + ox;
for (var x = 0; x < sourceRect.Width; x++)
{
var pixel = source[x + sourceRowOffset];
dstSpan[x + destRowOffset] = pixel;
}
var srcRow = source[sourceRowOffset..(sourceRowOffset + srcWidth)];
var dstRow = dstSpan[destRowOffset..(destRowOffset + srcWidth)];
srcRow.CopyTo(dstRow);
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -26,12 +26,10 @@ namespace Robust.Client.Utility
{
if (cache.TryGetResource<RSIResource>(
SharedSpriteComponent.TextureRoot / rsiSpecifier.RsiPath,
out var theRsi))
out var theRsi) &&
theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
{
if (theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
{
return state;
}
return state;
}
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);
@@ -43,6 +41,40 @@ namespace Robust.Client.Utility
return specifier.RsiStateLike().Default;
}
public static float[] FrameDelays(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Delays;
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new float[0];
default:
throw new NotImplementedException();
}
}
public static Texture[] FrameArr(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Icons[0];
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new Texture[0];
default:
throw new NotImplementedException();
}
}
public static IDirectionalTextureProvider DirFrame0(this SpriteSpecifier specifier)
{
return specifier.RsiStateLike();

View File

@@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using NUnit.Framework;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Generators.UnitTesting
{
[Parallelizable]
public abstract class AnalyzerTest
{
protected static Assembly GetAssemblyFromCompilation(Compilation newComp)
{
using var stream = new MemoryStream();
newComp.Emit(stream);
var assembly = Assembly.Load(stream.ToArray());
return assembly;
}
protected static Compilation CreateCompilation(string source)
{
var dd = typeof(Enumerable).GetTypeInfo().Assembly.Location;
var coreDir = Directory.GetParent(dd) ?? throw new Exception("Couldn't find location of coredir");
var references = new[]
{
MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(Dictionary<,>).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(typeof(DataFieldAttribute).GetTypeInfo().Assembly.Location),
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar + "mscorlib.dll"),
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar +
"System.Runtime.dll"),
MetadataReference.CreateFromFile(coreDir.FullName + Path.DirectorySeparatorChar +
"System.Collections.dll"),
};
var syntaxTree = CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Preview));
return CSharpCompilation.Create(
"comp",
new[] {syntaxTree},
references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
}
protected static (Compilation, ImmutableArray<Diagnostic> diagnostics) RunGenerators(Compilation c,
params ISourceGenerator[] gens)
{
var driver = CSharpGeneratorDriver.Create(
ImmutableArray.Create(gens),
ImmutableArray<AdditionalText>.Empty,
(CSharpParseOptions) c.SyntaxTrees.First().Options);
driver.RunGeneratorsAndUpdateCompilation(c, out var d, out var diagnostics);
return (d, diagnostics);
}
}
}

View File

@@ -0,0 +1,82 @@
using System.Linq;
using NUnit.Framework;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Generators.UnitTesting
{
public class DataClassTests : AnalyzerTest
{
[Test]
public void DCTest()
{
const string source = @"
using System.Collections.Generic;
using Robust.Shared.Prototypes;
//using Robust.Shared.Serialization;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Test{
[DataClass]
public class TestClass{
[DataFieldWithConstant(""drawdepth"", typeof(DrawDepthTag))]
private int _drawDepth = DrawDepthTag.Default;
[DataField(""myList"")]
public List<TestClass> testList;
[DataField(""myList"")]
public string abc = ""testing"";
[DataField(""drawdepth"", constType: typeof(TestClass))]
public int test;
}
}
";
var comp = CreateCompilation(source);
//Assert.IsEmpty(comp.GetDiagnostics());
var (newcomp, generatorDiags) = RunGenerators(comp, new DataClassGenerator());
Assert.IsEmpty(generatorDiags);
var type = newcomp.GetTypeByMetadataName("Test.TestClass_AUTODATA");
Assert.NotNull(type);
var memberNames = type.MemberNames.ToArray();
// 3 properties
Assert.That(memberNames, Has.Length.EqualTo(3));
Assert.That(memberNames, Contains.Item("testList"));
Assert.That(memberNames, Contains.Item("abc"));
Assert.That(memberNames, Contains.Item("test"));
var members = type.GetMembers();
// 3 properties + constructor
Assert.That(members, Has.Length.EqualTo(4));
var memberDictionary = members.ToDictionary(m => m.Name, m => m);
var yamlFieldNamespace = typeof(DataFieldAttribute).FullName;
Assert.NotNull(yamlFieldNamespace);
var yamlFieldAttribute = comp.GetTypeByMetadataName(yamlFieldNamespace);
var testListYamlAttribute = memberDictionary["testList"].GetAttribute(yamlFieldAttribute);
Assert.NotNull(testListYamlAttribute);
Assert.That(testListYamlAttribute.ConstructorArguments[0].Value, Is.EqualTo("myList"));
var abcYamlAttribute = memberDictionary["abc"].GetAttribute(yamlFieldAttribute);
Assert.NotNull(abcYamlAttribute);
Assert.That(abcYamlAttribute.ConstructorArguments[0].Value, Is.EqualTo("myList"));
var testYamlAttribute = memberDictionary["test"].GetAttribute(yamlFieldAttribute);
Assert.NotNull(testYamlAttribute);
Assert.That(testYamlAttribute.ConstructorArguments[0].Value, Is.EqualTo("drawdepth"));
Assert.NotNull(testYamlAttribute.ConstructorArguments[3].Value);
Assert.That(testYamlAttribute.ConstructorArguments[3].Value.ToString(), Is.EqualTo("Test.TestClass"));
//TODO Check for dataclass & if its correct
}
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Robust.Generators\Robust.Generators.csproj" />
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit.ConsoleRunner" Version="3.11.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="NUnit.Analyzers" Version="0.6.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Engine.targets" />
</Project>

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
</ItemGroup>
</Project>

View File

@@ -1,23 +1,11 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Threading;
using Prometheus;
using Robust.Server.Console;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Server.ViewVariables;
using Robust.Shared.Asynchronous;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Exceptions;
using Robust.Server.Scripting;
using Robust.Server.ServerStatus;
using Robust.Shared;
using Robust.Server.DataMetrics;
using Robust.Server.Debugging;
using Robust.Server.GameObjects;
@@ -25,12 +13,26 @@ using Robust.Server.GameStates;
using Robust.Server.Log;
using Robust.Server.Placement;
using Robust.Server.Player;
using Robust.Server.Scripting;
using Robust.Server.ServerStatus;
using Robust.Server.Utility;
using Robust.Server.ViewVariables;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Serilog.Debugging;
using Serilog.Sinks.Loki;
using Stopwatch = Robust.Shared.Timing.Stopwatch;
@@ -140,6 +142,10 @@ namespace Robust.Server
/// <inheritdoc />
public bool Start(Func<ILogHandler>? logHandlerFactory = null)
{
var profilePath = Path.Join(Environment.CurrentDirectory, "AAAAAAAA");
ProfileOptimization.SetProfileRoot(profilePath);
ProfileOptimization.StartProfile("AAAAAAAAAA");
_config.Initialize(true);
if (LoadConfigAndUserData)
@@ -177,6 +183,8 @@ namespace Robust.Server
_config.OverrideConVars(_commandLineArgs.CVars);
}
ProfileOptSetup.Setup(_config);
//Sets up Logging
_logHandlerFactory = logHandlerFactory;
@@ -298,6 +306,8 @@ namespace Robust.Server
_entities.Initialize();
IoCManager.Resolve<ISerializationManager>().Initialize();
// because of 'reasons' this has to be called after the last assembly is loaded
// otherwise the prototypes will be cleared
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
@@ -324,6 +334,8 @@ namespace Robust.Server
WindowsTickPeriod.TimeBeginPeriod((uint) _config.GetCVar(CVars.SysWinTickPeriod));
}
GC.Collect();
return false;
}

View File

@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Robust.Shared.IoC;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
@@ -14,7 +14,7 @@ namespace Robust.Server.Debugging
public void Initialize()
{
_net.RegisterNetMessage<MsgRay>(MsgRay.NAME);
_physics.DebugDrawRay += data => PhysicsOnDebugDrawRay(data);
// TODO _physics.DebugDrawRay += data => PhysicsOnDebugDrawRay(data);
}
[Conditional("DEBUG")]

View File

@@ -51,7 +51,7 @@ namespace Robust.Server.GameObjects
}
}
public class PlayerAttachSystemMessage : EntitySystemMessage
public class PlayerAttachSystemMessage : EntityEventArgs
{
public PlayerAttachSystemMessage(IEntity entity, IPlayerSession newPlayer)
{
@@ -63,7 +63,7 @@ namespace Robust.Server.GameObjects
public IPlayerSession NewPlayer { get; }
}
public class PlayerDetachedSystemMessage : EntitySystemMessage
public class PlayerDetachedSystemMessage : EntityEventArgs
{
public PlayerDetachedSystemMessage(IEntity entity)
{

View File

@@ -1,359 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
public sealed class ContainerManagerComponent : SharedContainerManagerComponent
{
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
private readonly Dictionary<string, IContainer> EntityContainers = new();
private Dictionary<string, List<EntityUid>>? _entitiesWaitingResolve;
[ViewVariables] private IEnumerable<IContainer> _allContainers => EntityContainers.Values;
/// <summary>
/// Shortcut method to make creation of containers easier.
/// Creates a new container on the entity and gives it back to you.
/// </summary>
/// <param name="id">The ID of the new container.</param>
/// <param name="entity">The entity to create the container for.</param>
/// <returns>The new container.</returns>
/// <exception cref="ArgumentException">Thrown if there already is a container with the specified ID.</exception>
/// <seealso cref="IContainerManager.MakeContainer{T}(string)" />
public static T Create<T>(string id, IEntity entity) where T : IContainer
{
if (!entity.TryGetComponent<IContainerManager>(out var containermanager))
{
containermanager = entity.AddComponent<ContainerManagerComponent>();
}
return containermanager.MakeContainer<T>(id);
}
public static T Ensure<T>(string id, IEntity entity) where T : IContainer
{
return Ensure<T>(id, entity, out _);
}
public static T Ensure<T>(string id, IEntity entity, out bool alreadyExisted) where T : IContainer
{
var containerManager = entity.EnsureComponent<ContainerManagerComponent>();
if (!containerManager.TryGetContainer(id, out var existing))
{
alreadyExisted = false;
return containerManager.MakeContainer<T>(id);
}
if (!(existing is T container))
{
throw new InvalidOperationException(
$"The container exists but is of a different type: {existing.GetType()}");
}
alreadyExisted = true;
return container;
}
public override T MakeContainer<T>(string id)
{
return (T) MakeContainer(id, typeof(T));
}
private IContainer MakeContainer(string id, Type type)
{
if (HasContainer(id))
{
throw new ArgumentException($"Container with specified ID already exists: '{id}'");
}
var container = (IContainer) Activator.CreateInstance(type, id, this)!;
EntityContainers[id] = container;
Dirty();
return container;
}
public new AllContainersEnumerable GetAllContainers()
{
return new(this);
}
protected override IEnumerable<IContainer> GetAllContainersImpl()
{
return GetAllContainers();
}
/// <inheritdoc />
public override IContainer GetContainer(string id)
{
return EntityContainers[id];
}
/// <inheritdoc />
public override bool HasContainer(string id)
{
return EntityContainers.ContainsKey(id);
}
/// <inheritdoc />
public override bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container)
{
if (!HasContainer(id))
{
container = null;
return false;
}
container = GetContainer(id);
return true;
}
public override bool TryGetContainer(IEntity entity, [NotNullWhen(true)] out IContainer? container)
{
foreach (var contain in EntityContainers.Values)
{
if (!contain.Deleted && contain.Contains(entity))
{
container = contain;
return true;
}
}
container = default;
return false;
}
public override bool ContainsEntity(IEntity entity)
{
foreach (var container in EntityContainers.Values)
{
if (!container.Deleted && container.Contains(entity))
{
return true;
}
}
return false;
}
public override void ForceRemove(IEntity entity)
{
foreach (var container in EntityContainers.Values)
{
if (container.Contains(entity))
{
container.ForceRemove(entity);
}
}
}
public override void InternalContainerShutdown(IContainer container)
{
EntityContainers.Remove(container.ID);
}
/// <inheritdoc />
public override bool Remove(IEntity entity)
{
foreach (var containers in EntityContainers.Values)
{
if (containers.Contains(entity))
{
return containers.Remove(entity);
}
}
return true; // If we don't contain the entity, it will always be removed
}
public override void OnRemove()
{
base.OnRemove();
// IContianer.Shutdown modifies the EntityContainers collection
foreach (var container in EntityContainers.Values.ToArray())
{
container.Shutdown();
}
EntityContainers.Clear();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
if (serializer.Reading)
{
if (serializer.TryReadDataField<Dictionary<string, ContainerPrototypeData>>("containers", out var data))
{
_entitiesWaitingResolve = new Dictionary<string, List<EntityUid>>();
foreach (var (key, datum) in data)
{
if (datum.Type == null)
{
throw new InvalidOperationException("Container does not have type set.");
}
var type = _reflectionManager.LooseGetType(datum.Type);
MakeContainer(key, type);
if (datum.Entities.Count == 0)
{
continue;
}
var list = new List<EntityUid>(datum.Entities.Where(u => u.IsValid()));
_entitiesWaitingResolve.Add(key, list);
}
}
}
else
{
var dict = new Dictionary<string, ContainerPrototypeData>();
foreach (var (key, container) in EntityContainers)
{
var list = new List<EntityUid>(container.ContainedEntities.Select(e => e.Uid));
var data = new ContainerPrototypeData(list, container.GetType().FullName!);
dict.Add(key, data);
}
// ReSharper disable once RedundantTypeArgumentsOfMethod
serializer.DataWriteFunction<Dictionary<string, ContainerPrototypeData>?>("containers", null,
() => dict);
}
}
public override void Initialize()
{
base.Initialize();
if (_entitiesWaitingResolve == null)
{
return;
}
foreach (var (key, entities) in _entitiesWaitingResolve)
{
var container = GetContainer(key);
foreach (var uid in entities)
{
container.Insert(Owner.EntityManager.GetEntity(uid));
}
}
_entitiesWaitingResolve = null;
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new ContainerManagerComponentState(
_allContainers.ToDictionary(
c => c.ID,
DataFor));
}
private static ContainerManagerComponentState.ContainerData DataFor(IContainer container)
{
return new()
{
ContainedEntities = container.ContainedEntities.Select(e => e.Uid).ToArray(),
ShowContents = container.ShowContents,
OccludesLight = container.OccludesLight
};
}
private struct ContainerPrototypeData : IExposeData
{
public List<EntityUid> Entities;
public string? Type;
public ContainerPrototypeData(List<EntityUid> entities, string type)
{
Entities = entities;
Type = type;
}
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Entities, "entities", new List<EntityUid>());
serializer.DataField(ref Type, "type", null);
}
}
public struct AllContainersEnumerable : IEnumerable<IContainer>
{
private readonly ContainerManagerComponent _manager;
public AllContainersEnumerable(ContainerManagerComponent manager)
{
_manager = manager;
}
public AllContainersEnumerator GetEnumerator()
{
return new(_manager);
}
IEnumerator<IContainer> IEnumerable<IContainer>.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
public struct AllContainersEnumerator : IEnumerator<IContainer>
{
private Dictionary<string, IContainer>.ValueCollection.Enumerator _enumerator;
public AllContainersEnumerator(ContainerManagerComponent manager)
{
_enumerator = manager.EntityContainers.Values.GetEnumerator();
Current = default;
}
public bool MoveNext()
{
while (_enumerator.MoveNext())
{
if (!_enumerator.Current.Deleted)
{
Current = _enumerator.Current;
return true;
}
}
return false;
}
void IEnumerator.Reset()
{
((IEnumerator<IContainer>) _enumerator).Reset();
}
[AllowNull] public IContainer Current { get; private set; }
object? IEnumerator.Current => Current;
public void Dispose()
{
}
}
}
}

View File

@@ -1,14 +1,19 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
public class EyeComponent : SharedEyeComponent
{
private bool _drawFov;
private Vector2 _zoom;
[DataField("drawFov")]
private bool _drawFov = true;
[DataField("zoom")]
private Vector2 _zoom = Vector2.One/2f;
private Vector2 _offset;
private Angle _rotation;
@@ -68,13 +73,5 @@ namespace Robust.Server.GameObjects
{
return new EyeComponentState(DrawFov, Zoom, Offset, Rotation);
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _zoom, "zoom", Vector2.One/2f);
serializer.DataFieldCached(ref _drawFov, "drawFov", true);
}
}
}

View File

@@ -1,7 +1,9 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
@@ -10,10 +12,14 @@ namespace Robust.Server.GameObjects
[ComponentReference(typeof(IPointLightComponent))]
public class PointLightComponent : Component, IPointLightComponent
{
private Color _color;
private bool _enabled;
private float _radius;
private Vector2 _offset;
[DataField("color")]
private Color _color = new(200, 200, 200);
[DataField("enabled")]
private bool _enabled = true;
[DataField("radius")]
private float _radius = 10;
[DataField("offset")]
private Vector2 _offset = Vector2.Zero;
public override string Name => "PointLight";
public override uint? NetID => NetIDs.POINT_LIGHT;
@@ -73,17 +79,6 @@ namespace Robust.Server.GameObjects
}
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _enabled, "enabled", true);
serializer.DataField(ref _color, "color", new Color(200, 200, 200));
serializer.DataField(ref _radius, "radius", 10);
serializer.DataField(ref _offset, "offset", Vector2.Zero);
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new PointLightComponentState(Enabled, Color, Radius, Offset);

View File

@@ -4,27 +4,49 @@ using Robust.Shared.GameObjects;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
{
public class SpriteComponent : SharedSpriteComponent, ISpriteRenderableComponent
public class SpriteComponent : SharedSpriteComponent, ISpriteRenderableComponent, ISerializationHooks
{
const string LayerSerializationCache = "spritelayersrv";
[ViewVariables]
[DataField("layers", priority: 2, readOnly: true)]
private List<PrototypeLayerData> Layers = new();
private bool _visible;
[DataField("visible")]
private bool _visible = true;
[DataFieldWithConstant("drawdepth", typeof(DrawDepthTag))]
private int _drawDepth = DrawDepthTag.Default;
private Vector2 _scale;
private Vector2 _offset;
private Color _color;
private bool _directional;
[DataField("scale")]
private Vector2 _scale = Vector2.One;
[DataField("offset")]
private Vector2 _offset = Vector2.Zero;
[DataField("color")]
private Color _color = Color.White;
[DataField("directional")]
private bool _directional = true;
[DataField("sprite")]
private string? _baseRSIPath;
private Angle _rotation;
[DataField("rotation")]
private Angle _rotation = Angle.Zero;
[DataField("state")] private string? state;
[DataField("texture")] private string? texture;
[ViewVariables(VVAccess.ReadWrite)]
public int DrawDepth
@@ -129,6 +151,31 @@ namespace Robust.Server.GameObjects
[ViewVariables]
public int LayerCount => Layers.Count;
void ISerializationHooks.AfterDeserialization()
{
if (Layers.Count == 0)
{
if (state != null || texture != null)
{
var layerZeroData = SharedSpriteComponent.PrototypeLayerData.New();
if (!string.IsNullOrWhiteSpace(state))
{
layerZeroData.State = state;
}
if (!string.IsNullOrWhiteSpace(texture))
{
layerZeroData.TexturePath = texture;
}
Layers.Insert(0, layerZeroData);
state = null;
texture = null;
}
}
}
public int AddLayerWithSprite(SpriteSpecifier specifier)
{
var layer = PrototypeLayerData.New();
@@ -272,7 +319,7 @@ namespace Robust.Server.GameObjects
{
if (Layers.Count <= layer)
{
Logger.ErrorS("go.comp.sprite", "Layer with index '{0}' does not exist, cannot set set! Trace:\n{1}",
Logger.ErrorS("go.comp.sprite", "Layer with index '{0}' does not exist, cannot set state! Trace:\n{1}",
layer, Environment.StackTrace);
return;
}
@@ -389,59 +436,6 @@ namespace Robust.Server.GameObjects
Dirty();
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref _visible, "visible", true);
serializer.DataFieldCached(ref _drawDepth, "drawdepth", DrawDepthTag.Default, WithFormat.Constants<DrawDepthTag>());
serializer.DataFieldCached(ref _offset, "offset", Vector2.Zero);
serializer.DataFieldCached(ref _scale, "scale", Vector2.One);
serializer.DataFieldCached(ref _color, "color", Color.White);
serializer.DataFieldCached(ref _directional, "directional", true);
serializer.DataFieldCached(ref _baseRSIPath, "sprite", null);
serializer.DataFieldCached(ref _rotation, "rotation", Angle.Zero);
// TODO: Writing?
if (!serializer.Reading)
{
return;
}
if (serializer.TryGetCacheData<List<PrototypeLayerData>>(LayerSerializationCache, out var layers))
{
Layers = layers.ShallowClone();
return;
}
var layerData =
serializer.ReadDataField<List<PrototypeLayerData>>("layers", new List<PrototypeLayerData>());
if(layerData.Count == 0){
var baseState = serializer.ReadDataField<string?>("state", null);
var texturePath = serializer.ReadDataField<string?>("texture", null);
if (baseState != null || texturePath != null)
{
var layerZeroData = PrototypeLayerData.New();
if (!string.IsNullOrWhiteSpace(baseState))
{
layerZeroData.State = baseState;
}
if (!string.IsNullOrWhiteSpace(texturePath))
{
layerZeroData.TexturePath = texturePath;
}
layerData.Insert(0, layerZeroData);
}
}
serializer.SetCacheData(LayerSerializationCache, layerData.ShallowClone());
Layers = layerData;
}
public override ComponentState GetComponentState(ICommonSession player)
{
return new SpriteComponentState(Visible, DrawDepth, Scale, Rotation, Offset, Color,

View File

@@ -10,6 +10,7 @@ using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Server.GameObjects
{
@@ -19,27 +20,24 @@ namespace Robust.Server.GameObjects
/// </summary>
/// <seealso cref="BoundUserInterface"/>
[PublicAPI]
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent
public sealed class ServerUserInterfaceComponent : SharedUserInterfaceComponent, ISerializationHooks
{
private readonly Dictionary<object, BoundUserInterface> _interfaces =
new();
[DataField("interfaces", readOnly: true)]
private List<PrototypeData> _interfaceData = new();
/// <summary>
/// Enumeration of all the interfaces this component provides.
/// </summary>
public IEnumerable<BoundUserInterface> Interfaces => _interfaces.Values;
public override void ExposeData(ObjectSerializer serializer)
void ISerializationHooks.AfterDeserialization()
{
base.ExposeData(serializer);
_interfaces.Clear();
if (!serializer.Reading)
{
return;
}
var data = serializer.ReadDataFieldCached("interfaces", new List<PrototypeData>());
foreach (var prototypeData in data)
foreach (var prototypeData in _interfaceData)
{
_interfaces[prototypeData.UiKey] = new BoundUserInterface(prototypeData.UiKey, this);
}

View File

@@ -1,5 +1,7 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
@@ -7,6 +9,7 @@ namespace Robust.Server.GameObjects
[RegisterComponent]
public class VisibilityComponent : Component
{
[DataField("layer")]
private int _layer = 1;
public override string Name => "Visibility";
@@ -20,12 +23,5 @@ namespace Robust.Server.GameObjects
get => _layer;
set => _layer = value;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _layer, "layer", 1);
}
}
}

View File

@@ -1,47 +0,0 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
[PublicAPI]
public abstract class ContainerModifiedMessage : EntitySystemMessage
{
protected ContainerModifiedMessage(IEntity entity, IContainer container)
{
Entity = entity;
Container = container;
}
/// <summary>
/// The entity that was removed or inserted from/into the container.
/// </summary>
public IEntity Entity { get; }
/// <summary>
/// The container being acted upon.
/// </summary>
public IContainer Container { get; }
}
/// <summary>
/// Raised when an entity is removed from a container.
/// </summary>
[PublicAPI]
public sealed class EntRemovedFromContainerMessage : ContainerModifiedMessage
{
public EntRemovedFromContainerMessage(IEntity entity, IContainer container) : base(entity, container)
{
}
}
/// <summary>
/// Raised when an entity is inserted into a container.
/// </summary>
[PublicAPI]
public sealed class EntInsertedIntoContainerMessage : ContainerModifiedMessage
{
public EntInsertedIntoContainerMessage(IEntity entity, IContainer container) : base(entity, container)
{
}
}
}

View File

@@ -2,7 +2,7 @@ using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
{
public sealed class EntityDeletedMessage : EntitySystemMessage
public sealed class EntityDeletedMessage : EntityEventArgs
{
public IEntity Entity { get; }

View File

@@ -12,7 +12,7 @@ namespace Robust.Server.GameObjects
/// <remarks>
/// List is empty if it's no longer intersecting any.
/// </remarks>
public sealed class TileLookupUpdateMessage : EntitySystemMessage
public sealed class TileLookupUpdateMessage : EntityEventArgs
{
public Dictionary<GridId, List<Vector2i>>? NewIndices { get; }

View File

@@ -1,29 +1,31 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Server.Player;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using static Robust.Server.GameObjects.AudioSystem;
using Robust.Shared.Player;
using Robust.Shared.Players;
namespace Robust.Server.GameObjects
{
public class AudioSystem : EntitySystem
public class AudioSystem : EntitySystem, IAudioSystem
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
public const int AudioDistanceRange = 25;
private const int AudioDistanceRange = 25;
private uint _streamIndex;
public class AudioSourceServer
private class AudioSourceServer : IPlayingAudioStream
{
private readonly uint _id;
private readonly AudioSystem _audioSystem;
private readonly IEnumerable<IPlayerSession>? _sessions;
private readonly IEnumerable<ICommonSession>? _sessions;
internal AudioSourceServer(AudioSystem parent, uint identifier, IEnumerable<IPlayerSession>? sessions = null)
internal AudioSourceServer(AudioSystem parent, uint identifier, IEnumerable<ICommonSession>? sessions = null)
{
_audioSystem = parent;
_id = identifier;
@@ -35,7 +37,13 @@ namespace Robust.Server.GameObjects
}
}
private void InternalStop(uint id, IEnumerable<IPlayerSession>? sessions = null)
/// <inheritdoc />
public override void Initialize()
{
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
}
private void InternalStop(uint id, IEnumerable<ICommonSession>? sessions = null)
{
var msg = new StopAudioMessageClient
{
@@ -65,7 +73,9 @@ namespace Robust.Server.GameObjects
/// <param name="audioParams"></param>
/// <param name="predicate">The predicate that will be used to send the audio to players, or null to send to everyone.</param>
/// <param name="excludedSession">Session that won't receive the audio message.</param>
public AudioSourceServer PlayGlobal(string filename, AudioParams? audioParams = null, Func<IPlayerSession, bool>? predicate = null, IPlayerSession? excludedSession = null)
/// <param name="recipients"></param>
[Obsolete("Use the Play() overload.")]
public IPlayingAudioStream PlayGlobal(string filename, AudioParams? audioParams = null, Func<IPlayerSession, bool>? predicate = null, IPlayerSession? excludedSession = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioGlobalMessage
@@ -81,7 +91,7 @@ namespace Robust.Server.GameObjects
return new AudioSourceServer(this, id);
}
var players = predicate != null ? _playerManager.GetPlayersBy(predicate) : _playerManager.GetAllPlayers();
IList<IPlayerSession> players = predicate != null ? _playerManager.GetPlayersBy(predicate) : _playerManager.GetAllPlayers();
for (var i = players.Count - 1; i >= 0; i--)
{
@@ -96,7 +106,6 @@ namespace Robust.Server.GameObjects
}
return new AudioSourceServer(this, id, players);
}
/// <summary>
@@ -107,7 +116,8 @@ namespace Robust.Server.GameObjects
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Sessions that won't receive the audio message.</param>
public AudioSourceServer PlayFromEntity(string filename, IEntity entity, AudioParams? audioParams = null, int range = AudioDistanceRange, IPlayerSession? excludedSession = null)
[Obsolete("Use the Play() overload.")]
public IPlayingAudioStream PlayFromEntity(string filename, IEntity entity, AudioParams? audioParams = null, int range = AudioDistanceRange, IPlayerSession? excludedSession = null)
{
var id = CacheIdentifier();
@@ -120,13 +130,19 @@ namespace Robust.Server.GameObjects
Identifier = id,
};
// send to every player
if (range <= 0 && excludedSession == null)
{
RaiseNetworkEvent(msg);
return new AudioSourceServer(this, id);
}
var players = range > 0.0f ? _playerManager.GetPlayersInRange(entity.Transform.Coordinates, range) : _playerManager.GetAllPlayers();
List<IPlayerSession> players;
if (range > 0.0f)
players = _playerManager.GetPlayersInRange(entity.Transform.Coordinates, range);
else
players = _playerManager.GetAllPlayers();
for (var i = players.Count - 1; i >= 0; i--)
{
@@ -151,7 +167,8 @@ namespace Robust.Server.GameObjects
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Session that won't receive the audio message.</param>
public AudioSourceServer PlayAtCoords(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, int range = AudioDistanceRange, IPlayerSession? excludedSession = null)
[Obsolete("Use the Play() overload.")]
public IPlayingAudioStream PlayAtCoords(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, int range = AudioDistanceRange, IPlayerSession? excludedSession = null)
{
var id = CacheIdentifier();
var msg = new PlayAudioPositionalMessage
@@ -168,7 +185,12 @@ namespace Robust.Server.GameObjects
return new AudioSourceServer(this, id);
}
var players = range > 0.0f ? _playerManager.GetPlayersInRange(coordinates, range) : _playerManager.GetAllPlayers();
List<IPlayerSession> players;
if (range > 0.0f)
players = _playerManager.GetPlayersInRange(coordinates, range);
else
players = _playerManager.GetAllPlayers();
for (var i = players.Count - 1; i >= 0; i--)
{
@@ -185,88 +207,102 @@ namespace Robust.Server.GameObjects
return new AudioSourceServer(this, id, players);
}
#region DEPRECATED
/// <summary>
/// Play an audio file globally, without position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams"></param>
[Obsolete("Deprecated. Use PlayGlobal instead.")]
public void Play(string filename, AudioParams? audioParams = null)
/// <inheritdoc />
public int DefaultSoundRange => AudioDistanceRange;
/// <inheritdoc />
public int OcclusionCollisionMask { get; set; }
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
{
PlayGlobal(filename, audioParams);
var id = CacheIdentifier();
var msg = new PlayAudioGlobalMessage
{
FileName = filename,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
var players = (playerFilter as IFilter).Recipients;
foreach (var player in players)
{
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
[Obsolete("Deprecated. Use PlayFromEntity instead.")]
public void Play(string filename, IEntity entity, AudioParams? audioParams = null)
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
{
PlayFromEntity(filename, entity, audioParams);
//TODO: Calculate this from PAS
var range = audioParams is null || audioParams.Value.MaxDistance <= 0 ? AudioDistanceRange : audioParams.Value.MaxDistance;
var id = CacheIdentifier();
var msg = new PlayAudioEntityMessage
{
FileName = filename,
Coordinates = entity.Transform.Coordinates,
EntityUid = entity.Uid,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id,
};
IList<ICommonSession> players;
var recipients = (playerFilter as IFilter).Recipients;
if (range > 0.0f)
players = PASInRange(recipients, entity.Transform.MapPosition, range);
else
players = recipients;
foreach (var player in players)
{
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
[Obsolete("Deprecated. Use PlayAtCoords instead.")]
public void Play(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
/// <inheritdoc />
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
PlayAtCoords(filename, coordinates, audioParams);
//TODO: Calculate this from PAS
var range = audioParams is null || audioParams.Value.MaxDistance <= 0 ? AudioDistanceRange : audioParams.Value.MaxDistance;
var id = CacheIdentifier();
var msg = new PlayAudioPositionalMessage
{
FileName = filename,
Coordinates = coordinates,
AudioParams = audioParams ?? AudioParams.Default,
Identifier = id
};
IList<ICommonSession> players;
var recipients = (playerFilter as IFilter).Recipients;
if (range > 0.0f)
players = PASInRange(recipients, coordinates.ToMap(EntityManager), range);
else
players = recipients;
foreach (var player in players)
{
RaiseNetworkEvent(msg, player.ConnectedClient);
}
return new AudioSourceServer(this, id, players);
}
#endregion
}
public static class AudioSystemExtensions
{
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Sessions that won't receive the audio message.</param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static void PlaySoundFrom(
this IEntity entity,
string filename,
AudioParams? audioParams = null,
int range = AudioDistanceRange,
IPlayerSession? excludedSession = null,
AudioSystem? audioSystem = null)
private static List<ICommonSession> PASInRange(IEnumerable<ICommonSession> players, MapCoordinates position, float range)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
audioSystem.PlayFromEntity(filename, entity, audioParams, range, excludedSession);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
/// <param name="range">The max range at which the audio will be heard. Less than or equal to 0 to send to every player.</param>
/// <param name="excludedSession">Session that won't receive the audio message.</param>
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
public static void PlaySoundFrom(
this EntityCoordinates coordinates,
string filename,
AudioParams? audioParams = null,
int range = AudioDistanceRange,
IPlayerSession? excludedSession = null,
AudioSystem? audioSystem = null)
{
audioSystem ??= EntitySystem.Get<AudioSystem>();
audioSystem.PlayAtCoords(filename, coordinates, audioParams, range, excludedSession);
return players.Where(x =>
x.AttachedEntity != null &&
position.InRange(x.AttachedEntity.Transform.MapPosition, range))
.ToList();
}
}
}

View File

@@ -1,11 +1,37 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Server.GameObjects
{
[UsedImplicitly]
public class PhysicsSystem : SharedPhysicsSystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
public override void Initialize()
{
base.Initialize();
_mapManager.OnGridCreated += HandleGridCreated;
}
public override void Shutdown()
{
base.Shutdown();
_mapManager.OnGridCreated -= HandleGridCreated;
}
private void HandleGridCreated(GridId gridId)
{
if (!EntityManager.TryGetEntity(_mapManager.GetGrid(gridId).GridEntityId, out var gridEntity)) return;
var grid = _mapManager.GetGrid(gridId);
var collideComp = gridEntity.AddComponent<PhysicsComponent>();
collideComp.CanCollide = true;
collideComp.AddFixture(new Fixture(collideComp, new PhysShapeGrid(grid)) {CollisionMask = MapGridHelpers.CollisionGroup, CollisionLayer = MapGridHelpers.CollisionGroup});
}
/// <inheritdoc />
public override void Update(float frameTime)
{

View File

@@ -6,6 +6,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
namespace Robust.Server.GameObjects
{
@@ -198,8 +199,8 @@ namespace Robust.Server.GameObjects
private Box2 GetEntityBox(IEntity entity)
{
// Need to clip the aabb as anything with an edge intersecting another tile might be picked up, such as walls.
if (entity.TryGetComponent(out IPhysicsComponent? physics))
return new Box2(physics.WorldAABB.BottomLeft + 0.01f, physics.WorldAABB.TopRight - 0.01f);
if (entity.TryGetComponent(out IPhysBody? physics))
return new Box2(physics.GetWorldAABB().BottomLeft + 0.01f, physics.GetWorldAABB().TopRight - 0.01f);
// Don't want to accidentally get neighboring tiles unless we're near an edge
return Box2.CenteredAround(entity.Transform.Coordinates.ToMapPos(EntityManager), Vector2.One / 2);
@@ -335,7 +336,7 @@ namespace Robust.Server.GameObjects
return;
}
var bounds = GetEntityBox(moveEvent.Sender);
var bounds = moveEvent.WorldAABB ?? GetEntityBox(moveEvent.Sender);
var newNodes = GetOrCreateNodes(moveEvent.NewPosition, bounds);
if (oldNodes.Count == newNodes.Count && oldNodes.SetEquals(newNodes))

View File

@@ -1,5 +1,8 @@
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
namespace Robust.Server.GameObjects
{
public class ServerComponentFactory : ComponentFactory
@@ -27,7 +30,13 @@ namespace Robust.Server.GameObjects
RegisterReference<BasicActorComponent, IActorComponent>();
Register<PhysicsComponent>();
RegisterReference<PhysicsComponent, IPhysicsComponent>();
RegisterReference<PhysicsComponent, IPhysBody>();
Register<CollisionWakeComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<OccluderComponent>();
RegisterIgnore("Input");
@@ -35,9 +44,6 @@ namespace Robust.Server.GameObjects
RegisterReference<SpriteComponent, SharedSpriteComponent>();
RegisterReference<SpriteComponent, ISpriteRenderableComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<AppearanceComponent>();
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();
@@ -52,7 +58,6 @@ namespace Robust.Server.GameObjects
#if DEBUG
Register<DebugExceptionOnAddComponent>();
Register<DebugExceptionExposeDataComponent>();
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
#endif

View File

@@ -6,9 +6,11 @@ using Prometheus;
using Robust.Server.Player;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -367,7 +369,7 @@ namespace Robust.Server.GameObjects
continue;
}
if (entity.TryGetComponent(out IPhysicsComponent? body))
if (entity.TryGetComponent(out IPhysBody? body))
{
if (body.LinearVelocity.EqualsApprox(Vector2.Zero, MinimumMotionForMovers))
{
@@ -534,7 +536,7 @@ namespace Robust.Server.GameObjects
continue;
}
if (!entity.TryGetComponent(out IPhysicsComponent? body))
if (!entity.TryGetComponent(out IPhysBody? body))
{
// can't be a mover w/o physics
continue;
@@ -720,10 +722,10 @@ namespace Robust.Server.GameObjects
_deletionHistory.RemoveAll(hist => hist.tick <= toTick);
}
public override bool UpdateEntityTree(IEntity entity)
public override bool UpdateEntityTree(IEntity entity, Box2? worldAABB = null)
{
var currentTick = CurrentTick;
var updated = base.UpdateEntityTree(entity);
var updated = base.UpdateEntityTree(entity, worldAABB);
if (entity.Deleted
|| !entity.Initialized
@@ -735,6 +737,7 @@ namespace Robust.Server.GameObjects
DebugTools.Assert(entity.Transform.Initialized);
// note: updated can be false even if something moved a bit
worldAABB ??= GetWorldAabbFromEntity(entity);
foreach (var (player, lastSeen) in _playerLastSeen)
{
@@ -781,14 +784,14 @@ namespace Robust.Server.GameObjects
// saw it previously
// player can't see it now
if (!viewbox.Intersects(GetWorldAabbFromEntity(entity)))
if (!viewbox.Intersects(worldAABB.Value))
{
var addToMovers = false;
if (entity.Transform.LastModifiedTick >= currentTick)
{
addToMovers = true;
}
else if (entity.TryGetComponent(out IPhysicsComponent? physics)
else if (entity.TryGetComponent(out IPhysBody? physics)
&& physics.LastModifiedTick >= currentTick)
{
addToMovers = true;

View File

@@ -87,7 +87,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message)
public void SendSystemNetworkMessage(EntityEventArgs message)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
newMsg.Type = EntityMessageType.SystemMessage;
@@ -98,7 +98,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public void SendSystemNetworkMessage(EntitySystemMessage message, INetChannel targetConnection)
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel targetConnection)
{
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
newMsg.Type = EntityMessageType.SystemMessage;

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