Compare commits

...

19 Commits

Author SHA1 Message Date
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
Pieter-Jan Briers
d751c0b3ab Revert "Physics (#1602)"
This reverts commit fefcc7cba3.
2021-02-28 18:45:18 +01:00
Pieter-Jan Briers
2ace0e9e5a Expose Patreon tier info from auth server. 2021-02-28 18:45:01 +01:00
Pieter-Jan Briers
31716f5104 Work around Roslyn scripting bug with ref structs. 2021-02-28 18:45:01 +01:00
290 changed files with 11401 additions and 7460 deletions

View File

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

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

@@ -64,9 +64,11 @@ 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!;
@@ -356,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

@@ -1,4 +1,5 @@
#if CLIENT_SCRIPTING
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -172,7 +173,7 @@ namespace Robust.Client.Console
else if (ScriptInstanceShared.HasReturnValue(newScript))
{
var msg = new FormattedMessage();
msg.AddText(CSharpObjectFormatter.Instance.FormatObject(_state.ReturnValue));
msg.AddText(ScriptInstanceShared.SafeFormat(_state.ReturnValue));
OutputPanel.AddMessage(msg);
}
@@ -189,7 +190,6 @@ namespace Robust.Client.Console
_autoImportRepeatBuffer = (found.ToArray(), code);
}
private sealed class ScriptGlobalsImpl : ScriptGlobals
{
private readonly ScriptConsoleClient _owner;
@@ -215,7 +215,7 @@ namespace Robust.Client.Console
public override void show(object obj)
{
write(CSharpObjectFormatter.Instance.FormatObject(obj));
write(ScriptInstanceShared.SafeFormat(obj));
}
}
}

View File

