mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0936cf3c7f | ||
|
|
43b75a69c2 | ||
|
|
c17c8d7a11 | ||
|
|
223fd8126f | ||
|
|
1d5559be4a | ||
|
|
0b749ff8bb | ||
|
|
069fa89fcb | ||
|
|
80f9f24243 | ||
|
|
93018c9843 | ||
|
|
e2675271d0 | ||
|
|
d1f7edecef | ||
|
|
b5a3c0b988 | ||
|
|
06e62b031a | ||
|
|
24707b7385 | ||
|
|
ab95f39f9f | ||
|
|
cdd38abab5 | ||
|
|
d751c0b3ab | ||
|
|
2ace0e9e5a | ||
|
|
31716f5104 | ||
|
|
fefcc7cba3 | ||
|
|
30df989e8d | ||
|
|
86bfea6bd4 | ||
|
|
d890f168c2 | ||
|
|
f888a810bf | ||
|
|
16249a4dde | ||
|
|
e33488ba55 | ||
|
|
bfe6eeddb1 | ||
|
|
7f540e741c | ||
|
|
b7855f5af5 |
17
.github/CODEOWNERS
vendored
17
.github/CODEOWNERS
vendored
@@ -1,14 +1,7 @@
|
||||
# Lines starting with '#' are comments.
|
||||
# Each line is a file pattern followed by one or more owners.
|
||||
# Last match in file takes precedence.
|
||||
|
||||
# These owners will be the default owners for everything in the repo.
|
||||
# * @defunkt
|
||||
* @Acruid @PJB3005 @Silvertorch5
|
||||
# Be they Fluent translations or Freemarker templates, I know them both!
|
||||
*.ftl @RemieRichards
|
||||
|
||||
# Order is important. The last matching pattern has the most precedence.
|
||||
# So if a pull request only touches javascript files, only these owners
|
||||
# will be requested to review.
|
||||
# *.js @octocat @github/js
|
||||
|
||||
# You can also use email addresses if you prefer.
|
||||
# docs/* docs@example.com
|
||||
# Ping for all PRs
|
||||
* @Acruid @PJB3005 @Silvertorch5
|
||||
1
Resources/Locale/en-US/ss14window.ftl
Normal file
1
Resources/Locale/en-US/ss14window.ftl
Normal file
@@ -0,0 +1 @@
|
||||
ss14window-placeholder-title = Exemplary Window Title Here
|
||||
4
Resources/Prototypes/dummy_entity.yml
Normal file
4
Resources/Prototypes/dummy_entity.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
- type: entity
|
||||
name: blank entity
|
||||
id: BlankEntity
|
||||
abstract: true
|
||||
10
Robust.Analyzers/Diagnostics.cs
Normal file
10
Robust.Analyzers/Diagnostics.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
42
Robust.Analyzers/MeansImplicitAssigmentSuppressor.cs
Normal file
42
Robust.Analyzers/MeansImplicitAssigmentSuppressor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -13,6 +13,7 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Utility;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
@@ -63,14 +64,18 @@ namespace Robust.Client.Audio.Midi
|
||||
bool IsAvailable { get; }
|
||||
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
void Shutdown();
|
||||
}
|
||||
|
||||
internal class MidiManager : IDisposable, IMidiManager
|
||||
internal class MidiManager : IMidiManager
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
public bool IsAvailable
|
||||
{
|
||||
get
|
||||
@@ -154,6 +159,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedBroadPhaseSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
@@ -298,7 +304,7 @@ namespace Robust.Client.Audio.Midi
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = IoCManager.Resolve<IPhysicsManager>().IntersectRayPenetration(
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
@@ -352,7 +358,7 @@ namespace Robust.Client.Audio.Midi
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public void Shutdown()
|
||||
{
|
||||
_alive = false;
|
||||
_midiThread?.Join();
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
41
Robust.Client/Console/Commands/PhysicsOverlayCommands.cs
Normal file
41
Robust.Client/Console/Commands/PhysicsOverlayCommands.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Robust.Client.Debugging;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class PhysicsOverlayCommands : IConsoleCommand
|
||||
{
|
||||
public string Command => "physics";
|
||||
public string Description => $"{Command} <contactnormals / contactpoints / shapes>";
|
||||
public string Help => $"{Command} <overlay>";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine($"Invalid number of args supplied");
|
||||
return;
|
||||
}
|
||||
|
||||
var system = EntitySystem.Get<DebugPhysicsSystem>();
|
||||
|
||||
switch (args[0])
|
||||
{
|
||||
case "contactnormals":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactNormals;
|
||||
break;
|
||||
case "contactpoints":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactPoints;
|
||||
break;
|
||||
case "shapes":
|
||||
system.Flags ^= PhysicsDebugFlags.Shapes;
|
||||
break;
|
||||
default:
|
||||
shell.WriteLine($"{args[0]} is not a recognised overlay");
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Robust.Client/Console/Commands/VelocitiesCommand.cs
Normal file
17
Robust.Client/Console/Commands/VelocitiesCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public class VelocitiesCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showvelocities";
|
||||
public string Description => "Displays your angular and linear velocities";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<VelocityDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@ using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
@@ -136,7 +139,7 @@ namespace Robust.Client.Debugging
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Mask: {Convert.ToString(body.CollisionMask, 2)}");
|
||||
row++;
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {((IPhysicsComponent)body).Anchored}");
|
||||
DrawString(screenHandle, _font, drawPos + new Vector2(0, row * lineHeight), $"Enabled: {body.CanCollide}, Hard: {body.Hard}, Anchored: {(body).BodyType == BodyType.Static}");
|
||||
row++;
|
||||
}
|
||||
|
||||
@@ -158,19 +161,22 @@ namespace Robust.Client.Debugging
|
||||
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
|
||||
foreach (var physBody in _physicsManager.GetCollidingEntities(mapId, viewport))
|
||||
foreach (var physBody in EntitySystem.Get<SharedBroadPhaseSystem>().GetCollidingEntities(mapId, viewport))
|
||||
{
|
||||
// all entities have a TransformComponent
|
||||
var transform = physBody.Entity.Transform;
|
||||
|
||||
var worldBox = physBody.WorldAABB;
|
||||
var worldBox = physBody.GetWorldAABB();
|
||||
if (worldBox.IsEmpty()) continue;
|
||||
|
||||
var colorEdge = Color.Red.WithAlpha(0.33f);
|
||||
var sleepThreshold = IoCManager.Resolve<IConfigurationManager>().GetCVar(CVars.TimeToSleep);
|
||||
|
||||
foreach (var shape in physBody.PhysicsShapes)
|
||||
foreach (var fixture in physBody.Fixtures)
|
||||
{
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, physBody.SleepAccumulator / (float) physBody.SleepThreshold);
|
||||
var shape = fixture.Shape;
|
||||
var sleepPercent = physBody.Awake ? physBody.SleepTime / sleepThreshold : 1.0f;
|
||||
shape.DebugDraw(drawing, transform.WorldMatrix, in viewport, sleepPercent);
|
||||
}
|
||||
|
||||
if (worldBox.Contains(mouseWorldPos))
|
||||
@@ -233,6 +239,16 @@ namespace Robust.Client.Debugging
|
||||
_handle.DrawCircle(origin, radius, color);
|
||||
}
|
||||
|
||||
public override void DrawPolygonShape(Vector2[] vertices, in Color color)
|
||||
{
|
||||
_handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, vertices, color);
|
||||
}
|
||||
|
||||
public override void DrawLine(Vector2 start, Vector2 end, in Color color)
|
||||
{
|
||||
_handle.DrawLine(start, end, color);
|
||||
}
|
||||
|
||||
public override void SetTransform(in Matrix3 transform)
|
||||
{
|
||||
_handle.SetTransform(transform);
|
||||
|
||||
163
Robust.Client/Debugging/DebugPhysicsSystem.cs
Normal file
163
Robust.Client/Debugging/DebugPhysicsSystem.cs
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Dynamics.Contacts;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
internal sealed class DebugPhysicsSystem : SharedDebugPhysicsSystem
|
||||
{
|
||||
/*
|
||||
* Used for debugging shapes, controllers, joints, contacts
|
||||
*/
|
||||
|
||||
private const int MaxContactPoints = 2048;
|
||||
internal int PointCount;
|
||||
|
||||
internal ContactPoint[] _points = new ContactPoint[MaxContactPoints];
|
||||
|
||||
public PhysicsDebugFlags Flags
|
||||
{
|
||||
get => _flags;
|
||||
set
|
||||
{
|
||||
if (value == _flags) return;
|
||||
|
||||
if (_flags == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
|
||||
|
||||
if (value == PhysicsDebugFlags.None)
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsDebugOverlay));
|
||||
|
||||
_flags = value;
|
||||
}
|
||||
}
|
||||
|
||||
private PhysicsDebugFlags _flags;
|
||||
|
||||
public override void HandlePreSolve(Contact contact, in Manifold oldManifold)
|
||||
{
|
||||
if ((Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
{
|
||||
Manifold manifold = contact.Manifold;
|
||||
|
||||
if (manifold.PointCount == 0)
|
||||
return;
|
||||
|
||||
Fixture fixtureA = contact.FixtureA!;
|
||||
|
||||
PointState[] state1, state2;
|
||||
CollisionManager.GetPointStates(out state1, out state2, oldManifold, manifold);
|
||||
|
||||
Span<Vector2> points = stackalloc Vector2[2];
|
||||
Vector2 normal;
|
||||
contact.GetWorldManifold(out normal, points);
|
||||
|
||||
for (int i = 0; i < manifold.PointCount && PointCount < MaxContactPoints; ++i)
|
||||
{
|
||||
if (fixtureA == null)
|
||||
_points[i] = new ContactPoint();
|
||||
|
||||
ContactPoint cp = _points[PointCount];
|
||||
cp.Position = points[i];
|
||||
cp.Normal = normal;
|
||||
cp.State = state2[i];
|
||||
_points[PointCount] = cp;
|
||||
++PointCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct ContactPoint
|
||||
{
|
||||
public Vector2 Normal;
|
||||
public Vector2 Position;
|
||||
public PointState State;
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum PhysicsDebugFlags : byte
|
||||
{
|
||||
None = 0,
|
||||
ContactPoints = 1 << 0,
|
||||
ContactNormals = 1 << 1,
|
||||
Shapes = 1 << 2,
|
||||
}
|
||||
|
||||
internal sealed class PhysicsDebugOverlay : Overlay
|
||||
{
|
||||
private DebugPhysicsSystem _physics = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public PhysicsDebugOverlay(DebugPhysicsSystem system) : base(nameof(PhysicsDebugOverlay))
|
||||
{
|
||||
_physics = system;
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
{
|
||||
if (_physics.Flags == PhysicsDebugFlags.None) return;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
{
|
||||
// Port DebugDrawing over.
|
||||
}
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.ContactPoints) != 0)
|
||||
{
|
||||
const float axisScale = 0.3f;
|
||||
|
||||
for (int i = 0; i < _physics.PointCount; ++i)
|
||||
{
|
||||
DebugPhysicsSystem.ContactPoint point = _physics._points[i];
|
||||
|
||||
if (point.State == PointState.Add)
|
||||
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 243, 77));
|
||||
else if (point.State == PointState.Persist)
|
||||
worldHandle.DrawCircle(point.Position, 0.5f, new Color(255, 77, 77, 77));
|
||||
|
||||
if ((_physics.Flags & PhysicsDebugFlags.ContactNormals) != 0)
|
||||
{
|
||||
Vector2 p1 = point.Position;
|
||||
Vector2 p2 = p1 + point.Normal * axisScale;
|
||||
worldHandle.DrawLine(p1, p2, new Color(255, 102, 230, 102));
|
||||
}
|
||||
}
|
||||
|
||||
_physics.PointCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace Robust.Client
|
||||
RegisterReflection();
|
||||
}
|
||||
|
||||
|
||||
internal static void RegisterReflection()
|
||||
{
|
||||
// Gets a handle to the shared and the current (client) dll.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
@@ -23,7 +24,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
Register<PhysicsComponent>();
|
||||
RegisterReference<PhysicsComponent, IPhysBody>();
|
||||
RegisterReference<PhysicsComponent, IPhysicsComponent>();
|
||||
|
||||
Register<CollisionWakeComponent>();
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
RegisterReference<ContainerManagerComponent, IContainerManager>();
|
||||
|
||||
RegisterIgnore("KeyBindingInput");
|
||||
|
||||
Register<InputComponent>();
|
||||
@@ -49,14 +55,10 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
Register<AnimationPlayerComponent>();
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
RegisterReference<ContainerManagerComponent, IContainerManager>();
|
||||
|
||||
Register<TimerComponent>();
|
||||
|
||||
#if DEBUG
|
||||
Register<DebugExceptionOnAddComponent>();
|
||||
Register<DebugExceptionExposeDataComponent>();
|
||||
Register<DebugExceptionInitializeComponent>();
|
||||
Register<DebugExceptionStartupComponent>();
|
||||
#endif
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed partial class ContainerManagerComponent
|
||||
{
|
||||
[DebuggerDisplay("ClientContainer {Owner.Uid}/{ID}")]
|
||||
private sealed class ClientContainer : IContainer
|
||||
{
|
||||
public List<IEntity> Entities { get; } = new List<IEntity>();
|
||||
|
||||
public ClientContainer(string id, ContainerManagerComponent manager)
|
||||
{
|
||||
ID = id;
|
||||
Manager = manager;
|
||||
}
|
||||
|
||||
[ViewVariables] public IContainerManager Manager { get; }
|
||||
[ViewVariables] public string ID { get; }
|
||||
[ViewVariables] public IEntity Owner => Manager.Owner;
|
||||
[ViewVariables] public bool Deleted { get; private set; }
|
||||
[ViewVariables] public IReadOnlyList<IEntity> ContainedEntities => Entities;
|
||||
[ViewVariables]
|
||||
public bool ShowContents { get; set; }
|
||||
[ViewVariables]
|
||||
public bool OccludesLight { get; set; }
|
||||
|
||||
public bool CanInsert(IEntity toinsert)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Insert(IEntity toinsert)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanRemove(IEntity toremove)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Remove(IEntity toremove)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ForceRemove(IEntity toRemove)
|
||||
{
|
||||
throw new NotSupportedException("Cannot directly modify containers on the client");
|
||||
}
|
||||
|
||||
public bool Contains(IEntity contained)
|
||||
{
|
||||
return Entities.Contains(contained);
|
||||
}
|
||||
|
||||
public void DoInsert(IEntity entity)
|
||||
{
|
||||
Entities.Add(entity);
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
|
||||
}
|
||||
|
||||
public void DoRemove(IEntity entity)
|
||||
{
|
||||
Entities.Remove(entity);
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new UpdateContainerOcclusionMessage(entity));
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
Deleted = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void InternalContainerShutdown(IContainer container)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
@@ -11,12 +10,14 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AudioSystem : EntitySystem
|
||||
public class AudioSystem : EntitySystem, IAudioSystem
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
@@ -24,17 +25,21 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
public int OcclusionCollisionMask;
|
||||
private readonly List<PlayingStream> _playingClydeStreams = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<PlayAudioEntityMessage>(PlayAudioEntityHandler);
|
||||
SubscribeNetworkEvent<PlayAudioGlobalMessage>(PlayAudioGlobalHandler);
|
||||
SubscribeNetworkEvent<PlayAudioPositionalMessage>(PlayAudioPositionalHandler);
|
||||
SubscribeNetworkEvent<StopAudioMessageClient>(StopAudioMessageHandler);
|
||||
|
||||
SubscribeLocalEvent<SoundSystem.QueryAudioSystem>((ev => ev.Audio = this));
|
||||
_broadPhaseSystem = Get<SharedBroadPhaseSystem>();
|
||||
}
|
||||
|
||||
private void StopAudioMessageHandler(StopAudioMessageClient ev)
|
||||
@@ -141,7 +146,7 @@ namespace Robust.Client.GameObjects
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = IoCManager.Resolve<IPhysicsManager>().IntersectRayPenetration(
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
@@ -176,7 +181,6 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
stream.Source.Dispose();
|
||||
stream.Done = true;
|
||||
stream.DoPlaybackDone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -335,92 +339,30 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
Source.StopPlaying();
|
||||
}
|
||||
|
||||
public event Action? PlaybackDone;
|
||||
|
||||
public void DoPlaybackDone()
|
||||
{
|
||||
PlaybackDone?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPlayingAudioStream
|
||||
{
|
||||
void Stop();
|
||||
|
||||
event Action PlaybackDone;
|
||||
}
|
||||
|
||||
public static class AudioSystemExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this IEntity entity,
|
||||
string filename,
|
||||
AudioParams? audioParams,
|
||||
AudioSystem? audioSystem = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(filename, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream following an entity.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this IEntity entity,
|
||||
AudioStream stream,
|
||||
AudioParams? audioParams = null,
|
||||
AudioSystem? audioSystem = null)
|
||||
/// <inheritdoc />
|
||||
public int DefaultSoundRange => 25;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, AudioParams? audioParams = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(stream, entity, audioParams);
|
||||
return Play(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this EntityCoordinates coordinates,
|
||||
string filename,
|
||||
AudioParams? audioParams = null,
|
||||
AudioSystem? audioSystem = null)
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, IEntity entity, AudioParams? audioParams = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(filename, coordinates, audioParams);
|
||||
return Play(filename, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio stream at a static position.
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
/// <param name="audioSystem">A pre-fetched instance of <see cref="AudioSystem"/> to use, can be null.</param>
|
||||
public static IPlayingAudioStream? Play(
|
||||
this EntityCoordinates coordinates,
|
||||
AudioStream stream,
|
||||
AudioParams? audioParams = null,
|
||||
AudioSystem? audioSystem = null)
|
||||
/// <inheritdoc />
|
||||
public IPlayingAudioStream? Play(Filter playerFilter, string filename, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
audioSystem ??= EntitySystem.Get<AudioSystem>();
|
||||
return audioSystem.Play(stream, coordinates, audioParams);
|
||||
return Play(filename, coordinates, audioParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public class VelocityDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
internal bool Enabled { get; set; }
|
||||
|
||||
private Label _label = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_label = new Label();
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
if (!Enabled)
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
|
||||
if (player == null || !player.TryGetComponent(out PhysicsComponent? body))
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var screenPos = _eyeManager.WorldToScreen(player.Transform.WorldPosition);
|
||||
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
|
||||
_label.Visible = true;
|
||||
|
||||
_label.Text = $"Speed: {body.LinearVelocity.Length}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular:{body.AngularVelocity}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace Robust.Client.GameStates
|
||||
handle.UseShader(_shader);
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
foreach (var boundingBox in _componentManager.EntityQuery<IPhysicsComponent>(true))
|
||||
foreach (var boundingBox in _componentManager.EntityQuery<IPhysBody>(true))
|
||||
{
|
||||
// all entities have a TransformComponent
|
||||
var transform = ((IComponent)boundingBox).Owner.Transform;
|
||||
@@ -42,7 +42,7 @@ namespace Robust.Client.GameStates
|
||||
if(transform.LerpDestination == null)
|
||||
continue;
|
||||
|
||||
var aabb = ((IPhysBody)boundingBox).AABB;
|
||||
var aabb = boundingBox.GetWorldAABB();
|
||||
|
||||
// if not on screen, or too small, continue
|
||||
if (!aabb.Translated(transform.WorldPosition).Intersects(viewport) || aabb.IsEmpty())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -36,6 +36,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly ConcurrentQueue<(int sourceHandle, int filterHandle)> _bufferedSourceDisposeQueue = new();
|
||||
private readonly ConcurrentQueue<int> _bufferDisposeQueue = new();
|
||||
|
||||
// The base gain value for a listener, used to boost the default volume.
|
||||
private const float _baseGain = 2f;
|
||||
|
||||
public bool HasAlDeviceExtension(string extension) => _alcDeviceExtensions.Contains(extension);
|
||||
public bool HasAlContextExtension(string extension) => _alContextExtensions.Contains(extension);
|
||||
|
||||
@@ -182,7 +185,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
{
|
||||
AL.Listener(ALListenerf.Gain, newVolume);
|
||||
AL.Listener(ALListenerf.Gain, _baseGain * newVolume);
|
||||
}
|
||||
|
||||
public IClydeAudioSource CreateAudioSource(AudioStream stream)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
13
Robust.Client/Physics/BroadPhaseSystem.cs
Normal file
13
Robust.Client/Physics/BroadPhaseSystem.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
|
||||
namespace Robust.Client.Physics
|
||||
{
|
||||
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(PhysicsSystem));
|
||||
}
|
||||
}
|
||||
}
|
||||
135
Robust.Client/Physics/DebugPhysicsIslandSystem.cs
Normal file
135
Robust.Client/Physics/DebugPhysicsIslandSystem.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Physics
|
||||
{
|
||||
internal sealed class PhysicsIslandCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "showislands";
|
||||
public string Description => "Shows the current physics bodies involved in each physics island.";
|
||||
public string Help => "showislands";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 0)
|
||||
{
|
||||
shell.WriteLine("This command doesn't take args!");
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<DebugPhysicsIslandSystem>().Mode ^= DebugPhysicsIslandMode.Solve;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DebugPhysicsIslandSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
public DebugPhysicsIslandMode Mode { get; set; } = DebugPhysicsIslandMode.None;
|
||||
|
||||
/*
|
||||
* Island solve debug:
|
||||
* This will draw above every body involved in a particular island solve.
|
||||
*/
|
||||
|
||||
public readonly Queue<(TimeSpan Time, List<IPhysBody> Bodies)> IslandSolve = new();
|
||||
public const float SolveDuration = 0.1f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<IslandSolveMessage>(HandleIslandSolveMessage);
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsIslandOverlay());
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
while (IslandSolve.TryPeek(out var solve))
|
||||
{
|
||||
if (solve.Time.TotalSeconds + SolveDuration > _gameTiming.CurTime.TotalSeconds)
|
||||
{
|
||||
IslandSolve.Dequeue();
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsIslandOverlay));
|
||||
}
|
||||
|
||||
private void HandleIslandSolveMessage(IslandSolveMessage message)
|
||||
{
|
||||
if ((Mode & DebugPhysicsIslandMode.Solve) == 0x0) return;
|
||||
IslandSolve.Enqueue((_gameTiming.CurTime, message.Bodies));
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum DebugPhysicsIslandMode : ushort
|
||||
{
|
||||
None = 0,
|
||||
Solve = 1 << 0,
|
||||
Contacts = 1 << 1,
|
||||
}
|
||||
|
||||
internal sealed class PhysicsIslandOverlay : Overlay
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private DebugPhysicsIslandSystem _islandSystem = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public PhysicsIslandOverlay() : base(nameof(PhysicsIslandOverlay))
|
||||
{
|
||||
_islandSystem = EntitySystem.Get<DebugPhysicsIslandSystem>();
|
||||
_eyeManager = IoCManager.Resolve<IEyeManager>();
|
||||
_gameTiming = IoCManager.Resolve<IGameTiming>();
|
||||
}
|
||||
|
||||
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace)
|
||||
{
|
||||
var worldHandle = (DrawingHandleWorld) handle;
|
||||
|
||||
DrawIslandSolve(worldHandle);
|
||||
}
|
||||
|
||||
private void DrawIslandSolve(DrawingHandleWorld handle)
|
||||
{
|
||||
if ((_islandSystem.Mode & DebugPhysicsIslandMode.Solve) == 0x0) return;
|
||||
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
|
||||
foreach (var solve in _islandSystem.IslandSolve)
|
||||
{
|
||||
var ratio = (float) Math.Max(
|
||||
(solve.Time.TotalSeconds + DebugPhysicsIslandSystem.SolveDuration -
|
||||
_gameTiming.CurTime.TotalSeconds) / DebugPhysicsIslandSystem.SolveDuration, 0.0f);
|
||||
|
||||
if (ratio <= 0.0f) continue;
|
||||
|
||||
foreach (var body in solve.Bodies)
|
||||
{
|
||||
var worldAABB = body.GetWorldAABB();
|
||||
if (!viewport.Intersects(worldAABB)) continue;
|
||||
|
||||
handle.DrawRect(worldAABB, Color.Green.WithAlpha(ratio * 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
using System;
|
||||
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.Utility;
|
||||
|
||||
namespace Robust.Client.Placement
|
||||
{
|
||||
@@ -87,7 +86,17 @@ namespace Robust.Client.Placement
|
||||
public bool Eraser { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The texture we use to show from our placement manager to represent the entity to place
|
||||
/// Holds the selection rectangle for the eraser
|
||||
/// </summary>
|
||||
public Box2? EraserRect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Drawing shader for drawing without being affected by lighting
|
||||
/// </summary>
|
||||
private ShaderInstance? _drawingShader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The texture we use to show from our placement manager to represent the entity to place
|
||||
/// </summary>
|
||||
public List<IDirectionalTextureProvider>? CurrentTextures { get; set; }
|
||||
|
||||
@@ -115,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);
|
||||
@@ -153,6 +154,8 @@ namespace Robust.Client.Placement
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_drawingShader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
|
||||
|
||||
NetworkManager.RegisterNetMessage<MsgPlacement>(MsgPlacement.NAME, HandlePlacementMessage);
|
||||
|
||||
_modeDictionary.Clear();
|
||||
@@ -182,7 +185,17 @@ namespace Robust.Client.Placement
|
||||
.Bind(EngineKeyFunctions.EditorGridPlace, InputCmdHandler.FromDelegate(
|
||||
session =>
|
||||
{
|
||||
if (IsActive && !Eraser) ActivateGridMode();
|
||||
if (IsActive)
|
||||
{
|
||||
if (Eraser)
|
||||
{
|
||||
EraseRectMode();
|
||||
}
|
||||
else
|
||||
{
|
||||
ActivateGridMode();
|
||||
}
|
||||
}
|
||||
}))
|
||||
.Bind(EngineKeyFunctions.EditorPlaceObject, new PointerStateInputCmdHandler(
|
||||
(session, coords, uid) =>
|
||||
@@ -190,6 +203,13 @@ namespace Robust.Client.Placement
|
||||
if (!IsActive)
|
||||
return false;
|
||||
|
||||
if (EraserRect.HasValue)
|
||||
{
|
||||
HandleRectDeletion(StartPoint, EraserRect.Value);
|
||||
EraserRect = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Eraser)
|
||||
{
|
||||
if (HandleDeletion(coords))
|
||||
@@ -308,6 +328,7 @@ namespace Robust.Client.Placement
|
||||
_placenextframe = false;
|
||||
IsActive = false;
|
||||
Eraser = false;
|
||||
EraserRect = null;
|
||||
PlacementOffset = Vector2i.Zero;
|
||||
}
|
||||
|
||||
@@ -384,6 +405,15 @@ namespace Robust.Client.Placement
|
||||
NetworkManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
public void HandleRectDeletion(EntityCoordinates start, Box2 rect)
|
||||
{
|
||||
var msg = NetworkManager.CreateNetMessage<MsgPlacement>();
|
||||
msg.PlaceType = PlacementManagerMessage.RequestRectRemove;
|
||||
msg.EntityCoordinates = new EntityCoordinates(StartPoint.EntityId, rect.BottomLeft);
|
||||
msg.RectSize = rect.Size;
|
||||
NetworkManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
public void ToggleEraser()
|
||||
{
|
||||
if (!Eraser && !IsActive)
|
||||
@@ -459,11 +489,62 @@ namespace Robust.Client.Placement
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CurrentEraserMouseCoordinates(out EntityCoordinates coordinates)
|
||||
{
|
||||
var ent = PlayerManager.LocalPlayer?.ControlledEntity;
|
||||
if (ent == null)
|
||||
{
|
||||
coordinates = new EntityCoordinates();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var map = ent.Transform.MapID;
|
||||
if (map == MapId.Nullspace || !Eraser)
|
||||
{
|
||||
coordinates = new EntityCoordinates();
|
||||
return false;
|
||||
}
|
||||
coordinates = EntityCoordinates.FromMap(ent.EntityManager, MapManager,
|
||||
eyeManager.ScreenToMap(new ScreenCoordinates(_inputManager.MouseScreenPosition)));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void FrameUpdate(FrameEventArgs e)
|
||||
{
|
||||
if (!CurrentMousePosition(out var mouseScreen))
|
||||
{
|
||||
if (EraserRect.HasValue)
|
||||
{
|
||||
if (!CurrentEraserMouseCoordinates(out EntityCoordinates end))
|
||||
return;
|
||||
float b, l, t, r;
|
||||
if (StartPoint.X < end.X)
|
||||
{
|
||||
l = StartPoint.X;
|
||||
r = end.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
l = end.X;
|
||||
r = StartPoint.X;
|
||||
}
|
||||
if (StartPoint.Y < end.Y)
|
||||
{
|
||||
b = StartPoint.Y;
|
||||
t = end.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
b = end.Y;
|
||||
t = StartPoint.Y;
|
||||
}
|
||||
EraserRect = new Box2(l, b, r, t);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentMode!.AlignPlacementMode(mouseScreen);
|
||||
|
||||
@@ -501,6 +582,15 @@ namespace Robust.Client.Placement
|
||||
PlacementType = PlacementTypes.Grid;
|
||||
}
|
||||
|
||||
private void EraseRectMode()
|
||||
{
|
||||
if (!CurrentEraserMouseCoordinates(out EntityCoordinates coordinates))
|
||||
return;
|
||||
|
||||
StartPoint = coordinates;
|
||||
EraserRect = new Box2(coordinates.Position, Vector2.Zero);
|
||||
}
|
||||
|
||||
private bool DeactivateSpecialPlacement()
|
||||
{
|
||||
if (PlacementType == PlacementTypes.None)
|
||||
@@ -513,7 +603,14 @@ namespace Robust.Client.Placement
|
||||
private void Render(DrawingHandleWorld handle)
|
||||
{
|
||||
if (CurrentMode == null || !IsActive)
|
||||
{
|
||||
if (EraserRect.HasValue)
|
||||
{
|
||||
handle.UseShader(_drawingShader);
|
||||
handle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentMode.Render(handle);
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Placement
|
||||
@@ -230,7 +232,7 @@ namespace Robust.Client.Placement
|
||||
bounds.Width,
|
||||
bounds.Height);
|
||||
|
||||
return pManager.PhysicsManager.TryCollideRect(collisionBox, mapCoords.MapId);
|
||||
return EntitySystem.Get<SharedBroadPhaseSystem>().TryCollideRect(collisionBox, mapCoords.MapId);
|
||||
}
|
||||
|
||||
protected Vector2 ScreenToWorld(Vector2 point)
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
public interface IPlayerManager
|
||||
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
|
||||
{
|
||||
IEnumerable<IPlayerSession> Sessions { get; }
|
||||
new IEnumerable<IPlayerSession> Sessions { get; }
|
||||
IReadOnlyDictionary<NetUserId, IPlayerSession> SessionsDict { get; }
|
||||
|
||||
LocalPlayer? LocalPlayer { get; }
|
||||
@@ -17,8 +17,6 @@ namespace Robust.Client.Player
|
||||
/// </summary>
|
||||
event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
|
||||
|
||||
int PlayerCount { get; }
|
||||
int MaxPlayers { get; }
|
||||
event EventHandler PlayerListUpdated;
|
||||
|
||||
void Initialize();
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
using Robust.Shared.Players;
|
||||
using System;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// Client side session of a player.
|
||||
/// Client side session of a player.
|
||||
/// </summary>
|
||||
public interface IPlayerSession : ICommonSession
|
||||
{
|
||||
/// <summary>
|
||||
/// Current name of this player.
|
||||
/// </summary>
|
||||
new string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current connection latency of this session from the server to their client.
|
||||
/// </summary>
|
||||
short Ping { get; set; }
|
||||
}
|
||||
[Obsolete("Use the base " + nameof(ICommonSession))]
|
||||
public interface IPlayerSession : ICommonSession { }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -30,6 +31,21 @@ namespace Robust.Client.Player
|
||||
/// </summary>
|
||||
private readonly Dictionary<NetUserId, IPlayerSession> _sessions = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ICommonSession> NetworkedSessions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (LocalPlayer is not null)
|
||||
return new[] {LocalPlayer.Session};
|
||||
|
||||
return Enumerable.Empty<ICommonSession>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerable<ICommonSession> ISharedPlayerManager.Sessions => _sessions.Values;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int PlayerCount => _sessions.Values.Count;
|
||||
|
||||
@@ -52,9 +68,9 @@ namespace Robust.Client.Player
|
||||
private LocalPlayer? _localPlayer;
|
||||
public event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables] public IEnumerable<IPlayerSession> Sessions => _sessions.Values;
|
||||
[ViewVariables]
|
||||
IEnumerable<IPlayerSession> IPlayerManager.Sessions => _sessions.Values;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyDictionary<NetUserId, IPlayerSession> SessionsDict => _sessions;
|
||||
@@ -191,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);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,53 @@
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Robust.Client.Player
|
||||
{
|
||||
|
||||
internal sealed class PlayerSession : IPlayerSession
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public SessionStatus Status { get; set; } = SessionStatus.Connecting;
|
||||
internal SessionStatus Status { get; set; } = SessionStatus.Connecting;
|
||||
|
||||
/// <inheritdoc />
|
||||
SessionStatus ICommonSession.Status
|
||||
{
|
||||
get => this.Status;
|
||||
set => this.Status = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEntity? AttachedEntity { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityUid? AttachedEntityUid => AttachedEntity?.Uid;
|
||||
|
||||
/// <inheritdoc />
|
||||
public NetUserId UserId { get; }
|
||||
|
||||
/// <inheritdoc cref="IPlayerSession" />
|
||||
public string Name { get; set; } = "<Unknown>";
|
||||
internal string Name { get; set; } = "<Unknown>";
|
||||
|
||||
/// <inheritdoc cref="IPlayerSession" />
|
||||
string ICommonSession.Name
|
||||
{
|
||||
get => this.Name;
|
||||
set => this.Name = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public short Ping { get; set; }
|
||||
internal short Ping { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public INetChannel ConnectedClient { get; internal set; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
short ICommonSession.Ping
|
||||
{
|
||||
get => this.Ping;
|
||||
set => this.Ping = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of a PlayerSession.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,13 +148,6 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
SearchBar.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
base.Close();
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
@@ -201,6 +194,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
private void OnEraseButtonToggled(BaseButton.ButtonToggledEventArgs args)
|
||||
{
|
||||
placementManager.ToggleEraser();
|
||||
OverrideMenu.Disabled = args.Pressed;
|
||||
}
|
||||
|
||||
private void BuildEntityList(string? searchStr = null)
|
||||
@@ -510,6 +504,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
}
|
||||
|
||||
EraseButton.Pressed = false;
|
||||
OverrideMenu.Disabled = false;
|
||||
}
|
||||
|
||||
private class DoNotMeasure : Control
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
63
Robust.Generators.UnitTesting/AnalyzerTest.cs
Normal file
63
Robust.Generators.UnitTesting/AnalyzerTest.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Robust.Generators.UnitTesting/DataClassTests.cs
Normal file
82
Robust.Generators.UnitTesting/DataClassTests.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
11
Robust.Generators/Robust.Generators.csproj
Normal file
11
Robust.Generators/Robust.Generators.csproj
Normal 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>
|
||||
@@ -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>();
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace Robust.Server.Console
|
||||
_systemConsole.Print(text + "\n");
|
||||
}
|
||||
|
||||
private static string FormatPlayerString(IBaseSession? session)
|
||||
private static string FormatPlayerString(ICommonSession? session)
|
||||
{
|
||||
return session != null ? $"{session.Name}" : "[HOST]";
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Server.Debugging
|
||||
public void Initialize()
|
||||
{
|
||||
_net.RegisterNetMessage<MsgRay>(MsgRay.NAME);
|
||||
_physics.DebugDrawRay += data => PhysicsOnDebugDrawRay(data);
|
||||
// TODO _physics.DebugDrawRay += data => PhysicsOnDebugDrawRay(data);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
[PublicAPI]
|
||||
public abstract class ContainerModifiedMessage : EntitySystemMessage
|
||||
{
|
||||
protected ContainerModifiedMessage(IEntity entity, IContainer container)
|
||||
{
|
||||
Entity = entity;
|
||||
Container = container;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The entity that was removed or inserted from/into the container.
|
||||
/// </summary>
|
||||
public IEntity Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The container being acted upon.
|
||||
/// </summary>
|
||||
public IContainer Container { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an entity is removed from a container.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class EntRemovedFromContainerMessage : ContainerModifiedMessage
|
||||
{
|
||||
public EntRemovedFromContainerMessage(IEntity entity, IContainer container) : base(entity, container)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when an entity is inserted into a container.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class EntInsertedIntoContainerMessage : ContainerModifiedMessage
|
||||
{
|
||||
public EntInsertedIntoContainerMessage(IEntity entity, IContainer container) : base(entity, container)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
@@ -198,8 +199,8 @@ namespace Robust.Server.GameObjects
|
||||
private Box2 GetEntityBox(IEntity entity)
|
||||
{
|
||||
// Need to clip the aabb as anything with an edge intersecting another tile might be picked up, such as walls.
|
||||
if (entity.TryGetComponent(out IPhysicsComponent? physics))
|
||||
return new Box2(physics.WorldAABB.BottomLeft + 0.01f, physics.WorldAABB.TopRight - 0.01f);
|
||||
if (entity.TryGetComponent(out IPhysBody? physics))
|
||||
return new Box2(physics.GetWorldAABB().BottomLeft + 0.01f, physics.GetWorldAABB().TopRight - 0.01f);
|
||||
|
||||
// Don't want to accidentally get neighboring tiles unless we're near an edge
|
||||
return Box2.CenteredAround(entity.Transform.Coordinates.ToMapPos(EntityManager), Vector2.One / 2);
|
||||
@@ -335,7 +336,7 @@ namespace Robust.Server.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = GetEntityBox(moveEvent.Sender);
|
||||
var bounds = moveEvent.WorldAABB ?? GetEntityBox(moveEvent.Sender);
|
||||
var newNodes = GetOrCreateNodes(moveEvent.NewPosition, bounds);
|
||||
|
||||
if (oldNodes.Count == newNodes.Count && oldNodes.SetEquals(newNodes))
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
public class ServerComponentFactory : ComponentFactory
|
||||
@@ -27,7 +30,13 @@ namespace Robust.Server.GameObjects
|
||||
RegisterReference<BasicActorComponent, IActorComponent>();
|
||||
|
||||
Register<PhysicsComponent>();
|
||||
RegisterReference<PhysicsComponent, IPhysicsComponent>();
|
||||
RegisterReference<PhysicsComponent, IPhysBody>();
|
||||
|
||||
Register<CollisionWakeComponent>();
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
RegisterReference<ContainerManagerComponent, IContainerManager>();
|
||||
|
||||
Register<OccluderComponent>();
|
||||
|
||||
RegisterIgnore("Input");
|
||||
@@ -35,9 +44,6 @@ namespace Robust.Server.GameObjects
|
||||
RegisterReference<SpriteComponent, SharedSpriteComponent>();
|
||||
RegisterReference<SpriteComponent, ISpriteRenderableComponent>();
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
RegisterReference<ContainerManagerComponent, IContainerManager>();
|
||||
|
||||
Register<AppearanceComponent>();
|
||||
RegisterReference<AppearanceComponent, SharedAppearanceComponent>();
|
||||
|
||||
@@ -52,7 +58,6 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
#if DEBUG
|
||||
Register<DebugExceptionOnAddComponent>();
|
||||
Register<DebugExceptionExposeDataComponent>();
|
||||
Register<DebugExceptionInitializeComponent>();
|
||||
Register<DebugExceptionStartupComponent>();
|
||||
#endif
|
||||
|
||||
@@ -6,9 +6,11 @@ using Prometheus;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -367,7 +369,7 @@ namespace Robust.Server.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entity.TryGetComponent(out IPhysicsComponent? body))
|
||||
if (entity.TryGetComponent(out IPhysBody? body))
|
||||
{
|
||||
if (body.LinearVelocity.EqualsApprox(Vector2.Zero, MinimumMotionForMovers))
|
||||
{
|
||||
@@ -534,7 +536,7 @@ namespace Robust.Server.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entity.TryGetComponent(out IPhysicsComponent? body))
|
||||
if (!entity.TryGetComponent(out IPhysBody? body))
|
||||
{
|
||||
// can't be a mover w/o physics
|
||||
continue;
|
||||
@@ -720,10 +722,10 @@ namespace Robust.Server.GameObjects
|
||||
_deletionHistory.RemoveAll(hist => hist.tick <= toTick);
|
||||
}
|
||||
|
||||
public override bool UpdateEntityTree(IEntity entity)
|
||||
public override bool UpdateEntityTree(IEntity entity, Box2? worldAABB = null)
|
||||
{
|
||||
var currentTick = CurrentTick;
|
||||
var updated = base.UpdateEntityTree(entity);
|
||||
var updated = base.UpdateEntityTree(entity, worldAABB);
|
||||
|
||||
if (entity.Deleted
|
||||
|| !entity.Initialized
|
||||
@@ -735,6 +737,7 @@ namespace Robust.Server.GameObjects
|
||||
DebugTools.Assert(entity.Transform.Initialized);
|
||||
|
||||
// note: updated can be false even if something moved a bit
|
||||
worldAABB ??= GetWorldAabbFromEntity(entity);
|
||||
|
||||
foreach (var (player, lastSeen) in _playerLastSeen)
|
||||
{
|
||||
@@ -781,14 +784,14 @@ namespace Robust.Server.GameObjects
|
||||
// saw it previously
|
||||
|
||||
// player can't see it now
|
||||
if (!viewbox.Intersects(GetWorldAabbFromEntity(entity)))
|
||||
if (!viewbox.Intersects(worldAABB.Value))
|
||||
{
|
||||
var addToMovers = false;
|
||||
if (entity.Transform.LastModifiedTick >= currentTick)
|
||||
{
|
||||
addToMovers = true;
|
||||
}
|
||||
else if (entity.TryGetComponent(out IPhysicsComponent? physics)
|
||||
else if (entity.TryGetComponent(out IPhysBody? physics)
|
||||
&& physics.LastModifiedTick >= currentTick)
|
||||
{
|
||||
addToMovers = true;
|
||||
|
||||
@@ -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>
|
||||
|
||||
14
Robust.Server/Physics/BroadPhaseSystem.cs
Normal file
14
Robust.Server/Physics/BroadPhaseSystem.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Physics.Broadphase;
|
||||
|
||||
namespace Robust.Server.Physics
|
||||
{
|
||||
internal sealed class BroadPhaseSystem : SharedBroadPhaseSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(PhysicsSystem));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using System.Collections.Generic;
|
||||
@@ -59,6 +59,9 @@ namespace Robust.Server.Placement
|
||||
case PlacementManagerMessage.RequestEntRemove:
|
||||
HandleEntRemoveReq(msg.EntityUid);
|
||||
break;
|
||||
case PlacementManagerMessage.RequestRectRemove:
|
||||
HandleRectRemoveReq(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,6 +205,19 @@ namespace Robust.Server.Placement
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
|
||||
private void HandleRectRemoveReq(MsgPlacement msg)
|
||||
{
|
||||
EntityCoordinates start = msg.EntityCoordinates;
|
||||
Vector2 rectSize = msg.RectSize;
|
||||
foreach (IEntity entity in _entityManager.GetEntitiesIntersecting(start.GetMapId(_entityManager),
|
||||
new Box2(start.Position, start.Position + rectSize)))
|
||||
{
|
||||
if (entity.Deleted || entity.HasComponent<IMapGridComponent>() || entity.HasComponent<IActorComponent>())
|
||||
continue;
|
||||
entity.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Places mob in entity placement mode with given settings.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -13,21 +13,10 @@ namespace Robust.Server.Player
|
||||
/// <summary>
|
||||
/// Manages each players session when connected to the server.
|
||||
/// </summary>
|
||||
public interface IPlayerManager
|
||||
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Number of players currently connected to this server.
|
||||
/// Fetching this is thread safe.
|
||||
/// </summary>
|
||||
int PlayerCount { get; }
|
||||
|
||||
BoundKeyMap KeyMap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of players that can connect to this server at one time.
|
||||
/// </summary>
|
||||
int MaxPlayers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the <see cref="SessionStatus" /> of a <see cref="IPlayerSession" /> is changed.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
@@ -7,8 +7,6 @@ namespace Robust.Server.Player
|
||||
{
|
||||
public interface IPlayerSession : ICommonSession
|
||||
{
|
||||
EntityUid? AttachedEntityUid { get; }
|
||||
INetChannel ConnectedClient { get; }
|
||||
DateTime ConnectedTime { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,6 +14,7 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -53,6 +54,11 @@ namespace Robust.Server.Player
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<string, NetUserId> _userIdMap = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ICommonSession> NetworkedSessions => _sessions.Values;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ICommonSession> Sessions => _sessions.Values;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Server.GameObjects;
|
||||
using System;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Server.Player
|
||||
@@ -42,11 +43,32 @@ namespace Robust.Server.Player
|
||||
private SessionStatus _status = SessionStatus.Connecting;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name { get; }
|
||||
|
||||
[ViewVariables]
|
||||
internal string Name { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
string ICommonSession.Name
|
||||
{
|
||||
get => this.Name;
|
||||
set => this.Name = value;
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public SessionStatus Status
|
||||
internal short Ping
|
||||
{
|
||||
get => ConnectedClient.Ping;
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
short ICommonSession.Ping
|
||||
{
|
||||
get => this.Ping;
|
||||
set => this.Ping = value;
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
internal SessionStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set
|
||||
@@ -62,6 +84,13 @@ namespace Robust.Server.Player
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
SessionStatus ICommonSession.Status
|
||||
{
|
||||
get => this.Status;
|
||||
set => this.Status = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTime ConnectedTime { get; private set; }
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -43,6 +43,11 @@ namespace Robust.Shared.Maths
|
||||
|
||||
public static readonly Vector2 Infinity = new(float.PositiveInfinity, float.PositiveInfinity);
|
||||
|
||||
/// <summary>
|
||||
/// A vector with NaN X and Y.
|
||||
/// </summary>
|
||||
public static readonly Vector2 NaN = new(float.NaN, float.NaN);
|
||||
|
||||
/// <summary>
|
||||
/// Construct a vector from its coordinates.
|
||||
/// </summary>
|
||||
@@ -90,7 +95,7 @@ namespace Robust.Shared.Maths
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Vector2 Rounded()
|
||||
{
|
||||
return new((float) MathF.Round(X), (float) MathF.Round(Y));
|
||||
return new(MathF.Round(X), MathF.Round(Y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
48
Robust.Shared/Audio/IAudioSystem.cs
Normal file
48
Robust.Shared/Audio/IAudioSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
7
Robust.Shared/Audio/IPlayingAudioStream.cs
Normal file
7
Robust.Shared/Audio/IPlayingAudioStream.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Robust.Shared.Audio
|
||||
{
|
||||
public interface IPlayingAudioStream
|
||||
{
|
||||
void Stop();
|
||||
}
|
||||
}
|
||||
82
Robust.Shared/Audio/SoundSystem.cs
Normal file
82
Robust.Shared/Audio/SoundSystem.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,6 +280,105 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<string> PlayerName =
|
||||
CVarDef.Create("player.name", "JoeGenero", CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* PHYSICS
|
||||
*/
|
||||
|
||||
// - Sleep
|
||||
public static readonly CVarDef<float> AngularSleepTolerance =
|
||||
CVarDef.Create("physics.angsleeptol", 2.0f / 180.0f * MathF.PI);
|
||||
|
||||
public static readonly CVarDef<float> LinearSleepTolerance =
|
||||
CVarDef.Create("physics.linsleeptol", 0.001f);
|
||||
|
||||
public static readonly CVarDef<bool> SleepAllowed =
|
||||
CVarDef.Create("physics.sleepallowed", true);
|
||||
|
||||
// Box2D default is 0.5f
|
||||
public static readonly CVarDef<float> TimeToSleep =
|
||||
CVarDef.Create("physics.timetosleep", 0.2f);
|
||||
|
||||
// - Solver
|
||||
// These are the minimum recommended by Box2D with the standard being 8 velocity 3 position iterations.
|
||||
// Trade-off is obviously performance vs how long it takes to stabilise.
|
||||
public static readonly CVarDef<int> PositionIterations =
|
||||
CVarDef.Create("physics.positer", 3);
|
||||
|
||||
public static readonly CVarDef<int> VelocityIterations =
|
||||
CVarDef.Create("physics.veliter", 8);
|
||||
|
||||
public static readonly CVarDef<bool> WarmStarting =
|
||||
CVarDef.Create("physics.warmstart", true);
|
||||
|
||||
public static readonly CVarDef<bool> AutoClearForces =
|
||||
CVarDef.Create("physics.autoclearforces", true);
|
||||
|
||||
/// <summary>
|
||||
/// A velocity threshold for elastic collisions. Any collision with a relative linear
|
||||
/// velocity below this threshold will be treated as inelastic.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> VelocityThreshold =
|
||||
CVarDef.Create("physics.velocitythreshold", 0.5f);
|
||||
|
||||
// TODO: Copy Box2D's comments on baumgarte I think it's on the solver class.
|
||||
/// <summary>
|
||||
/// How much overlap is resolved per tick.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> Baumgarte =
|
||||
CVarDef.Create("physics.baumgarte", 0.2f);
|
||||
|
||||
/// <summary>
|
||||
/// A small length used as a collision and constraint tolerance. Usually it is
|
||||
/// chosen to be numerically significant, but visually insignificant.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> LinearSlop =
|
||||
CVarDef.Create("physics.linearslop", 0.005f);
|
||||
|
||||
/// <summary>
|
||||
/// A small angle used as a collision and constraint tolerance. Usually it is
|
||||
/// chosen to be numerically significant, but visually insignificant.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> AngularSlop =
|
||||
CVarDef.Create("physics.angularslop", 2.0f / 180.0f * MathF.PI);
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the polygon/edge shape skin. This should not be modified. Making
|
||||
/// this smaller means polygons will have an insufficient buffer for continuous collision.
|
||||
/// Making it larger may create artifacts for vertex collision.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Default is set to be 2 x linearslop. TODO Should we listen to linearslop changes?
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> PolygonRadius =
|
||||
CVarDef.Create("physics.polygonradius", 2 * 0.005f);
|
||||
|
||||
/// <summary>
|
||||
/// If true, it will run a GiftWrap convex hull on all polygon inputs.
|
||||
/// This makes for a more stable engine when given random input,
|
||||
/// but if speed of the creation of polygons are more important,
|
||||
/// you might want to set this to false.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> ConvexHullPolygons =
|
||||
CVarDef.Create("physics.convexhullpolygons", true);
|
||||
|
||||
public static readonly CVarDef<int> MaxPolygonVertices =
|
||||
CVarDef.Create("physics.maxpolygonvertices", 8);
|
||||
|
||||
public static readonly CVarDef<float> MaxLinearCorrection =
|
||||
CVarDef.Create("physics.maxlinearcorrection", 0.2f);
|
||||
|
||||
public static readonly CVarDef<float> MaxAngularCorrection =
|
||||
CVarDef.Create("physics.maxangularcorrection", 8.0f / 180.0f * MathF.PI);
|
||||
|
||||
// - Maximums
|
||||
// Squared
|
||||
public static readonly CVarDef<float> MaxLinVelocity =
|
||||
CVarDef.Create("physics.maxlinvelocity", 4.0f);
|
||||
|
||||
// Squared
|
||||
public static readonly CVarDef<float> MaxAngVelocity =
|
||||
CVarDef.Create("physics.maxangvelocity", 0.5f * MathF.PI);
|
||||
|
||||
/*
|
||||
* DISCORD
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Robust.Shared/Containers/Container.cs
Normal file
64
Robust.Shared/Containers/Container.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Robust.Shared/Containers/ContainerContentsModifiedMessage.cs
Normal file
38
Robust.Shared/Containers/ContainerContentsModifiedMessage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
402
Robust.Shared/Containers/ContainerManagerComponent.cs
Normal file
402
Robust.Shared/Containers/ContainerManagerComponent.cs
Normal 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() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Robust.Shared/Containers/ContainerModifiedMessage.cs
Normal file
28
Robust.Shared/Containers/ContainerModifiedMessage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Robust.Shared/Containers/ContainerSlot.cs
Normal file
73
Robust.Shared/Containers/ContainerSlot.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Robust.Shared/Containers/EntInsertedIntoContainerMessage.cs
Normal file
14
Robust.Shared/Containers/EntInsertedIntoContainerMessage.cs
Normal 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) { }
|
||||
}
|
||||
}
|
||||
14
Robust.Shared/Containers/EntRemovedFromContainerMessage.cs
Normal file
14
Robust.Shared/Containers/EntRemovedFromContainerMessage.cs
Normal 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) { }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
14
Robust.Shared/Containers/UpdateContainerOcclusionMessage.cs
Normal file
14
Robust.Shared/Containers/UpdateContainerOcclusionMessage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user