@@ -1,3 +1,27 @@
/*
* 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.GameObjects;
@@ -7,10 +31,9 @@ using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
// TODO: Copy farseer licence here coz it's heavily inspired by it.
namespace Robust.Client.Debugging
{
internal sealed class DebugPhysicsSystem : EntitySystem
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
{
/*
* Used for debugging shapes, controllers, joints, contacts
@@ -40,17 +63,8 @@ namespace Robust.Client.Debugging
private PhysicsDebugFlags _flags;
public override void Initialize()
public override void HandlePreSolve(Contact contact, in Manifold oldManifold)
{
base.Initialize();
SubscribeLocalEvent<PreSolveMessage>(HandlePreSolve);
}
private void HandlePreSolve(PreSolveMessage message)
{
Contact contact = message.Contact;
Manifold oldManifold = message.OldManifold;
if ((Flags & PhysicsDebugFlags.ContactPoints) != 0)
{
Manifold manifold = contact.Manifold;
@@ -63,9 +77,9 @@ namespace Robust.Client.Debugging
PointState[] state1, state2;
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
Vector2[] points;
Span<Vector2> points = stackalloc Vector2[2];
Vector2 normal;
contact.GetWorldManifold(out normal, out points);
contact.GetWorldManifold(out normal, points);
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
{

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;
@@ -152,6 +155,8 @@ namespace Robust.Client
_configurationManager.LoadCVarsFromAssembly(loadedModule);
}
IoCManager.Resolve<ISerializationManager>().Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
@@ -161,7 +166,7 @@ namespace Robust.Client
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_serializer.Initialize();
_inputManager.Initialize();
_consoleHost.Initialize();
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
@@ -377,6 +382,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;
@@ -24,6 +25,11 @@ namespace Robust.Client.GameObjects
Register<PhysicsComponent>();
RegisterReference<PhysicsComponent, IPhysBody>();
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

@@ -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,87 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Server.GameObjects;
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 EntInsertedIntoContainerMessage(entity, this));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
}
public void DoRemove(IEntity entity)
{
Entities.Remove(entity);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntRemovedFromContainerMessage(entity, this));
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

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Client.Audio;
@@ -12,12 +11,13 @@ 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!;
@@ -29,8 +29,6 @@ namespace Robust.Client.GameObjects
private readonly List<PlayingStream> _playingClydeStreams = new();
public int OcclusionCollisionMask;
/// <inheritdoc />
public override void Initialize()
{
@@ -40,6 +38,7 @@ namespace Robust.Client.GameObjects
SubscribeNetworkEvent<PlayAudioPositionalMessage>(PlayAudioPositionalHandler);
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
}
@@ -182,7 +181,6 @@ namespace Robust.Client.GameObjects
{
stream.Source.Dispose();
stream.Done = true;
stream.DoPlaybackDone();
}
/// <summary>
@@ -341,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

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

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

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

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

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

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

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

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

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

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,10 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
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 +12,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;
@@ -298,6 +299,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>();

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

@@ -336,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,4 +1,6 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Physics;
namespace Robust.Server.GameObjects
@@ -30,6 +32,11 @@ namespace Robust.Server.GameObjects
Register<PhysicsComponent>();
RegisterReference<PhysicsComponent, IPhysBody>();
Register<CollisionWakeComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<OccluderComponent>();
RegisterIgnore("Input");
@@ -37,9 +44,6 @@ namespace Robust.Server.GameObjects
RegisterReference<SpriteComponent, SharedSpriteComponent>();
RegisterReference<SpriteComponent, ISpriteRenderableComponent>();
Register<ContainerManagerComponent>();
RegisterReference<ContainerManagerComponent, IContainerManager>();
Register<AppearanceComponent>();
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();
@@ -54,7 +58,6 @@ namespace Robust.Server.GameObjects
#if DEBUG
Register<DebugExceptionOnAddComponent>();
Register<DebugExceptionExposeDataComponent>();
Register<DebugExceptionInitializeComponent>();
Register<DebugExceptionStartupComponent>();
#endif

View File

@@ -6,6 +6,7 @@ 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;
@@ -721,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
@@ -736,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)
{
@@ -782,7 +784,7 @@ 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)

View File

@@ -1,21 +1,26 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using YamlDotNet.RepresentationModel;
using Robust.Shared.Utility;
using Robust.Shared.Serialization;
using Robust.Shared.GameObjects;
using System.Globalization;
using System.Linq;
using Robust.Server.GameObjects;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Result;
using Robust.Shared.Serialization.Markdown;
using Robust.Shared.Serialization.Markdown.Validation;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;
namespace Robust.Server.Maps
{
@@ -223,7 +228,10 @@ namespace Robust.Server.Maps
/// <summary>
/// Handles the primary bulk of state during the map serialization process.
/// </summary>
private class MapContext : YamlObjectSerializer.Context, IEntityLoadContext
private class MapContext : ISerializationContext, IEntityLoadContext,
ITypeSerializer<GridId, ValueDataNode>,
ITypeSerializer<EntityUid, ValueDataNode>,
ITypeReaderWriter<IEntity, ValueDataNode>
{
private readonly IMapManagerInternal _mapManager;
private readonly ITileDefinitionManager _tileDefinitionManager;
@@ -255,6 +263,11 @@ namespace Robust.Server.Maps
private Dictionary<ushort, string>? _tileMap;
public Dictionary<(Type, Type), object> TypeReaders { get; }
public Dictionary<Type, object> TypeWriters { get; }
public Dictionary<Type, object> TypeCopiers => TypeWriters;
public Dictionary<(Type, Type), object> TypeValidators => TypeReaders;
public bool MapIsPostInit { get; private set; }
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
@@ -269,6 +282,18 @@ namespace Robust.Server.Maps
_prototypeManager = prototypeManager;
RootNode = new YamlMappingNode();
TypeWriters = new Dictionary<Type, object>()
{
{typeof(IEntity), this},
{typeof(GridId), this},
{typeof(EntityUid), this}
};
TypeReaders = new Dictionary<(Type, Type), object>()
{
{(typeof(IEntity), typeof(ValueDataNode)), this},
{(typeof(GridId), typeof(ValueDataNode)), this},
{(typeof(EntityUid), typeof(ValueDataNode)), this}
};
}
public MapContext(IMapManagerInternal maps, ITileDefinitionManager tileDefs,
@@ -286,6 +311,18 @@ namespace Robust.Server.Maps
RootNode = node;
TargetMap = targetMapId;
_prototypeManager = prototypeManager;
TypeWriters = new Dictionary<Type, object>()
{
{typeof(IEntity), this},
{typeof(GridId), this},
{typeof(EntityUid), this}
};
TypeReaders = new Dictionary<(Type, Type), object>()
{
{(typeof(IEntity), typeof(ValueDataNode)), this},
{(typeof(GridId), typeof(ValueDataNode)), this},
{(typeof(EntityUid), typeof(ValueDataNode)), this}
};
}
// Deserialization
@@ -530,7 +567,10 @@ namespace Robust.Server.Maps
{
foreach (var compData in componentList)
{
CurrentReadingEntityComponents[compData["type"].AsString()] = (YamlMappingNode) compData;
var copy = new YamlMappingNode(((YamlMappingNode)compData).AsEnumerable());
copy.Children.Remove(new YamlScalarNode("type"));
//TODO Paul: maybe replace mapping with datanode
CurrentReadingEntityComponents[compData["type"].AsString()] = copy;
}
}
@@ -687,9 +727,11 @@ namespace Robust.Server.Maps
private void WriteEntitySection()
{
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var entities = new YamlSequenceNode();
RootNode.Add("entities", entities);
var prototypeCompCache = new Dictionary<string, Dictionary<string, MappingDataNode>>();
foreach (var entity in Entities.OrderBy(e => EntityUidMap[e.Uid]))
{
CurrentWritingEntity = entity;
@@ -701,6 +743,14 @@ namespace Robust.Server.Maps
if (entity.Prototype != null)
{
mapping.Add("type", entity.Prototype.ID);
if (!prototypeCompCache.ContainsKey(entity.Prototype.ID))
{
prototypeCompCache[entity.Prototype.ID] = new Dictionary<string, MappingDataNode>();
foreach (var (compType, comp) in entity.Prototype.Components)
{
prototypeCompCache[entity.Prototype.ID].Add(compType, serializationManager.WriteValueAs<MappingDataNode>(comp.GetType(), comp));
}
}
}
var components = new YamlSequenceNode();
@@ -710,18 +760,21 @@ namespace Robust.Server.Maps
if (component is MapSaveIdComponent)
continue;
var compMapping = new YamlMappingNode();
CurrentWritingComponent = component.Name;
var compSerializer = YamlObjectSerializer.NewWriter(compMapping, this);
var compMapping = serializationManager.WriteValueAs<MappingDataNode>(component.GetType(), component, context: this);
component.ExposeData(compSerializer);
if (entity.Prototype != null && prototypeCompCache[entity.Prototype.ID].TryGetValue(component.Name, out var protMapping))
{
compMapping = compMapping.Except(protMapping);
if(compMapping == null) continue;
}
// Don't need to write it if nothing was written!
if (compMapping.Children.Count != 0)
{
compMapping.AddNode("type", new ValueDataNode(component.Name));
// Something actually got written!
compMapping.Add("type", component.Name);
components.Add(compMapping);
components.Add(compMapping.ToYamlNode());
}
}
@@ -734,140 +787,32 @@ namespace Robust.Server.Maps
}
}
public override bool TryNodeToType(YamlNode node, Type type, [NotNullWhen(true)] out object? obj)
{
if (type == typeof(GridId))
{
if (node.AsString() == "null")
{
obj = GridId.Invalid;
return true;
}
var val = node.AsInt();
if (val >= Grids.Count)
{
Logger.ErrorS("map", "Error in map file: found local grid ID '{0}' which does not exist.", val);
}
else
{
obj = Grids[val].Index;
return true;
}
}
if (type == typeof(EntityUid))
{
if (node.AsString() == "null")
{
obj = EntityUid.Invalid;
return true;
}
var val = node.AsInt();
if (val >= Entities.Count)
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.",
val);
}
else
{
obj = UidEntityMap[val];
return true;
}
}
if (typeof(IEntity).IsAssignableFrom(type))
{
var val = node.AsInt();
if (val >= Entities.Count)
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.",
val);
}
else
{
obj = Entities[val];
return true;
}
}
obj = null;
return false;
}
public override bool TryTypeToNode(object obj, [NotNullWhen(true)] out YamlNode? node)
{
switch (obj)
{
case GridId gridId:
if (!GridIDMap.TryGetValue(gridId, out var gridMapped))
{
Logger.WarningS("map", "Cannot write grid ID '{0}', falling back to nullspace.", gridId);
break;
}
else
{
node = new YamlScalarNode(gridMapped.ToString(CultureInfo.InvariantCulture));
return true;
}
case EntityUid entityUid:
if (!EntityUidMap.TryGetValue(entityUid, out var entityUidMapped))
{
// Terrible hack to mute this warning on the grids themselves when serializing blueprints.
if (!IsBlueprintMode || !CurrentWritingEntity!.HasComponent<MapGridComponent>() ||
CurrentWritingComponent != "Transform")
{
Logger.WarningS("map", "Cannot write entity UID '{0}'.", entityUid);
}
node = new YamlScalarNode("null");
return true;
}
else
{
node = new YamlScalarNode(entityUidMapped.ToString(CultureInfo.InvariantCulture));
return true;
}
case IEntity entity:
if (!EntityUidMap.TryGetValue(entity.Uid, out var entityMapped))
{
Logger.WarningS("map", "Cannot write entity UID '{0}'.", entity.Uid);
break;
}
else
{
node = new YamlScalarNode(entityMapped.ToString(CultureInfo.InvariantCulture));
return true;
}
}
node = null;
return false;
}
// Create custom object serializers that will correctly allow data to be overriden by the map file.
ObjectSerializer IEntityLoadContext.GetComponentSerializer(string componentName, YamlMappingNode? protoData)
IComponent IEntityLoadContext.GetComponentData(string componentName,
IComponent? protoData)
{
if (CurrentReadingEntityComponents == null)
{
throw new InvalidOperationException();
}
var list = new List<YamlMappingNode>();
var serializationManager = IoCManager.Resolve<ISerializationManager>();
var factory = IoCManager.Resolve<IComponentFactory>();
IComponent data = protoData != null
? serializationManager.CreateCopy(protoData, this)!
: (IComponent) Activator.CreateInstance(factory.GetRegistration(componentName).Type)!;
if (CurrentReadingEntityComponents.TryGetValue(componentName, out var mapping))
{
list.Add(mapping);
var mapData = (IDeserializedDefinition) serializationManager.Read(
factory.GetRegistration(componentName).Type,
mapping.ToDataNode(), this);
var newData = serializationManager.PopulateDataDefinition(data, mapData);
data = (IComponent) newData.RawValue!;
}
if (protoData != null)
{
list.Add(protoData);
}
return YamlObjectSerializer.NewReader(list, this);
return data;
}
public IEnumerable<string> GetExtraComponentTypes()
@@ -875,34 +820,6 @@ namespace Robust.Server.Maps
return CurrentReadingEntityComponents!.Keys;
}
public override bool IsValueDefault<T>(string field, T value, WithFormat<T> format)
{
if (CurrentWritingEntity!.Prototype == null)
{
// No prototype, can't be default.
return false;
}
if (!CurrentWritingEntity.Prototype.Components.TryGetValue(CurrentWritingComponent!, out var compData))
{
// This component was added mid-game.
return false;
}
var testSer = YamlObjectSerializer.NewReader(compData);
if (testSer.TryReadDataFieldCached(field, format, out var prototypeVal))
{
if (value == null)
{
return prototypeVal == null;
}
return YamlObjectSerializer.IsSerializedEqual(value, prototypeVal);
}
return false;
}
private bool IsMapSavable(IEntity entity)
{
if (entity.Prototype?.MapSavable == false || !GridIDMap.ContainsKey(entity.Transform.GridID))
@@ -925,6 +842,165 @@ namespace Robust.Server.Maps
return true;
}
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context = null)
{
if (node.Value == "null") return new DeserializedValue<GridId>(GridId.Invalid);
var val = int.Parse(node.Value);
if (val >= Grids.Count)
{
Logger.ErrorS("map", "Error in map file: found local grid ID '{0}' which does not exist.", val);
}
else
{
return new DeserializedValue<GridId>(Grids[val].Index);
}
return new DeserializedValue<GridId>(GridId.Invalid);
}
ValidationNode ITypeValidator<IEntity, ValueDataNode>.Validate(ISerializationManager serializationManager,
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
{
if (!int.TryParse(node.Value, out var val) || !UidEntityMap.ContainsKey(val))
{
return new ErrorNode(node, "Invalid EntityUid", true);
}
return new ValidatedValueNode(node);
}
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
{
if (node.Value == "null")
{
return new ValidatedValueNode(node);
}
if (!int.TryParse(node.Value, out var val) || !UidEntityMap.ContainsKey(val))
{
return new ErrorNode(node, "Invalid EntityUid", true);
}
return new ValidatedValueNode(node);
}
ValidationNode ITypeValidator<GridId, ValueDataNode>.Validate(ISerializationManager serializationManager,
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
{
if (node.Value == "null") return new ValidatedValueNode(node);
if (!int.TryParse(node.Value, out var val) || val >= Grids.Count)
{
return new ErrorNode(node, "Invalid GridId", true);
}
return new ValidatedValueNode(node);
}
public DataNode Write(ISerializationManager serializationManager, IEntity value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
return Write(serializationManager, value.Uid, alwaysWrite, context);
}
public DataNode Write(ISerializationManager serializationManager, EntityUid value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
if (!EntityUidMap.TryGetValue(value, out var entityUidMapped))
{
// Terrible hack to mute this warning on the grids themselves when serializing blueprints.
if (!IsBlueprintMode || !CurrentWritingEntity!.HasComponent<MapGridComponent>() ||
CurrentWritingComponent != "Transform")
{
Logger.WarningS("map", "Cannot write entity UID '{0}'.", value);
}
return new ValueDataNode("null");
}
else
{
return new ValueDataNode(entityUidMapped.ToString(CultureInfo.InvariantCulture));
}
}
public DataNode Write(ISerializationManager serializationManager, GridId value, bool alwaysWrite = false,
ISerializationContext? context = null)
{
if (!GridIDMap.TryGetValue(value, out var gridMapped))
{
Logger.WarningS("map", "Cannot write grid ID '{0}', falling back to nullspace.", gridMapped);
return new ValueDataNode("");
}
else
{
return new ValueDataNode(gridMapped.ToString(CultureInfo.InvariantCulture));
}
}
DeserializationResult ITypeReader<EntityUid, ValueDataNode>.Read(ISerializationManager serializationManager,
ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context)
{
if (node.Value == "null")
{
return new DeserializedValue<EntityUid>(EntityUid.Invalid);
}
var val = int.Parse(node.Value);
if (val >= Entities.Count)
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
}
else
{
return new DeserializedValue<EntityUid>(UidEntityMap[val]);
}
return new DeserializedValue<EntityUid>(EntityUid.Invalid);
}
DeserializationResult ITypeReader<IEntity, ValueDataNode>.Read(ISerializationManager serializationManager,
ValueDataNode node,
IDependencyCollection dependencies,
bool skipHook,
ISerializationContext? context)
{
var val = int.Parse(node.Value);
if (val >= Entities.Count || !UidEntityMap.ContainsKey(val) || !Entities.TryFirstOrDefault(e => e.Uid == UidEntityMap[val], out var entity))
{
Logger.ErrorS("map", "Error in map file: found local entity UID '{0}' which does not exist.", val);
return null!;
}
else
{
return new DeserializedValue<IEntity>(entity);
}
}
[MustUseReturnValue]
public GridId Copy(ISerializationManager serializationManager, GridId source, GridId target,
bool skipHook,
ISerializationContext? context = null)
{
return new(source.Value);
}
[MustUseReturnValue]
public EntityUid Copy(ISerializationManager serializationManager, EntityUid source, EntityUid target,
bool skipHook,
ISerializationContext? context = null)
{
return new((int) source);
}
}
/// <summary>

View File

@@ -7,7 +7,6 @@ namespace Robust.Server.Player
{
public interface IPlayerSession : ICommonSession
{
INetChannel ConnectedClient { get; }
DateTime ConnectedTime { get; }
/// <summary>

View File

@@ -230,7 +230,7 @@ namespace Robust.Server.Scripting
}
else if (ScriptInstanceShared.HasReturnValue(newScript))
{
msg.AddText(CSharpObjectFormatter.Instance.FormatObject(instance.State.ReturnValue));
msg.AddText(ScriptInstanceShared.SafeFormat(instance.State.ReturnValue));
}
replyMessage.Response = msg;
@@ -290,7 +290,7 @@ namespace Robust.Server.Scripting
public override void show(object obj)
{
write(CSharpObjectFormatter.Instance.FormatObject(obj));
write(ScriptInstanceShared.SafeFormat(obj));
}
}
}

View File

@@ -18,6 +18,7 @@ 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.Timing;
@@ -45,6 +46,7 @@ namespace Robust.Server
IoCManager.Register<IMapLoader, MapLoader>();
IoCManager.Register<IPlacementManager, PlacementManager>();
IoCManager.Register<IPlayerManager, PlayerManager>();
IoCManager.Register<ISharedPlayerManager, PlayerManager>();
IoCManager.Register<IPrototypeManager, ServerPrototypeManager>();
IoCManager.Register<IReflectionManager, ServerReflectionManager>();
IoCManager.Register<IResourceManager, ResourceManager>();

View File

@@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Text;
using Robust.Shared.Maths;
@@ -220,5 +221,19 @@ namespace Robust.Shared.Scripting
.AddImports(_defaultImports)
.AddReferences(GetDefaultReferences(reflectionManager));
}
public static string SafeFormat(object obj)
{
// Working "around" https://github.com/dotnet/roslyn/issues/51548
try
{
return CSharpObjectFormatter.Instance.FormatObject(obj);
}
catch (NotSupportedException)
{
return "<CSharpObjectFormatter.FormatObject threw>";
}
}
}
}

View File

@@ -1,6 +1,8 @@
using Robust.Shared.Serialization;
using System;
using System.Diagnostics.Contracts;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Audio
{
@@ -8,43 +10,52 @@ namespace Robust.Shared.Audio
/// Contains common audio parameters for audio playback on the client.
/// </summary>
[Serializable, NetSerializable]
public struct AudioParams : IExposeData
[DataDefinition]
public struct AudioParams : IPopulateDefaultValues
{
/// <summary>
/// Base volume to play the audio at, in dB.
/// </summary>
[DataField("volume")]
public float Volume { get; set; }
/// <summary>
/// Scale for the audio pitch.
/// </summary>
[DataField("pitchscale")]
public float PitchScale { get; set; }
/// <summary>
/// Audio bus to play on.
/// </summary>
[DataField("busname")]
public string BusName { get; set; }
/// <summary>
/// Only applies to positional audio.
/// The maximum distance from which the audio is hearable.
/// </summary>
[DataField("maxdistance")]
public float MaxDistance { get; set; }
/// <summary>
/// Only applies to positional audio.
/// Positional audio is dampened over distance with this as exponent.
/// </summary>
[DataField("attenuation")]
public float Attenuation { get; set; }
/// <summary>
/// Only applies to global (non-positional) audio.
/// Target channels if the audio configuration has more than 2 speakers.
/// </summary>
[DataField("mixtarget")]
public AudioMixTarget MixTarget { get; set; }
[DataField("loop")]
public bool Loop { get; set; }
[DataField("playoffset")]
public float PlayOffsetSeconds { get; set; }
// For the max distance value: it's 2000 in Godot, but I assume that's PIXELS due to the 2D positioning,
@@ -54,18 +65,6 @@ namespace Robust.Shared.Audio
/// </summary>
public static readonly AudioParams Default = new(0, 1, "Master", 62.5f, 1, AudioMixTarget.Stereo, false, 0f);
void IExposeData.ExposeData(ObjectSerializer serializer)
{
Volume = serializer.ReadDataField("volume", 0f);
PitchScale = serializer.ReadDataField("pitchscale", 1f);
BusName = serializer.ReadDataField("busname", "Master");
MaxDistance = serializer.ReadDataField("maxdistance", 62.5f);
Attenuation = serializer.ReadDataField("attenuation", 1f);
MixTarget = serializer.ReadDataField("mixtarget", AudioMixTarget.Stereo);
Loop = serializer.ReadDataField("loop", false);
PlayOffsetSeconds = serializer.ReadDataField("playoffset", 0f);
}
public AudioParams(float volume, float pitchScale, string busName, float maxDistance, float attenuation,
AudioMixTarget mixTarget, bool loop, float playOffsetSeconds) : this()
{
@@ -169,6 +168,15 @@ namespace Robust.Shared.Audio
me.PlayOffsetSeconds = offset;
return me;
}
public void PopulateDefaultValues()
{
PitchScale = 1f;
BusName = "Master";
MaxDistance = 62.5f;
Attenuation = 1f;
MixTarget = AudioMixTarget.Stereo;
}
}
/// <summary>

View File

@@ -0,0 +1,48 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Player;
namespace Robust.Shared.Audio
{
/// <summary>
/// Common interface for the Audio System, which is used to play sounds on clients.
/// </summary>
public interface IAudioSystem
{
/// <summary>
/// Default max range at which the sound can be heard.
/// </summary>
int DefaultSoundRange { get; }
/// <summary>
/// Used in the PAS to designate the physics collision mask of occluders.
/// </summary>
int OcclusionCollisionMask { get; set; }
/// <summary>
/// Play an audio file globally, without position.
/// </summary>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null);
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <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">Audio parameters to apply when playing the sound.</param>
IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null);
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <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">Audio parameters to apply when playing the sound.</param>
IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null);
}
}

View File

@@ -0,0 +1,7 @@
namespace Robust.Shared.Audio
{
public interface IPlayingAudioStream
{
void Stop();
}
}

View File

@@ -0,0 +1,82 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Player;
namespace Robust.Shared.Audio
{
/// <summary>
/// A static proxy class for interfacing with the AudioSystem.
/// </summary>
public static class SoundSystem
{
/// <summary>
/// Default max range at which the sound can be heard.
/// </summary>
public static int DefaultSoundRange => GetAudio()?.DefaultSoundRange ?? 0;
/// <summary>
/// Used in the PAS to designate the physics collision mask of occluders.
/// </summary>
public static int OcclusionCollisionMask
{
get => GetAudio()?.OcclusionCollisionMask ?? 0;
set
{
var audio = GetAudio();
if (audio is null)
return;
audio.OcclusionCollisionMask = value;
}
}
private static IAudioSystem? GetAudio()
{
// There appears to be no way to get a System by interface.
var args = new QueryAudioSystem();
IoCManager.Resolve<IEntityManager>().EventBus.RaiseEvent(EventSource.Local, args);
return args.Audio;
}
/// <summary>
/// Play an audio file globally, without position.
/// </summary>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams">Audio parameters to apply when playing the sound.</param>
public static IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
{
return GetAudio()?.Play(playerFilter, filename, audioParams);
}
/// <summary>
/// Play an audio file following an entity.
/// </summary>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <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">Audio parameters to apply when playing the sound.</param>
public static IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
{
return GetAudio()?.Play(playerFilter, filename, entity, audioParams);
}
/// <summary>
/// Play an audio file at a static position.
/// </summary>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <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">Audio parameters to apply when playing the sound.</param>
public static IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return GetAudio()?.Play(playerFilter, filename, coordinates, audioParams);
}
internal class QueryAudioSystem : EntitySystemMessage
{
public IAudioSystem? Audio { get; set; }
}
}
}

View File

@@ -296,7 +296,7 @@ namespace Robust.Shared
// Box2D default is 0.5f
public static readonly CVarDef<float> TimeToSleep =
CVarDef.Create("physics.timetosleep", 0.50f);
CVarDef.Create("physics.timetosleep", 0.2f);
// - Solver
// These are the minimum recommended by Box2D with the standard being 8 velocity 3 position iterations.

View File

@@ -1,80 +1,23 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Server.GameObjects
namespace Robust.Shared.Containers
{
/// <summary>
/// Default implementation for containers,
/// cannot be inherited. If additional logic is needed,
/// this logic should go on the systems that are holding this container.
/// For example, inventory containers should be modified only through an inventory component.
/// </summary>
[UsedImplicitly]
public sealed class Container : BaseContainer
{
/// <summary>
/// The generic container class uses a list of entities
/// </summary>
private readonly List<IEntity> _containerList = new();
/// <inheritdoc />
public Container(string id, IContainerManager manager) : base(id, manager) { }
/// <inheritdoc />
public override IReadOnlyList<IEntity> ContainedEntities => _containerList;
/// <inheritdoc />
protected override void InternalInsert(IEntity toinsert)
{
_containerList.Add(toinsert);
base.InternalInsert(toinsert);
}
/// <inheritdoc />
protected override void InternalRemove(IEntity toremove)
{
_containerList.Remove(toremove);
base.InternalRemove(toremove);
}
/// <inheritdoc />
public override bool Contains(IEntity contained)
{
return _containerList.Contains(contained);
}
/// <inheritdoc />
public override void Shutdown()
{
base.Shutdown();
foreach (var entity in _containerList)
{
entity.Delete();
}
}
}
/// <summary>
/// Base container class that all container inherit from.
/// </summary>
public abstract class BaseContainer : IContainer
{
/// <inheritdoc />
public IContainerManager Manager { get; private set; }
[ViewVariables]
public abstract IReadOnlyList<IEntity> ContainedEntities { get; }
/// <inheritdoc />
[ViewVariables]
public string ID { get; }
/// <inheritdoc />
[ViewVariables]
public IEntity Owner => Manager.Owner;
public abstract string ContainerType { get; }
/// <inheritdoc />
[ViewVariables]
@@ -82,47 +25,46 @@ namespace Robust.Server.GameObjects
/// <inheritdoc />
[ViewVariables]
public abstract IReadOnlyList<IEntity> ContainedEntities { get; }
public string ID { get; internal set; } = default!; // Make sure you set me in init
/// <inheritdoc />
public IContainerManager Manager { get; internal set; } = default!; // Make sure you set me in init
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public bool ShowContents { get; set; }
[field: DataField("occludes")]
public bool OccludesLight { get; set; } = true;
/// <inheritdoc />
[ViewVariables]
public IEntity Owner => Manager.Owner;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
public bool OccludesLight { get; set; }
[field: DataField("showEnts")]
public bool ShowContents { get; set; }
/// <summary>
/// DO NOT CALL THIS METHOD DIRECTLY!
/// You want <see cref="IContainerManager.MakeContainer{T}(string)" /> instead.
/// </summary>
protected BaseContainer(string id, IContainerManager manager)
{
DebugTools.Assert(!string.IsNullOrEmpty(id));
DebugTools.AssertNotNull(manager);
ID = id;
Manager = manager;
}
protected BaseContainer() { }
/// <inheritdoc />
public bool Insert(IEntity toinsert)
{
DebugTools.Assert(!Deleted);
//Verify we can insert and that the object got properly removed from its current location
//Verify we can insert into this container
if (!CanInsert(toinsert))
return false;
var transform = toinsert.Transform;
if (transform.Parent == null) // Only true if Parent is the map entity
return false;
// CanInsert already checks nullability of Parent (or container forgot to call base that does)
if (toinsert.TryGetContainerMan(out var containerManager) && !containerManager.Remove(toinsert))
return false; // Can't remove from existing container, can't insert.
if(transform.Parent.Owner.TryGetContainerMan(out var containerManager) && !containerManager.Remove(toinsert))
{
// Can't remove from existing container, can't insert.
return false;
}
InternalInsert(toinsert);
transform.AttachParent(Owner.Transform);
@@ -133,19 +75,6 @@ namespace Robust.Server.GameObjects
return true;
}
/// <summary>
/// Implement to store the reference in whatever form you want
/// </summary>
/// <param name="toinsert"></param>
protected virtual void InternalInsert(IEntity toinsert)
{
DebugTools.Assert(!Deleted);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntInsertedIntoContainerMessage(toinsert, this));
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toinsert, false));
Manager.Dirty();
}
/// <inheritdoc />
public virtual bool CanInsert(IEntity toinsert)
{
@@ -155,6 +84,10 @@ namespace Robust.Server.GameObjects
if (Owner == toinsert)
return false;
// no, you can't put maps or grids into containers
if (toinsert.HasComponent<IMapComponent>() || toinsert.HasComponent<IMapGridComponent>())
return false;
// Crucial, prevent circular insertion.
return !toinsert.Transform.ContainsEntity(Owner.Transform);
@@ -165,19 +98,13 @@ namespace Robust.Server.GameObjects
public bool Remove(IEntity toremove)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toremove);
DebugTools.Assert(toremove.IsValid());
if (toremove == null)
return true;
if (!CanRemove(toremove))
{
return false;
}
if (!CanRemove(toremove)) return false;
InternalRemove(toremove);
if (!toremove.IsValid())
return true;
toremove.Transform.AttachParentToContainerOrGrid();
return true;
}
@@ -186,27 +113,13 @@ namespace Robust.Server.GameObjects
public void ForceRemove(IEntity toRemove)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toRemove);
DebugTools.Assert(toRemove.IsValid());
InternalRemove(toRemove);
}
/// <summary>
/// Implement to remove the reference you used to store the entity
/// </summary>
/// <param name="toremove"></param>
protected virtual void InternalRemove(IEntity toremove)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toremove);
DebugTools.Assert(toremove.IsValid());
Owner?.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntRemovedFromContainerMessage(toremove, this));
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toremove, true));
Manager.Dirty();
}
/// <inheritdoc />
public virtual bool CanRemove(IEntity toremove)
{
@@ -223,39 +136,36 @@ namespace Robust.Server.GameObjects
Manager.InternalContainerShutdown(this);
Deleted = true;
}
}
/// <summary>
/// The contents of this container have been changed.
/// </summary>
public class ContainerContentsModifiedMessage : ComponentMessage
{
/// <summary>
/// Container whose contents were modified.
/// </summary>
public IContainer Container { get; }
/// <summary>
/// Entity that was added or removed from the container.
/// Implement to store the reference in whatever form you want
/// </summary>
public IEntity Entity { get; }
/// <summary>
/// If true, the entity was removed. If false, it was added to the container.
/// </summary>
public bool Removed { get; }
/// <summary>
/// Constructs a new instance of <see cref="ContainerContentsModifiedMessage"/>.
/// </summary>
/// <param name="container">Container whose contents were modified.</param>
/// <param name="entity">Entity that was added or removed in the container.</param>
/// <param name="removed">If true, the entity was removed. If false, it was added to the container.</param>
public ContainerContentsModifiedMessage(IContainer container, IEntity entity, bool removed)
/// <param name="toinsert"></param>
protected virtual void InternalInsert(IEntity toinsert)
{
Container = container;
Entity = entity;
Removed = removed;
DebugTools.Assert(!Deleted);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntInsertedIntoContainerMessage(toinsert, this));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(toinsert));
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toinsert, false));
Manager.Dirty();
}
/// <summary>
/// Implement to remove the reference you used to store the entity
/// </summary>
/// <param name="toremove"></param>
protected virtual void InternalRemove(IEntity toremove)
{
DebugTools.Assert(!Deleted);
DebugTools.AssertNotNull(Manager);
DebugTools.AssertNotNull(toremove);
DebugTools.Assert(toremove.IsValid());
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new EntRemovedFromContainerMessage(toremove, this));
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(toremove));
Manager.Owner.SendMessage(Manager, new ContainerContentsModifiedMessage(this, toremove, true));
Manager.Dirty();
}
}
}

View File

@@ -0,0 +1,64 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Containers
{
/// <summary>
/// Default implementation for containers,
/// cannot be inherited. If additional logic is needed,
/// this logic should go on the systems that are holding this container.
/// For example, inventory containers should be modified only through an inventory component.
/// </summary>
[UsedImplicitly]
[SerializedType(ClassName)]
public sealed class Container : BaseContainer
{
private const string ClassName = "Container";
/// <summary>
/// The generic container class uses a list of entities
/// </summary>
[DataField("ents")]
private readonly List<IEntity> _containerList = new();
/// <inheritdoc />
public override IReadOnlyList<IEntity> ContainedEntities => _containerList;
/// <inheritdoc />
public override string ContainerType => ClassName;
/// <inheritdoc />
protected override void InternalInsert(IEntity toinsert)
{
_containerList.Add(toinsert);
base.InternalInsert(toinsert);
}
/// <inheritdoc />
protected override void InternalRemove(IEntity toremove)
{
_containerList.Remove(toremove);
base.InternalRemove(toremove);
}
/// <inheritdoc />
public override bool Contains(IEntity contained)
{
return _containerList.Contains(contained);
}
/// <inheritdoc />
public override void Shutdown()
{
base.Shutdown();
foreach (var entity in _containerList)
{
entity.Delete();
}
}
}
}

View File

@@ -0,0 +1,38 @@
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
/// <summary>
/// The contents of this container have been changed.
/// </summary>
public class ContainerContentsModifiedMessage : ComponentMessage
{
/// <summary>
/// Container whose contents were modified.
/// </summary>
public IContainer Container { get; }
/// <summary>
/// Entity that was added or removed from the container.
/// </summary>
public IEntity Entity { get; }
/// <summary>
/// If true, the entity was removed. If false, it was added to the container.
/// </summary>
public bool Removed { get; }
/// <summary>
/// Constructs a new instance of <see cref="ContainerContentsModifiedMessage"/>.
/// </summary>
/// <param name="container">Container whose contents were modified.</param>
/// <param name="entity">Entity that was added or removed in the container.</param>
/// <param name="removed">If true, the entity was removed. If false, it was added to the container.</param>
public ContainerContentsModifiedMessage(IContainer container, IEntity entity, bool removed)
{
Container = container;
Entity = entity;
Removed = removed;
}
}
}

View File

@@ -1,5 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
@@ -9,6 +11,7 @@ namespace Robust.Shared.Containers
/// <summary>
/// Helper functions for the container system.
/// </summary>
[PublicAPI]
public static class ContainerHelpers
{
/// <summary>
@@ -23,9 +26,11 @@ namespace Robust.Shared.Containers
// Notice the recursion starts at the Owner of the passed in entity, this
// allows containers inside containers (toolboxes in lockers).
if (entity.Transform.Parent != null)
if (TryGetManagerComp(entity.Transform.Parent.Owner, out var containerComp))
return containerComp.ContainsEntity(entity);
if (entity.Transform.Parent == null)
return false;
if (TryGetManagerComp(entity.Transform.Parent.Owner, out var containerComp))
return containerComp.ContainsEntity(entity);
return false;
}
@@ -41,7 +46,8 @@ namespace Robust.Shared.Containers
DebugTools.AssertNotNull(entity);
DebugTools.Assert(!entity.Deleted);
if (entity.Transform.Parent != null && TryGetManagerComp(entity.Transform.Parent.Owner, out manager) && manager.ContainsEntity(entity))
var parentTransform = entity.Transform.Parent;
if (parentTransform != null && TryGetManagerComp(parentTransform.Owner, out manager) && manager.ContainsEntity(entity))
return true;
manager = default;
@@ -67,7 +73,7 @@ namespace Robust.Shared.Containers
}
/// <summary>
/// Attempts to remove an entity from its container, if any.
/// Attempts to remove an entity from its container, if any.
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
@@ -87,7 +93,6 @@ namespace Robust.Shared.Containers
container.ForceRemove(entity);
return true;
}
wasInContainer = false;
@@ -95,7 +100,7 @@ namespace Robust.Shared.Containers
}
/// <summary>
/// Attempts to remove an entity from its container, if any.
/// Attempts to remove an entity from its container, if any.
/// </summary>
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
@@ -106,7 +111,7 @@ namespace Robust.Shared.Containers
}
/// <summary>
/// Attempts to remove all entities in a container.
/// Attempts to remove all entities in a container.
/// </summary>
public static void EmptyContainer(this IContainer container, bool force = false, EntityCoordinates? moveTo = null, bool attachToGridOrMap = false)
{
@@ -128,7 +133,7 @@ namespace Robust.Shared.Containers
}
/// <summary>
/// Attempts to remove and delete all entities in a container.
/// Attempts to remove and delete all entities in a container.
/// </summary>
public static void CleanContainer(this IContainer container)
{
@@ -145,23 +150,16 @@ namespace Robust.Shared.Containers
if (transform.Parent == null
|| !TryGetContainer(transform.Parent.Owner, out var container)
|| !TryInsertIntoContainer(transform, container))
{
transform.AttachToGridOrMap();
}
}
private static bool TryInsertIntoContainer(this ITransformComponent transform, IContainer container)
{
if (container.Insert(transform.Owner))
{
return true;
}
if (container.Insert(transform.Owner)) return true;
if (container.Owner.Transform.Parent != null
&& TryGetContainer(container.Owner, out var newContainer))
{
return TryInsertIntoContainer(transform, newContainer);
}
return false;
}
@@ -190,19 +188,58 @@ namespace Robust.Shared.Containers
var isOtherContained = TryGetContainer(other, out var otherContainer);
// Both entities are not in a container
if (!isUserContained && !isOtherContained)
{
return true;
}
if (!isUserContained && !isOtherContained) return true;
// Both entities are in different contained states
if (isUserContained != isOtherContained)
{
return false;
}
if (isUserContained != isOtherContained) return false;
// Both entities are in the same container
return userContainer == otherContainer;
}
/// <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="entity">The entity to create the container for.</param>
/// <param name="containerId"></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 CreateContainer<T>(this IEntity entity, string containerId)
where T : IContainer
{
if (!entity.TryGetComponent<IContainerManager>(out var containermanager))
containermanager = entity.AddComponent<ContainerManagerComponent>();
return containermanager.MakeContainer<T>(containerId);
}
public static T EnsureContainer<T>(this IEntity entity, string containerId)
where T : IContainer
{
return EnsureContainer<T>(entity, containerId, out _);
}
public static T EnsureContainer<T>(this IEntity entity, string containerId, out bool alreadyExisted)
where T : IContainer
{
var containerManager = entity.EnsureComponent<ContainerManagerComponent>();
if (!containerManager.TryGetContainer(containerId, out var existing))
{
alreadyExisted = false;
return containerManager.MakeContainer<T>(containerId);
}
if (!(existing is T container))
{
throw new InvalidOperationException(
$"The container exists but is of a different type: {existing.GetType()}");
}
alreadyExisted = true;
return container;
}
}
}

View File

@@ -0,0 +1,402 @@
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.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Containers
{
/// <summary>
/// Holds data about a set of entity containers on this entity.
/// </summary>
// [RegisterComponent]
// [ComponentReference(typeof(IContainerManager))]
public class ContainerManagerComponent : Component, IContainerManager
{
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[ViewVariables]
[DataField("containers")]
private Dictionary<string, IContainer> _containers = new();
/// <inheritdoc />
public sealed override string Name => "ContainerContainer";
/// <inheritdoc />
public sealed override uint? NetID => NetIDs.CONTAINER_MANAGER;
/// <inheritdoc />
public override void OnRemove()
{
base.OnRemove();
// IContianer.Shutdown modifies the _containers collection
foreach (var container in _containers.Values.ToArray())
{
container.Shutdown();
}
_containers.Clear();
}
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
foreach (var container in _containers)
{
var baseContainer = (BaseContainer)container.Value;
baseContainer.Manager = this;
baseContainer.ID = container.Key;
}
}
/// <inheritdoc />
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.ContainerSet.Any(data => data.Id == 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 (containerType, id, showEnts, occludesLight, entityUids) in cast.ContainerSet)
{
if (!_containers.TryGetValue(id, out var container))
{
container = ContainerFactory(containerType, id);
_containers.Add(id, container);
}
// sync show flag
container.ShowContents = showEnts;
container.OccludesLight = occludesLight;
// Remove gone entities.
List<IEntity>? toRemove = null;
foreach (var entity in container.ContainedEntities)
{
if (!entityUids.Contains(entity.Uid))
{
toRemove ??= new List<IEntity>();
toRemove.Add(entity);
}
}
if (toRemove != null)
{
foreach (var goner in toRemove)
{
container.Remove(goner);
}
}
// Add new entities.
foreach (var uid in entityUids)
{
var entity = Owner.EntityManager.GetEntity(uid);
if (!container.ContainedEntities.Contains(entity)) container.Insert(entity);
}
}
}
private IContainer ContainerFactory(string containerType, string id)
{
var type = _serializer.FindSerializedType(typeof(IContainer), containerType);
if (type is null) throw new ArgumentException($"Container of type {containerType} for id {id} cannot be found.");
var newContainer = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
newContainer.ID = id;
newContainer.Manager = this;
return newContainer;
}
/// <inheritdoc />
public override ComponentState GetComponentState(ICommonSession player)
{
// naive implementation that just sends the full state of the component
List<ContainerManagerComponentState.ContainerData> containerSet = new();
foreach (var container in _containers.Values)
{
var uidArr = new EntityUid[container.ContainedEntities.Count];
for (var index = 0; index < container.ContainedEntities.Count; index++)
{
var iEntity = container.ContainedEntities[index];
uidArr[index] = iEntity.Uid;
}
var sContainer = new ContainerManagerComponentState.ContainerData(container.ContainerType, container.ID, container.ShowContents, container.OccludesLight, uidArr);
containerSet.Add(sContainer);
}
return new ContainerManagerComponentState(containerSet);
}
/// <inheritdoc />
public T MakeContainer<T>(string id)
where T : IContainer
{
return (T) MakeContainer(id, typeof(T));
}
/// <inheritdoc />
public IContainer GetContainer(string id)
{
return _containers[id];
}
/// <inheritdoc />
public bool HasContainer(string id)
{
return _containers.ContainsKey(id);
}
/// <inheritdoc />
public bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container)
{
var ret = _containers.TryGetValue(id, out var cont);
container = cont!;
return ret;
}
/// <inheritdoc />
public 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;
}
/// <inheritdoc />
public bool ContainsEntity(IEntity entity)
{
foreach (var container in _containers.Values)
{
if (!container.Deleted && container.Contains(entity)) return true;
}
return false;
}
/// <inheritdoc />
public void ForceRemove(IEntity entity)
{
foreach (var container in _containers.Values)
{
if (container.Contains(entity)) container.ForceRemove(entity);
}
}
/// <inheritdoc />
public void InternalContainerShutdown(IContainer container)
{
_containers.Remove(container.ID);
}
/// <inheritdoc />
public bool Remove(IEntity entity)
{
foreach (var containers in _containers.Values)
{
if (containers.Contains(entity)) return containers.Remove(entity);
}
return true; // If we don't contain the entity, it will always be removed
}
/// <inheritdoc />
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.ContainedEntities)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
new UpdateContainerOcclusionMessage(containerEntity));
}
}
}
private IContainer MakeContainer(string id, Type type)
{
if (HasContainer(id)) throw new ArgumentException($"Container with specified ID already exists: '{id}'");
var container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type);
container.ID = id;
container.Manager = this;
_containers[id] = container;
Dirty();
return container;
}
public AllContainersEnumerable GetAllContainers()
{
return new(this);
}
[Serializable, NetSerializable]
internal class ContainerManagerComponentState : ComponentState
{
public List<ContainerData> ContainerSet;
public ContainerManagerComponentState(List<ContainerData> containers) : base(NetIDs.CONTAINER_MANAGER)
{
ContainerSet = containers;
}
[Serializable, NetSerializable]
public readonly struct ContainerData
{
public readonly string ContainerType;
public readonly string Id;
public readonly bool ShowContents;
public readonly bool OccludesLight;
public readonly EntityUid[] ContainedEntities;
public ContainerData(string containerType, string id, bool showContents, bool occludesLight, EntityUid[] containedEntities)
{
ContainerType = containerType;
Id = id;
ShowContents = showContents;
OccludesLight = occludesLight;
ContainedEntities = containedEntities;
}
public void Deconstruct(out string type, out string id, out bool showEnts, out bool occludesLight, out EntityUid[] ents)
{
type = ContainerType;
id = Id;
showEnts = ShowContents;
occludesLight = OccludesLight;
ents = ContainedEntities;
}
}
}
[DataDefinition]
private struct ContainerPrototypeData : IPopulateDefaultValues
{
[DataField("entities")]
public List<EntityUid> Entities;
[DataField("type")]
public string? Type;
public ContainerPrototypeData(List<EntityUid> entities, string type)
{
Entities = entities;
Type = type;
}
public void PopulateDefaultValues()
{
Entities = new List<EntityUid>();
}
}
public readonly 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._containers.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

@@ -0,0 +1,28 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
/// <summary>
/// Raised when the contents of a container have been modified.
/// </summary>
[PublicAPI]
public abstract class ContainerModifiedMessage : EntitySystemMessage
{
/// <summary>
/// The container being acted upon.
/// </summary>
public IContainer Container { get; }
/// <summary>
/// The entity that was removed or inserted from/into the container.
/// </summary>
public IEntity Entity { get; }
protected ContainerModifiedMessage(IEntity entity, IContainer container)
{
Entity = entity;
Container = container;
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Containers
{
[UsedImplicitly]
[SerializedType(ClassName)]
public class ContainerSlot : BaseContainer
{
private const string ClassName = "ContainerSlot";
/// <inheritdoc />
public override IReadOnlyList<IEntity> ContainedEntities
{
get
{
if (ContainedEntity == null) return Array.Empty<IEntity>();
return new List<IEntity> {ContainedEntity};
}
}
[ViewVariables]
[field: DataField("ent")]
public IEntity? ContainedEntity { get; private set; }
/// <inheritdoc />
public override string ContainerType => ClassName;
/// <inheritdoc />
public override bool CanInsert(IEntity toinsert)
{
if (ContainedEntity != null)
return false;
return base.CanInsert(toinsert);
}
/// <inheritdoc />
public override bool Contains(IEntity contained)
{
if (contained == ContainedEntity)
return true;
return false;
}
/// <inheritdoc />
protected override void InternalInsert(IEntity toinsert)
{
ContainedEntity = toinsert;
base.InternalInsert(toinsert);
}
/// <inheritdoc />
protected override void InternalRemove(IEntity toremove)
{
ContainedEntity = null;
base.InternalRemove(toremove);
}
/// <inheritdoc />
public override void Shutdown()
{
base.Shutdown();
ContainedEntity?.Delete();
}
}
}

View File

@@ -1,9 +1,10 @@
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
namespace Robust.Shared.Containers
{
internal sealed class ContainerSystem : EntitySystem
public class ContainerSystem : EntitySystem
{
/// <inheritdoc />
public override void Initialize()
{
SubscribeLocalEvent<EntParentChangedMessage>(HandleParentChanged);
@@ -18,9 +19,7 @@ namespace Robust.Server.GameObjects
return;
if (oldParentEntity.TryGetComponent(out IContainerManager? containerManager))
{
containerManager.ForceRemove(message.Entity);
}
}
}
}

View File

@@ -0,0 +1,14 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
/// <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

@@ -0,0 +1,14 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
/// <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) { }
}
}

View File

@@ -1,42 +1,43 @@
using System;
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects
namespace Robust.Shared.Containers
{
/// <summary>
/// A container is a way to "contain" entities inside other entities, in a logical way.
/// This is alike BYOND's <c>contents</c> system, except more advanced.
/// </summary>
/// <remarks>
/// <p>
/// Containers are logical separations of entities contained inside another entity.
/// for example, a crate with two separated compartments would have two separate containers.
/// If an entity inside compartment A drops something,
/// the dropped entity would be placed in compartment A too,
/// and compartment B would be completely untouched.
/// </p>
/// <p>
/// Containers are managed by an entity's <see cref="IContainerManager" />,
/// and have an ID to be referenced by.
/// </p>
/// <p>
/// Containers are logical separations of entities contained inside another entity.
/// for example, a crate with two separated compartments would have two separate containers.
/// If an entity inside compartment A drops something,
/// the dropped entity would be placed in compartment A too,
/// and compartment B would be completely untouched.
/// </p>
/// <p>
/// Containers are managed by an entity's <see cref="IContainerManager" />,
/// and have an ID to be referenced by.
/// </p>
/// </remarks>
/// <seealso cref="IContainerManager" />
[PublicAPI]
[ImplicitDataDefinitionForInheritors]
public interface IContainer
{
/// <summary>
/// The container manager owning this container.
/// Readonly collection of all the entities contained within this specific container
/// </summary>
IContainerManager Manager { get; }
IReadOnlyList<IEntity> ContainedEntities { get; }
/// <summary>
/// The ID of this container.
/// The type of this container.
/// </summary>
string ID { get; }
/// <summary>
/// The entity owning this container.
/// </summary>
IEntity Owner { get; }
string ContainerType { get; }
/// <summary>
/// True if the container has been shut down via <see cref="Shutdown" />
@@ -44,16 +45,30 @@ namespace Robust.Shared.GameObjects
bool Deleted { get; }
/// <summary>
/// Readonly collection of all the entities contained within this specific container
/// The ID of this container.
/// </summary>
IReadOnlyList<IEntity> ContainedEntities { get; }
string ID { get; }
/// <summary>
/// The container manager owning this container.
/// </summary>
IContainerManager Manager { get; }
/// <summary>
/// Prevents light from escaping the container, from ex. a flashlight.
/// </summary>
bool OccludesLight { get; set; }
/// <summary>
/// The entity owning this container.
/// </summary>
IEntity Owner { get; }
/// <summary>
/// Should the contents of this container be shown? False for closed containers like lockers, true for
/// things like glass display cases.
/// </summary>
bool ShowContents { get; set; }
bool OccludesLight { get; set; }
/// <summary>
/// Checks if the entity can be inserted into this container.
@@ -91,6 +106,11 @@ namespace Robust.Shared.GameObjects
/// <returns>True if the entity was removed, false otherwise.</returns>
bool Remove(IEntity toremove);
/// <summary>
/// Forcefully removes an entity from the container. Normally you would want to use <see cref="Remove" />,
/// this function should be avoided.
/// </summary>
/// <param name="toRemove">The entity to attempt to remove.</param>
void ForceRemove(IEntity toRemove);
/// <summary>

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
namespace Robust.Shared.GameObjects
namespace Robust.Shared.Containers
{
/// <summary>
/// Manages containers on an entity.
@@ -17,7 +18,8 @@ namespace Robust.Shared.GameObjects
/// <typeparam name="T">The type of the new container</typeparam>
/// <returns>The new container.</returns>
/// <exception cref="ArgumentException">Thrown if there already is a container with the specified ID</exception>
T MakeContainer<T>(string id) where T: IContainer;
T MakeContainer<T>(string id)
where T : IContainer;
/// <summary>
/// Attempts to remove <paramref name="entity" /> contained inside the owning entity,
@@ -32,7 +34,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
/// <param name="id">The ID to look up.</param>
/// <returns>The container.</returns>
/// <exception cref="KeyNotFoundException" >Thrown if the container does not exist.</exception>
/// <exception cref="KeyNotFoundException">Thrown if the container does not exist.</exception>
IContainer GetContainer(string id);
/// <summary>
@@ -64,7 +66,7 @@ namespace Robust.Shared.GameObjects
void ForceRemove(IEntity entity);
/// <summary>
/// DO NOT CALL THIS DIRECTLY. Call <see cref="IContainer.Shutdown"/> instead.
/// DO NOT CALL THIS DIRECTLY. Call <see cref="IContainer.Shutdown" /> instead.
/// </summary>
void InternalContainerShutdown(IContainer container);
}

View File

@@ -0,0 +1,14 @@
using Robust.Shared.GameObjects;
namespace Robust.Shared.Containers
{
public readonly struct UpdateContainerOcclusionMessage
{
public IEntity Entity { get; }
public UpdateContainerOcclusionMessage(IEntity entity)
{
Entity = entity;
}
}
}

View File

@@ -28,6 +28,9 @@ namespace Robust.Shared.ContentPack
/// </summary>
internal sealed partial class AssemblyTypeChecker
{
// Used to be in Sandbox.yml, moved out of there to facilitate faster loading.
private const string SystemAssemblyName = "System.Runtime";
private readonly IResourceManager _res;
/// <summary>
@@ -43,13 +46,16 @@ namespace Robust.Shared.ContentPack
// Necessary for loads with launcher loader.
public Func<string, Stream?>? ExtraRobustLoader { get; init; }
private readonly ISawmill _sawmill;
private readonly SandboxConfig _config;
private readonly Task<SandboxConfig> _config;
public AssemblyTypeChecker(IResourceManager res, ISawmill sawmill)
{
_res = res;
_sawmill = sawmill;
_config = LoadConfig();
// Config is huge and YAML is slow so config loading is delayed.
// This means we can parallelize config loading with IL verification
// (first time we need the config is when we print verifier errors).
_config = Task.Run(LoadConfig);
}
private Resolver CreateResolver()
@@ -148,12 +154,14 @@ namespace Robust.Shared.ContentPack
return true;
}
var loadedConfig = _config.Result;
// We still do explicit type reference scanning, even though the actual whitelists work with raw members.
// This is so that we can simplify handling of generic type specifications during member checking:
// we won't have to check that any types in their type arguments are whitelisted.
foreach (var type in types)
{
if (!IsTypeAccessAllowed(type, out _))
if (!IsTypeAccessAllowed(loadedConfig, type, out _))
{
errors.Add(new SandboxError($"Access to type not allowed: {type}"));
}
@@ -161,11 +169,11 @@ namespace Robust.Shared.ContentPack
_sawmill.Debug($"Types... {fullStopwatch.ElapsedMilliseconds}ms");
CheckInheritance(inherited, errors);
CheckInheritance(loadedConfig, inherited, errors);
_sawmill.Debug($"Inheritance... {fullStopwatch.ElapsedMilliseconds}ms");
CheckMemberReferences(members, errors);
CheckMemberReferences(loadedConfig, members, errors);
foreach (var error in errors)
{
@@ -185,17 +193,33 @@ namespace Robust.Shared.ContentPack
{
_sawmill.Debug($"{name}: Verifying IL...");
var sw = Stopwatch.StartNew();
var ver = new Verifier(resolver);
ver.SetSystemModuleName(new AssemblyName(_config.SystemAssemblyName));
var verifyErrors = false;
foreach (var res in ver.Verify(peReader))
var bag = new ConcurrentBag<VerificationResult>();
var partitioner = Partitioner.Create(reader.TypeDefinitions);
Parallel.ForEach(partitioner.GetPartitions(Environment.ProcessorCount), handle =>
{
if (_config.AllowedVerifierErrors.Contains(res.Code))
var ver = new Verifier(resolver);
ver.SetSystemModuleName(new AssemblyName(SystemAssemblyName));
while (handle.MoveNext())
{
foreach (var result in ver.Verify(peReader, handle.Current, verifyMethods: true))
{
bag.Add(result);
}
}
});
var loadedCfg = _config.Result;
var verifyErrors = false;
foreach (var res in bag)
{
if (loadedCfg.AllowedVerifierErrors.Contains(res.Code))
{
continue;
}
var msg = $"{name}: ILVerify: {res.Message}";
var msg = $"{name}: ILVerify: {string.Format(res.Message, res.Args)}";
try
{
@@ -237,6 +261,7 @@ namespace Robust.Shared.ContentPack
}
private void CheckMemberReferences(
SandboxConfig sandboxConfig,
List<MMemberRef> members,
ConcurrentBag<SandboxError> errors)
{
@@ -273,7 +298,7 @@ namespace Robust.Shared.ContentPack
var baseTypeReferenced = (MTypeReferenced) baseType;
if (!IsTypeAccessAllowed(baseTypeReferenced, out var typeCfg))
if (!IsTypeAccessAllowed(sandboxConfig, baseTypeReferenced, out var typeCfg))
{
// Technically this error isn't necessary since we have an earlier pass
// checking all referenced types. That should have caught this
@@ -338,6 +363,7 @@ namespace Robust.Shared.ContentPack
}
private void CheckInheritance(
SandboxConfig sandboxConfig,
List<(MType type, MType parent, ArraySegment<MType> interfaceImpls)> inherited,
ConcurrentBag<SandboxError> errors)
{
@@ -367,7 +393,7 @@ namespace Robust.Shared.ContentPack
_ => throw new InvalidOperationException() // Can't happen.
};
if (!IsTypeAccessAllowed(realBaseType, out var cfg))
if (!IsTypeAccessAllowed(sandboxConfig, realBaseType, out var cfg))
{
return false;
}
@@ -377,13 +403,13 @@ namespace Robust.Shared.ContentPack
}
}
private bool IsTypeAccessAllowed(MTypeReferenced type, [NotNullWhen(true)] out TypeConfig? cfg)
private bool IsTypeAccessAllowed(SandboxConfig sandboxConfig, MTypeReferenced type, [NotNullWhen(true)] out TypeConfig? cfg)
{
if (type.Namespace == null)
{
if (type.ResolutionScope is MResScopeType parentType)
{
if (!IsTypeAccessAllowed((MTypeReferenced) parentType.Type, out var parentCfg))
if (!IsTypeAccessAllowed(sandboxConfig, (MTypeReferenced) parentType.Type, out var parentCfg))
{
cfg = null;
return false;
@@ -413,7 +439,7 @@ namespace Robust.Shared.ContentPack
}
// Check if in whitelisted namespaces.
foreach (var whNamespace in _config.WhitelistedNamespaces)
foreach (var whNamespace in sandboxConfig.WhitelistedNamespaces)
{
if (type.Namespace.StartsWith(whNamespace))
{
@@ -422,7 +448,7 @@ namespace Robust.Shared.ContentPack
}
}
if (!_config.Types.TryGetValue(type.Namespace, out var nsDict))
if (!sandboxConfig.Types.TryGetValue(type.Namespace, out var nsDict))
{
cfg = null;
return false;
@@ -749,8 +775,9 @@ namespace Robust.Shared.ContentPack
return handle.IsNil ? null : reader.GetString(handle);
}
private sealed class Resolver : ResolverBase
private sealed class Resolver : IResolver
{
private readonly ConcurrentDictionary<string, PEReader?> _dictionary = new();
private readonly AssemblyTypeChecker _parent;
private readonly string[] _diskLoadPaths;
private readonly ResourcePath[] _resLoadPaths;
@@ -762,7 +789,7 @@ namespace Robust.Shared.ContentPack
_resLoadPaths = resLoadPaths;
}
protected override PEReader? ResolveCore(string simpleName)
private PEReader? ResolveCore(string simpleName)
{
var dllName = $"{simpleName}.dll";
foreach (var diskLoadPath in _diskLoadPaths)
@@ -797,6 +824,11 @@ namespace Robust.Shared.ContentPack
return null;
}
public PEReader? Resolve(string simpleName)
{
return _dictionary.GetOrAdd(simpleName, ResolveCore);
}
}
private sealed class TypeProvider : ISignatureTypeProvider<MType, int>

View File

@@ -88,6 +88,8 @@ namespace Robust.Shared.ContentPack
if (_sandboxingEnabled)
{
var checkerSw = Stopwatch.StartNew();
var typeChecker = MakeTypeChecker();
Parallel.ForEach(files, pair =>
@@ -100,6 +102,8 @@ namespace Robust.Shared.ContentPack
throw new TypeCheckFailedException($"Assembly {name} failed type checks.");
}
});
Logger.DebugS("res.mod", $"Verified assemblies in {checkerSw.ElapsedMilliseconds}ms");
}
// Actually load them in the order they depend on each other.

View File

@@ -43,23 +43,7 @@ namespace Robust.Shared.ContentPack
/// <returns>Enumerable of all file paths in that directory and sub directories.</returns>
public static IEnumerable<string> GetFiles(string path)
{
var queue = new Queue<string>();
queue.Enqueue(path);
while (queue.Count > 0)
{
path = queue.Dequeue();
foreach (var subDir in Directory.GetDirectories(path))
{
queue.Enqueue(subDir);
}
foreach (var file in Directory.GetFiles(path))
{
yield return file;
}
}
return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories);
}
public static bool IsFileInUse(IOException exception)

View File

@@ -1,14 +1,13 @@
# This file controls all whitelists and other rules enforced by AssemblyTypeChecker.
# Yes, I typed most of this out by hand.
SystemAssemblyName: System.Runtime
# ILVerify errors that are allowed.
AllowedVerifierErrors:
# InitOnly happens a lot when calling e.g. ToString() on a readonly field.
# It's fine and doesn't break anything runtime related so...
- InitOnly
# ILVerify has problems with Default Interface Methods so...
- InterfaceMethodNotImplemented
# EVERYTHING in these namespaces is allowed.
WhitelistedNamespaces:

View File

@@ -1,8 +1,10 @@
using System;
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.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.ViewVariables;
@@ -10,6 +12,7 @@ namespace Robust.Shared.GameObjects
{
/// <inheritdoc />
[Reflect(false)]
[ImplicitDataDefinitionForInheritorsAttribute]
public abstract class Component : IComponent
{
/// <inheritdoc />
@@ -24,6 +27,7 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
public virtual bool NetworkSynchronizeExistence => false;
[DataField("netsync")]
private bool _netSyncEnabled = true;
/// <inheritdoc />
[ViewVariables]
@@ -159,12 +163,6 @@ namespace Robust.Shared.GameObjects
_running = false;
}
/// <inheritdoc />
public virtual void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref _netSyncEnabled, "netsync", true);
}
/// <inheritdoc />
public void Dirty()
{

View File

@@ -3,6 +3,7 @@
namespace Robust.Shared.GameObjects
{
[AttributeUsage(AttributeTargets.Field)]
[MeansImplicitAssignment]
public class ComponentDependencyAttribute : Attribute
{
public readonly string? OnAddMethodName;

View File

@@ -130,13 +130,6 @@ namespace Robust.Shared.GameObjects
ComponentAdded?.Invoke(this, new AddedComponentEventArgs(component));
}
if (entity.Initialized || entity.Initializing)
{
var defaultSerializer = DefaultValueSerializer.Reader();
defaultSerializer.CurrentType = component.GetType();
component.ExposeData(defaultSerializer);
}
_componentDependencyManager.OnComponentAdd(entity, component);
component.OnAdd();
@@ -450,7 +443,7 @@ namespace Robust.Shared.GameObjects
var comps = _entCompIndex[uid];
foreach (var comp in comps)
{
if (comp.Deleted || !(comp is T tComp)) continue;
if (comp.Deleted || comp is not T tComp) continue;
yield return tComp;
}

View File

@@ -1,37 +1,53 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Containers;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
public interface ICollideBehavior
/// <summary>
/// Called every tick for colliding bodies. Called once per pair.
/// </summary>
public sealed class CollisionMessage : EntitySystemMessage
{
public readonly IPhysBody BodyA;
public readonly IPhysBody BodyB;
public readonly float FrameTime;
public readonly Manifold Manifold;
public CollisionMessage(IPhysBody bodyA, IPhysBody bodyB, float frameTime, Manifold manifold)
{
BodyA = bodyA;
BodyB = bodyB;
FrameTime = frameTime;
Manifold = manifold;
}
}
/// <summary>
/// Called once when a collision starts
/// </summary>
public interface IStartCollide
{
/// <summary>
/// We'll pass in both our body and the other body to save the behaviors having to get these components themselves.
/// </summary>
void CollideWith(IPhysBody ourBody, IPhysBody otherBody);
void CollideWith(IPhysBody ourBody, IPhysBody otherBody, in Manifold manifold);
}
public interface IPostCollide
/// <summary>
/// Called once when a collision ends.
/// </summary>
public interface IEndCollide
{
/// <summary>
/// Run behaviour after all other collision behaviors have run.
/// </summary>
/// <param name="ourBody"></param>
/// <param name="otherBody"></param>
void PostCollide(IPhysBody ourBody, IPhysBody otherBody);
/// <param name="manifold"></param>
void CollideWith(IPhysBody ourBody, IPhysBody otherBody, in Manifold manifold);
}
public interface ICollideSpecial

View File

@@ -24,8 +24,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.Containers;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -38,15 +36,17 @@ using Robust.Shared.Physics.Dynamics.Contacts;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
{
[ComponentReference(typeof(IPhysBody))]
public sealed class PhysicsComponent : Component, IPhysBody
public sealed class PhysicsComponent : Component, IPhysBody, ISerializationHooks
{
private BodyStatus _bodyStatus;
[DataField("status")]
private BodyStatus _bodyStatus = BodyStatus.OnGround;
/// <inheritdoc />
public override string Name => "Physics";
@@ -131,7 +131,9 @@ namespace Robust.Shared.GameObjects
}
}
private BodyType _bodyType;
[DataField("bodyType")]
private BodyType _bodyType = BodyType.Static;
// We'll also block Static bodies from ever being awake given they don't need to move.
/// <inheritdoc />
@@ -144,23 +146,25 @@ namespace Robust.Shared.GameObjects
if (_awake == value)
return;
if (BodyType == BodyType.Static)
{
// Check nothing slipped through
DebugTools.Assert(!_awake);
return;
}
_awake = value;
if (value)
{
_sleepTime = 0.0f;
PhysicsMap.ContactManager.UpdateContacts(ContactEdges, true);
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsWakeMessage(this));
SendMessage(new PhysicsWakeCompMessage(this));
}
else
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsSleepMessage(this));
ResetDynamics();
_sleepTime = 0.0f;
PhysicsMap.ContactManager.UpdateContacts(ContactEdges, false);
SendMessage(new PhysicsSleepCompMessage(this));
}
Dirty();
@@ -191,7 +195,8 @@ namespace Robust.Shared.GameObjects
}
}
private bool _sleepingAllowed;
[DataField("sleepingAllowed")]
private bool _sleepingAllowed = true;
[ViewVariables]
public float SleepTime
@@ -208,6 +213,7 @@ namespace Robust.Shared.GameObjects
}
}
[DataField("sleepTime")]
private float _sleepTime;
/// <inheritdoc />
@@ -247,57 +253,17 @@ namespace Robust.Shared.GameObjects
}
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
void ISerializationHooks.AfterDeserialization()
{
base.ExposeData(serializer);
serializer.DataField(ref _bodyStatus, "status", BodyStatus.OnGround);
// Farseer defaults this to static buuut knowing our audience most are gonnna forget to set it.
serializer.DataField(ref _bodyType, "bodyType", BodyType.Dynamic);
serializer.DataField(ref _fixedRotation, "fixedRotation", true);
serializer.DataReadWriteFunction("fixtures", new List<Fixture>(), fixtures =>
foreach (var fixture in _fixtures)
{
foreach (var fixture in fixtures)
{
fixture.Body = this;
_fixtures.Add(fixture);
}
}, () => Fixtures);
fixture.Body = this;
}
serializer.DataReadWriteFunction("joints", new List<Joint>(), joints =>
{
if (joints.Count == 0) return;
// TODO: Brain no worky rn
throw new NotImplementedException();
}, () =>
{
var joints = new List<Joint>();
for (var jn = JointEdges; jn != null; jn = jn.Next)
{
joints.Add(jn.Joint);
}
return joints;
});
// TODO: Dump someday
serializer.DataReadFunction("anchored", true, value =>
{
_bodyType = value ? BodyType.Static : BodyType.Dynamic;
});
serializer.DataField(ref _linearDamping, "linearDamping", 0.02f);
serializer.DataField(ref _angularDamping, "angularDamping", 0.02f);
serializer.DataField(ref _mass, "mass", 1.0f);
if (_mass > 0f && BodyType == BodyType.Dynamic)
if (_mass > 0f && (BodyType & BodyType.Dynamic | BodyType.KinematicController) != 0)
{
_invMass = 1.0f / _mass;
}
serializer.DataField(ref _sleepingAllowed, "sleepingAllowed", true);
}
/// <inheritdoc />
@@ -484,6 +450,8 @@ namespace Robust.Shared.GameObjects
[ViewVariables]
public IReadOnlyList<Fixture> Fixtures => _fixtures;
[DataField("fixtures")]
[NeverPushInheritance]
private List<Fixture> _fixtures = new();
/// <summary>
@@ -509,6 +477,7 @@ namespace Robust.Shared.GameObjects
}
}
[DataField("canCollide")]
private bool _canCollide = true;
/// <summary>
@@ -580,7 +549,7 @@ namespace Robust.Shared.GameObjects
[ViewVariables(VVAccess.ReadWrite)]
public float Mass
{
get => BodyType == BodyType.Dynamic ? _mass : 0.0f;
get => (BodyType & (BodyType.Dynamic | BodyType.KinematicController)) != 0 ? _mass : 0.0f;
set
{
DebugTools.Assert(!float.IsNaN(value));
@@ -600,12 +569,14 @@ namespace Robust.Shared.GameObjects
}
}
private float _mass;
[DataField("mass")]
private float _mass = 1.0f;
/// <summary>
/// Inverse mass of the entity in kilograms (1 / Mass).
/// </summary>
public float InvMass => BodyType == BodyType.Dynamic ? _invMass : 0.0f;
[ViewVariables]
public float InvMass => (BodyType & (BodyType.Dynamic | BodyType.KinematicController)) != 0 ? _invMass : 0.0f;
private float _invMass;
@@ -647,11 +618,13 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Inverse moment of inertia (1 / I).
/// </summary>
[ViewVariables]
public float InvI { get; set; }
/// <summary>
/// Is the body allowed to have angular velocity.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public bool FixedRotation
{
get => _fixedRotation;
@@ -667,7 +640,9 @@ namespace Robust.Shared.GameObjects
}
}
private bool _fixedRotation;
// TODO: Should default to false someday IMO
[DataField("fixedRotation")]
private bool _fixedRotation = true;
// TODO: Will be used someday
/// <summary>
@@ -751,7 +726,8 @@ namespace Robust.Shared.GameObjects
}
}
private float _linearDamping;
[DataField("linearDamping")]
private float _linearDamping = 0.02f;
/// <summary>
/// This is a set amount that the body's angular velocity is reduced every tick.
@@ -774,7 +750,8 @@ namespace Robust.Shared.GameObjects
}
}
private float _angularDamping;
[DataField("angularDamping")]
private float _angularDamping = 0.02f;
/// <summary>
/// Current linear velocity of the entity in meters per second.
@@ -785,7 +762,8 @@ namespace Robust.Shared.GameObjects
get => _linVelocity;
set
{
DebugTools.Assert(!float.IsNaN(value.X) && !float.IsNaN(value.Y));
// Curse you Q
// DebugTools.Assert(!float.IsNaN(value.X) && !float.IsNaN(value.Y));
if (BodyType == BodyType.Static)
return;
@@ -812,7 +790,8 @@ namespace Robust.Shared.GameObjects
get => _angVelocity;
set
{
DebugTools.Assert(!float.IsNaN(value));
// TODO: This and linearvelocity asserts
// DebugTools.Assert(!float.IsNaN(value));
if (BodyType == BodyType.Static)
return;
@@ -1084,6 +1063,7 @@ namespace Robust.Shared.GameObjects
HasProxies = true;
}
// TOOD: Need SetTransformIgnoreContacts so we can teleport body and /ignore contacts/
public void DestroyContacts()
{
ContactEdge? contactEdge = ContactEdges;
@@ -1102,12 +1082,6 @@ namespace Robust.Shared.GameObjects
return EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(this, offset, approx);
}
/// <inheritdoc />
public bool CanMove()
{
return BodyType == BodyType.Dynamic || (!Anchored && Mass > 0);
}
/// <inheritdoc />
public override void Initialize()
{
@@ -1127,12 +1101,10 @@ namespace Robust.Shared.GameObjects
if (!Awake)
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsSleepMessage(this));
SendMessage(new PhysicsSleepCompMessage(this));
}
else
{
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PhysicsWakeMessage(this));
SendMessage(new PhysicsWakeCompMessage(this));
}
if (Owner.IsInContainer())
@@ -1213,6 +1185,10 @@ namespace Robust.Shared.GameObjects
public override void OnRemove()
{
base.OnRemove();
// Need to do these immediately in case collision behaviors deleted the body
// TODO: Could be more optimal as currently broadphase will call this ANYWAY
DestroyContacts();
ClearProxies();
CanCollide = false;
}
@@ -1224,7 +1200,7 @@ namespace Robust.Shared.GameObjects
InvI = 0.0f;
// Sweep
if (BodyType == BodyType.Kinematic)
if ((BodyType & (BodyType.Kinematic | BodyType.KinematicController)) != 0)
{
return;
}
@@ -1290,8 +1266,8 @@ namespace Robust.Shared.GameObjects
/// <returns></returns>
internal bool ShouldCollide(PhysicsComponent other)
{
// At least one body should be dynamic.
if (_bodyType != BodyType.Dynamic && other._bodyType != BodyType.Dynamic)
if ((_bodyType & (BodyType.Kinematic | BodyType.Static)) != 0 &&
(other._bodyType & (BodyType.Kinematic | BodyType.Static)) != 0)
{
return false;
}

View File

@@ -1,27 +1,10 @@
using Robust.Shared.Physics;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// An optimisation component for stuff that should be set as collidable when its awake and non-collidable when asleep.
/// </summary>
[RegisterComponent]
public sealed class CollisionWakeComponent : Component
{
public override string Name => "CollisionWake";
public override void HandleMessage(ComponentMessage message, IComponent? component)
{
base.HandleMessage(message, component);
switch (message)
{
case PhysicsWakeCompMessage msg:
msg.Body.CanCollide = true;
break;
case PhysicsSleepCompMessage msg:
msg.Body.CanCollide = false;
break;
}
}
}
}

View File

@@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Serialization;
namespace Robust.Shared.GameObjects
{
public abstract class SharedContainerManagerComponent : Component, IContainerManager
{
public sealed override string Name => "ContainerContainer";
public sealed override uint? NetID => NetIDs.CONTAINER_MANAGER;
public abstract T MakeContainer<T>(string id) where T : IContainer;
public abstract bool Remove(IEntity entity);
public abstract IContainer GetContainer(string id);
public abstract bool HasContainer(string id);
public abstract bool TryGetContainer(string id, [NotNullWhen(true)] out IContainer? container);
/// <inheritdoc />
public abstract bool TryGetContainer(IEntity entity, [NotNullWhen(true)] out IContainer? container);
public abstract bool ContainsEntity(IEntity entity);
public abstract void ForceRemove(IEntity entity);
public abstract void InternalContainerShutdown(IContainer container);
[Serializable, NetSerializable]
protected class ContainerManagerComponentState : ComponentState
{
public Dictionary<string, ContainerData> Containers { get; }
public ContainerManagerComponentState(Dictionary<string, ContainerData> containers) : base(NetIDs.CONTAINER_MANAGER)
{
Containers = containers;
}
[Serializable, NetSerializable]
public struct ContainerData
{
public bool ShowContents;
public bool OccludesLight;
public EntityUid[] ContainedEntities;
}
}
public IEnumerable<IContainer> GetAllContainers() => GetAllContainersImpl();
// Separate impl method to facilitate method hiding in the subclasses.
protected abstract IEnumerable<IContainer> GetAllContainersImpl();
}
}

View File

@@ -20,16 +20,6 @@ namespace Robust.Shared.GameObjects
public override void OnAdd() => throw new NotSupportedException();
}
/// <summary>
/// Throws an exception in <see cref="ExposeData" />.
/// </summary>
public sealed class DebugExceptionExposeDataComponent : Component
{
public override string Name => "DebugExceptionExposeData";
public override void ExposeData(ObjectSerializer serializer) => throw new NotSupportedException();
}
/// <summary>
/// Throws an exception in <see cref="Initialize" />.
/// </summary>

View File

@@ -25,7 +25,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Run MoveEvent, RotateEvent, and UpdateEntityTree updates.
/// </summary>
void RunDeferred();
void RunDeferred(Box2 worldAABB);
/// <summary>
/// Disables or enables to ability to locally rotate the entity. When set it removes any local rotation.
@@ -118,7 +118,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
GridId GridID { get; }
bool UpdateEntityTree();
bool UpdateEntityTree(Box2? worldAABB = null);
void AttachToGridOrMap();
void AttachParent(ITransformComponent parent);

View File

@@ -1,8 +1,10 @@
using System;
using Robust.Shared.Map;
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.Shared.GameObjects
@@ -12,7 +14,9 @@ namespace Robust.Shared.GameObjects
public sealed override string Name => "Occluder";
public sealed override uint? NetID => NetIDs.OCCLUDER;
[DataField("enabled")]
private bool _enabled = true;
[DataField("boundingBox")]
private Box2 _boundingBox = new(-0.5f, -0.5f, 0.5f, 0.5f);
[ViewVariables(VVAccess.ReadWrite)]
@@ -53,14 +57,6 @@ namespace Robust.Shared.GameObjects
EntitySystem.Get<OccluderSystem>().AddOrUpdateEntity(Owner, Owner.Transform.Coordinates);
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _enabled, "enabled", true);
serializer.DataField(ref _boundingBox, "boundingBox", new Box2(-0.5f, -0.5f, 0.5f, 0.5f));
}
public override void OnRemove()
{
base.OnRemove();

View File

@@ -1,8 +1,5 @@
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects.Components.Localization
@@ -14,27 +11,15 @@ namespace Robust.Shared.GameObjects.Components.Localization
public override uint? NetID => NetIDs.GRAMMAR;
[ViewVariables]
[DataField("localizationId")]
public string LocalizationId = "";
[ViewVariables]
[DataField("gender")]
public Gender? Gender = null;
[ViewVariables]
[DataField("proper")]
public bool? ProperNoun = null;
public override void ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref LocalizationId, "localizationId", "");
if (serializer.TryReadDataFieldCached("gender", out string? gender0))
{
var refl = IoCManager.Resolve<IReflectionManager>();
if (refl.TryParseEnumReference(gender0!, out var gender))
{
Gender = (Gender)gender;
}
}
serializer.DataField(ref ProperNoun, "proper", null);
}
}
}

View File

@@ -1,7 +1,9 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -19,7 +21,8 @@ namespace Robust.Shared.GameObjects
public class MapComponent : Component, IMapComponent
{
[ViewVariables(VVAccess.ReadOnly)]
private MapId _mapIndex;
[DataField("index")]
private MapId _mapIndex = MapId.Nullspace;
/// <inheritdoc />
public override string Name => "Map";
@@ -59,14 +62,6 @@ namespace Robust.Shared.GameObjects
((TransformComponent) Owner.Transform).ChangeMapId(_mapIndex);
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _mapIndex, "index", MapId.Nullspace);
}
}
/// <summary>

View File

@@ -2,8 +2,10 @@
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -24,7 +26,8 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly IMapManager _mapManager = default!;
[ViewVariables(VVAccess.ReadOnly)]
private GridId _gridIndex;
[DataField("index")]
private GridId _gridIndex = GridId.Invalid;
/// <inheritdoc />
public override string Name => "MapGrid";
@@ -80,14 +83,6 @@ namespace Robust.Shared.GameObjects
_gridIndex = state.GridIndex;
Grid.HasGravity = state.HasGravity;
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _gridIndex, "index", GridId.Invalid);
}
}
/// <summary>

View File

@@ -3,6 +3,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.GameObjects
@@ -68,7 +69,9 @@ namespace Robust.Shared.GameObjects
{
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[DataField("name")]
private string? _entityName;
[DataField("desc")]
private string? _entityDescription;
private EntityPrototype? _entityPrototype;
@@ -160,18 +163,6 @@ namespace Robust.Shared.GameObjects
_entityPrototype = _prototypes.Index<EntityPrototype>(state.PrototypeId);
}
/// <inheritdoc />
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _entityName, "name", null);
serializer.DataField(ref _entityDescription, "desc", null);
//serializer.DataField(ref _entityPrototype, "proto", null,
// s => _prototypes.Index<EntityPrototype>(s),
// p => p.ID);
}
internal override void ClearTicks()
{
// Do not clear modified ticks.

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
@@ -54,16 +55,26 @@ namespace Robust.Shared.GameObjects
}
[Serializable, NetSerializable]
protected struct PrototypeLayerData : IExposeData
[DataDefinition]
public class PrototypeLayerData
{
[DataField("shader")]
public string? Shader;
[DataField("texture")]
public string? TexturePath;
[DataField("sprite")]
public string? RsiPath;
[DataField("state")]
public string? State;
public Vector2 Scale;
public Angle Rotation;
public bool Visible;
public Color Color;
[DataField("scale")]
public Vector2 Scale = Vector2.One;
[DataField("rotation")]
public Angle Rotation = Angle.Zero;
[DataField("visible")]
public bool Visible = true;
[DataField("color")]
public Color Color = Color.White;
[DataField("map")]
public List<string>? MapKeys;
public static PrototypeLayerData New()
@@ -75,19 +86,6 @@ namespace Robust.Shared.GameObjects
Visible = true,
};
}
void IExposeData.ExposeData(ObjectSerializer serializer)
{
serializer.DataField(ref Shader, "shader", null);
serializer.DataField(ref TexturePath, "texture", null);
serializer.DataField(ref RsiPath, "sprite", null);
serializer.DataField(ref State, "state", null);
serializer.DataField(ref Scale, "scale", Vector2.One);
serializer.DataField(ref Rotation, "rotation", Angle.Zero);
serializer.DataField(ref Visible, "visible", true);
serializer.DataField(ref Color, "color", Color.White);
serializer.DataField(ref MapKeys, "map", null);
}
}
}
}

View File

@@ -6,7 +6,9 @@ using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects
{
@@ -19,6 +21,7 @@ namespace Robust.Shared.GameObjects
public sealed override string Name => "SnapGrid";
private bool IsSet;
[DataField("offset")]
private SnapGridOffset _offset = SnapGridOffset.Center;
[Dependency] private readonly IMapManager _mapManager = default!;
@@ -53,13 +56,6 @@ namespace Robust.Shared.GameObjects
}
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref _offset, "offset", SnapGridOffset.Center);
}
/// <summary>
/// Returns an enumerable over all the entities which are one tile over in a certain direction.
/// </summary>

View File

@@ -1,13 +1,16 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Robust.Shared.Animations;
using Robust.Shared.Containers;
using Robust.Shared.IoC;
using Robust.Shared.Map;
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;
@@ -15,9 +18,13 @@ namespace Robust.Shared.GameObjects
{
internal class TransformComponent : Component, ITransformComponent, IComponentDebug
{
[DataField("parent")]
private EntityUid _parent;
private Vector2 _localPosition; // holds offset from grid, or offset from parent
[DataField("pos")]
private Vector2 _localPosition = Vector2.Zero; // holds offset from grid, or offset from parent
[DataField("rot")]
private Angle _localRotation; // local rotation
[DataField("noRot")]
private bool _noLocalRotation;
private Matrix3 _localMatrix = Matrix3.Identity;
@@ -445,7 +452,7 @@ namespace Robust.Shared.GameObjects
base.OnRemove();
}
public void RunDeferred()
public void RunDeferred(Box2 worldAABB)
{
// if we resolved to (close enough) to the OG position then no update.
if ((_oldCoords == null || _oldCoords.Equals(Coordinates)) &&
@@ -455,19 +462,19 @@ namespace Robust.Shared.GameObjects
}
RebuildMatrices();
UpdateEntityTree();
UpdateEntityTree(worldAABB);
if (_oldCoords != null)
{
Owner.EntityManager.EventBus.RaiseEvent(
EventSource.Local, new MoveEvent(Owner, _oldCoords.Value, Coordinates));
EventSource.Local, new MoveEvent(Owner, _oldCoords.Value, Coordinates, worldAABB));
_oldCoords = null;
}
if (_oldLocalRotation != null)
{
Owner.EntityManager.EventBus.RaiseEvent(
EventSource.Local, new RotateEvent(Owner, _oldLocalRotation.Value, _localRotation));
EventSource.Local, new RotateEvent(Owner, _oldLocalRotation.Value, _localRotation, worldAABB));
_oldLocalRotation = null;
}
}
@@ -674,16 +681,6 @@ namespace Robust.Shared.GameObjects
}
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataField(ref _parent, "parent", new EntityUid());
serializer.DataField(ref _localPosition, "pos", Vector2.Zero);
serializer.DataField(ref _localRotation, "rot", new Angle());
serializer.DataField(ref _noLocalRotation, "noRot", false);
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState(ICommonSession player)
@@ -809,7 +806,7 @@ namespace Robust.Shared.GameObjects
_invLocalMatrix = itransMat;
}
public bool UpdateEntityTree() => Owner.EntityManager.UpdateEntityTree(Owner);
public bool UpdateEntityTree(Box2? worldAABB = null) => Owner.EntityManager.UpdateEntityTree(Owner, worldAABB);
public string GetDebugString()
{
@@ -859,6 +856,7 @@ namespace Robust.Shared.GameObjects
/// <param name="localPosition">Current position offset of this entity.</param>
/// <param name="rotation">Current direction offset of this entity.</param>
/// <param name="parentId">Current parent transform of this entity.</param>
/// <param name="noLocalRotation"></param>
public TransformComponentState(Vector2 localPosition, Angle rotation, EntityUid parentId, bool noLocalRotation)
: base(NetIDs.TRANSFORM)
{
@@ -870,32 +868,50 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Raised whenever an entity moves.
/// There is no guarantee it will be raised if they move in worldspace, only when moved relative to their parent.
/// </summary>
public class MoveEvent : EntitySystemMessage
{
public MoveEvent(IEntity sender, EntityCoordinates oldPos, EntityCoordinates newPos)
public MoveEvent(IEntity sender, EntityCoordinates oldPos, EntityCoordinates newPos, Box2? worldAABB = null)
{
Sender = sender;
OldPosition = oldPos;
NewPosition = newPos;
WorldAABB = worldAABB;
}
public IEntity Sender { get; }
public EntityCoordinates OldPosition { get; }
public EntityCoordinates NewPosition { get; }
public bool Handled { get; set; }
/// <summary>
/// New AABB of the entity.
/// </summary>
public Box2? WorldAABB { get; }
}
/// <summary>
/// Raised whenever this entity rotates in relation to their parent.
/// </summary>
public class RotateEvent : EntitySystemMessage
{
public RotateEvent(IEntity sender, Angle oldRotation, Angle newRotation)
public RotateEvent(IEntity sender, Angle oldRotation, Angle newRotation, Box2? worldAABB = null)
{
Sender = sender;
OldRotation = oldRotation;
NewRotation = newRotation;
WorldAABB = worldAABB;
}
public IEntity Sender { get; }
public Angle OldRotation { get; }
public Angle NewRotation { get; }
/// <summary>
/// New AABB of the entity.
/// </summary>
public Box2? WorldAABB { get; }
}
}

View File

@@ -1,5 +1,8 @@
using System;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects
{
@@ -8,15 +11,28 @@ namespace Robust.Shared.GameObjects
public sealed override string Name => "UserInterface";
public sealed override uint? NetID => NetIDs.USERINTERFACE;
protected sealed class PrototypeData : IExposeData
[DataDefinition]
public sealed class PrototypeData : ISerializationHooks
{
public object UiKey { get; private set; } = default!;
public string ClientType { get; private set; } = default!;
public object UiKey { get; set; } = default!;
void IExposeData.ExposeData(ObjectSerializer serializer)
[DataField("key", readOnly: true, required: true)]
private string _uiKeyRaw = default!;
[DataField("type", readOnly: true, required: true)]
public string ClientType { get; set; } = default!;
void ISerializationHooks.AfterDeserialization()
{
UiKey = serializer.ReadStringEnumKey("key");
ClientType = serializer.ReadDataField<string>("type");
var reflectionManager = IoCManager.Resolve<IReflectionManager>();
if (reflectionManager.TryParseEnumReference(_uiKeyRaw, out var @enum))
{
UiKey = @enum;
return;
}
UiKey = _uiKeyRaw;
}
}

View File

@@ -570,7 +570,7 @@ namespace Robust.Shared.GameObjects
private readonly Dictionary<MapId, DynamicTree<IEntity>> _entityTreesPerMap =
new();
public virtual bool UpdateEntityTree(IEntity entity)
public virtual bool UpdateEntityTree(IEntity entity, Box2? worldAABB = null)
{
if (entity.Deleted)
{
@@ -602,7 +602,7 @@ namespace Robust.Shared.GameObjects
// for debugging
var necessary = 0;
if (entTree.AddOrUpdate(entity))
if (entTree.AddOrUpdate(entity, worldAABB))
{
++necessary;
}

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