mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49327279d0 | ||
|
|
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 | ||
|
|
91391e1205 | ||
|
|
d5199ec459 | ||
|
|
e1e6f4fd10 | ||
|
|
e5b6fccf67 | ||
|
|
95a912c329 | ||
|
|
2b4833fc4e | ||
|
|
b814fc851a | ||
|
|
e87863203b | ||
|
|
33b66d9e18 | ||
|
|
fd406f7897 | ||
|
|
7a836d1018 | ||
|
|
393c15c44a | ||
|
|
a7f31f9ebf | ||
|
|
b332644d48 | ||
|
|
510f7c0e7c | ||
|
|
fdd05e3d3a | ||
|
|
6d41958853 | ||
|
|
cecc4dfcf2 | ||
|
|
4ac40f2e90 | ||
|
|
3e12d44173 | ||
|
|
a42b39bd84 | ||
|
|
22affccf24 | ||
|
|
028724c47b | ||
|
|
0114bff2fc | ||
|
|
4ddbd644eb | ||
|
|
f0366531ef | ||
|
|
6bd5814f4a | ||
|
|
78c132fdab | ||
|
|
460cf57d7c | ||
|
|
a3190a8aca | ||
|
|
6921fb2fbf | ||
|
|
9954d571de | ||
|
|
17fe000a1e | ||
|
|
fba415e765 | ||
|
|
583b7ebf38 | ||
|
|
771a256925 | ||
|
|
ae79e89347 | ||
|
|
6c7eeb95eb | ||
|
|
de0bd1887f | ||
|
|
eb3a815d48 | ||
|
|
e2a4dcdff1 | ||
|
|
68b0d7bf2e | ||
|
|
a9b163992b | ||
|
|
2409965cf8 | ||
|
|
eada37378a | ||
|
|
0f1da1ba2a | ||
|
|
e0cdcd228e | ||
|
|
fdb5e014b5 | ||
|
|
cefcad775b | ||
|
|
e40feac1f1 | ||
|
|
3ef4ac7452 | ||
|
|
93bf1b09e7 | ||
|
|
a1e557e870 | ||
|
|
864adb7445 | ||
|
|
9e3f3f0c1c |
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
|
||||
Submodule NetSerializer updated: 1e103e8e29...2d4f8b5611
@@ -574,29 +574,22 @@
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
- name: NGetText
|
||||
- name: Fluent.Net
|
||||
license: |
|
||||
The MIT License (MIT)
|
||||
blushingpenguin and Contributors
|
||||
|
||||
Copyright (c) 2012 Vitaly Zilnik
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
|
||||
- name: NetSerializer
|
||||
license: |
|
||||
|
||||
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
|
||||
40
Resources/Prototypes/dbg_rotationMarker.yml
Normal file
40
Resources/Prototypes/dbg_rotationMarker.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
- type: entity
|
||||
id: debugRotation1
|
||||
name: dbg_rotation1
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
visible: true
|
||||
sprite: debugRotation.rsi
|
||||
state: direction1
|
||||
placement:
|
||||
mode: AlignTileAny
|
||||
|
||||
- type: entity
|
||||
id: debugRotation4
|
||||
name: dbg_rotation4
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
visible: true
|
||||
sprite: debugRotation.rsi
|
||||
state: direction4
|
||||
placement:
|
||||
mode: AlignTileAny
|
||||
|
||||
- type: entity
|
||||
id: debugRotationTex
|
||||
name: dbg_rotationTex
|
||||
components:
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Sprite
|
||||
netsync: false
|
||||
visible: true
|
||||
texture: debugRotation.rsi/direction1.png
|
||||
placement:
|
||||
mode: AlignTileAny
|
||||
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
|
||||
BIN
Resources/Textures/debugRotation.rsi/direction1.png
Normal file
BIN
Resources/Textures/debugRotation.rsi/direction1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 359 B |
BIN
Resources/Textures/debugRotation.rsi/direction1.xcf
Normal file
BIN
Resources/Textures/debugRotation.rsi/direction1.xcf
Normal file
Binary file not shown.
BIN
Resources/Textures/debugRotation.rsi/direction4.png
Normal file
BIN
Resources/Textures/debugRotation.rsi/direction4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Resources/Textures/debugRotation.rsi/direction4.xcf
Normal file
BIN
Resources/Textures/debugRotation.rsi/direction4.xcf
Normal file
Binary file not shown.
17
Resources/Textures/debugRotation.rsi/meta.json
Normal file
17
Resources/Textures/debugRotation.rsi/meta.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version": 1,
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [{
|
||||
"name": "direction1",
|
||||
"directions": 1,
|
||||
"delays": [[1.0]]
|
||||
}, {
|
||||
"name": "direction4",
|
||||
"directions": 4,
|
||||
"delays": [[1.0], [1.0], [1.0], [1.0]]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
Resources/Textures/debugRotation.rsi/uvmap.xcf
Normal file
BIN
Resources/Textures/debugRotation.rsi/uvmap.xcf
Normal file
Binary file not shown.
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);
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,9 @@ namespace Robust.Build.Tasks
|
||||
public static Parser<char, float[]> Thickness { get; }
|
||||
= SkipWhitespaces.Then(
|
||||
OneOf(
|
||||
Try(Single1.Select(c => new[] {c})),
|
||||
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4})),
|
||||
Try(Single2.Select(c => new[] {c.Item1, c.Item2})),
|
||||
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4}))
|
||||
Try(Single1.Select(c => new[] {c}))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
32
Robust.Client.Injectors/RXamlColorAstNode.cs
Normal file
32
Robust.Client.Injectors/RXamlColorAstNode.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Reflection.Emit;
|
||||
using XamlX.Ast;
|
||||
using XamlX.Emit;
|
||||
using XamlX.IL;
|
||||
using XamlX.TypeSystem;
|
||||
|
||||
namespace Robust.Build.Tasks
|
||||
{
|
||||
internal class RXamlColorAstNode
|
||||
: XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
|
||||
{
|
||||
private readonly IXamlMethod _method;
|
||||
private readonly string _color;
|
||||
|
||||
public RXamlColorAstNode(IXamlLineInfo lineInfo, RXamlWellKnownTypes types, string color) : base(lineInfo)
|
||||
{
|
||||
_color = color;
|
||||
Type = new XamlAstClrTypeReference(lineInfo, types.Color, false);
|
||||
_method = types.ColorFromXaml;
|
||||
}
|
||||
|
||||
public IXamlAstTypeReference Type { get; }
|
||||
|
||||
public XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlILEmitter codeGen)
|
||||
{
|
||||
codeGen.Ldstr(_color);
|
||||
codeGen.EmitCall(_method);
|
||||
|
||||
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,10 @@ namespace Robust.Build.Tasks
|
||||
public IXamlConstructor Vector2ConstructorFull { get; }
|
||||
public IXamlType Vector2i { get; }
|
||||
public IXamlConstructor Vector2iConstructorFull { get; }
|
||||
public IXamlType Thickness { get; }
|
||||
public IXamlConstructor ThicknessConstructorFull { get; }
|
||||
public IXamlType Color { get; }
|
||||
public IXamlMethod ColorFromXaml { get; }
|
||||
|
||||
public RXamlWellKnownTypes(TransformerConfiguration cfg)
|
||||
{
|
||||
@@ -23,6 +27,7 @@ namespace Robust.Build.Tasks
|
||||
|
||||
(Vector2, Vector2ConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2", Single, 2);
|
||||
(Vector2i, Vector2iConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2i", Int32, 2);
|
||||
(Thickness, ThicknessConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Thickness", Single, 4);
|
||||
|
||||
(IXamlType, IXamlConstructor) GetNumericTypeInfo(string name, IXamlType componentType, int componentCount)
|
||||
{
|
||||
@@ -31,6 +36,12 @@ namespace Robust.Build.Tasks
|
||||
|
||||
return (type, ctor);
|
||||
}
|
||||
|
||||
Color = cfg.TypeSystem.GetType("Robust.Shared.Maths.Color");
|
||||
ColorFromXaml = Color.GetMethod(new FindMethodMethodSignature("FromXaml", Color, XamlIlTypes.String)
|
||||
{
|
||||
IsStatic = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -329,6 +329,77 @@ namespace Robust.Build.Tasks
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Equals(types.Thickness))
|
||||
{
|
||||
var foo = MathParsing.Thickness.Parse(text);
|
||||
|
||||
if (!foo.Success)
|
||||
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
|
||||
|
||||
var val = foo.Value;
|
||||
float[] full;
|
||||
if (val.Length == 1)
|
||||
{
|
||||
var u = val[0];
|
||||
full = new[] {u, u, u, u};
|
||||
}
|
||||
else if (val.Length == 2)
|
||||
{
|
||||
var h = val[0];
|
||||
var v = val[1];
|
||||
full = new[] {h, v, h, v};
|
||||
}
|
||||
else // 4
|
||||
{
|
||||
full = val;
|
||||
}
|
||||
|
||||
result = new RXamlSingleVecLikeConstAstNode(
|
||||
node,
|
||||
types.Thickness, types.ThicknessConstructorFull,
|
||||
types.Single, full);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Equals(types.Thickness))
|
||||
{
|
||||
var foo = MathParsing.Thickness.Parse(text);
|
||||
|
||||
if (!foo.Success)
|
||||
throw new XamlLoadException($"Unable to parse \"{text}\" as a Thickness", node);
|
||||
|
||||
var val = foo.Value;
|
||||
float[] full;
|
||||
if (val.Length == 1)
|
||||
{
|
||||
var u = val[0];
|
||||
full = new[] {u, u, u, u};
|
||||
}
|
||||
else if (val.Length == 2)
|
||||
{
|
||||
var h = val[0];
|
||||
var v = val[1];
|
||||
full = new[] {h, v, h, v};
|
||||
}
|
||||
else // 4
|
||||
{
|
||||
full = val;
|
||||
}
|
||||
|
||||
result = new RXamlSingleVecLikeConstAstNode(
|
||||
node,
|
||||
types.Thickness, types.ThicknessConstructorFull,
|
||||
types.Single, full);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.Equals(types.Color))
|
||||
{
|
||||
// TODO: Interpret these colors at XAML compile time instead of at runtime.
|
||||
result = new RXamlColorAstNode(node, types, text);
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Client.Audio.Midi;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Debugging;
|
||||
@@ -10,6 +10,7 @@ using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.Prototypes;
|
||||
using Robust.Client.Reflection;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
@@ -17,13 +18,17 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Client.ViewVariables;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
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
|
||||
{
|
||||
@@ -33,7 +38,7 @@ namespace Robust.Client
|
||||
{
|
||||
SharedIoC.RegisterIoC();
|
||||
|
||||
IoCManager.Register<IPrototypeManager, PrototypeManager>();
|
||||
IoCManager.Register<IPrototypeManager, ClientPrototypeManager>();
|
||||
IoCManager.Register<IEntityManager, ClientEntityManager>();
|
||||
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
|
||||
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
|
||||
@@ -51,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>();
|
||||
@@ -59,6 +65,7 @@ namespace Robust.Client
|
||||
IoCManager.Register<ILightManager, LightManager>();
|
||||
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
|
||||
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
|
||||
IoCManager.Register<IFontManager, FontManager>();
|
||||
IoCManager.Register<IFontManagerInternal, FontManager>();
|
||||
IoCManager.Register<IMidiManager, MidiManager>();
|
||||
@@ -96,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;
|
||||
@@ -481,6 +480,8 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
_writeNode(root, 0, writer);
|
||||
}
|
||||
|
||||
shell.WriteLine("Saved guidump");
|
||||
}
|
||||
|
||||
private static void _writeNode(Control control, int indents, TextWriter writer)
|
||||
@@ -542,7 +543,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var window = new SS14Window { CustomMinimumSize = (500, 400)};
|
||||
var window = new SS14Window { MinSize = (500, 400)};
|
||||
var tabContainer = new TabContainer();
|
||||
window.Contents.AddChild(tabContainer);
|
||||
var scroll = new ScrollContainer();
|
||||
@@ -562,7 +563,7 @@ namespace Robust.Client.Console.Commands
|
||||
optionButton.OnItemSelected += eventArgs => optionButton.SelectId(eventArgs.Id);
|
||||
vBox.AddChild(optionButton);
|
||||
|
||||
var tree = new Tree { SizeFlagsVertical = Control.SizeFlags.FillExpand };
|
||||
var tree = new Tree { VerticalExpand = true };
|
||||
var root = tree.CreateItem();
|
||||
root.Text = "Honk!";
|
||||
var child = tree.CreateItem();
|
||||
@@ -599,7 +600,7 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
grid.AddChild(new Button
|
||||
{
|
||||
CustomMinimumSize = (50, 50),
|
||||
MinSize = (50, 50),
|
||||
Text = $"{x}, {y}"
|
||||
});
|
||||
}
|
||||
@@ -631,6 +632,29 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
});
|
||||
|
||||
tabContainer.AddChild(new HSplitContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Red},
|
||||
Children =
|
||||
{
|
||||
new Label{ Text = "FOOBARBAZ"},
|
||||
}
|
||||
},
|
||||
new PanelContainer
|
||||
{
|
||||
PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Blue},
|
||||
Children =
|
||||
{
|
||||
new Label{ Text = "FOOBARBAZ"},
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
window.OpenCentered();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Robust.Client/Console/Commands/ReloadLocalizationsCommand.cs
Normal file
20
Robust.Client/Console/Commands/ReloadLocalizationsCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
internal sealed class ReloadLocalizationsCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "rldloc";
|
||||
public string Description => "Reloads localization (client & server)";
|
||||
public string Help => "Usage: rldloc";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
IoCManager.Resolve<ILocalizationManager>().ReloadLocalizations();
|
||||
|
||||
shell.RemoteExecuteCommand("sudo rldloc");
|
||||
}
|
||||
}
|
||||
}
|
||||
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,5 +1,8 @@
|
||||
#if CLIENT_SCRIPTING
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.CodeAnalysis;
|
||||
@@ -36,6 +39,8 @@ namespace Robust.Client.Console
|
||||
private readonly ScriptGlobals _globals;
|
||||
private ScriptState? _state;
|
||||
|
||||
private (string[] imports, string code)? _autoImportRepeatBuffer;
|
||||
|
||||
public ScriptConsoleClient()
|
||||
{
|
||||
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
|
||||
@@ -54,38 +59,56 @@ namespace Robust.Client.Console
|
||||
var code = InputBar.Text;
|
||||
InputBar.Clear();
|
||||
|
||||
// Remove > or . at the end of the output panel.
|
||||
OutputPanel.RemoveEntry(^1);
|
||||
|
||||
_inputBuffer.AppendLine(code);
|
||||
_linesEntered += 1;
|
||||
|
||||
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(_inputBuffer.ToString()), ScriptInstanceShared.ParseOptions);
|
||||
|
||||
if (!SyntaxFactory.IsCompleteSubmission(tree))
|
||||
if (_autoImportRepeatBuffer.HasValue && code == "y")
|
||||
{
|
||||
if (_linesEntered == 1)
|
||||
var (imports, repeatCode) = _autoImportRepeatBuffer.Value;
|
||||
var sb = new StringBuilder();
|
||||
foreach (var import in imports)
|
||||
{
|
||||
OutputPanel.AddText($"> {code}");
|
||||
sb.AppendFormat("using {0};\n", import);
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputPanel.AddText($". {code}");
|
||||
}
|
||||
OutputPanel.AddText(".");
|
||||
return;
|
||||
|
||||
sb.Append(repeatCode);
|
||||
|
||||
code = sb.ToString();
|
||||
}
|
||||
|
||||
code = _inputBuffer.ToString().Trim();
|
||||
|
||||
// Remove echo of partial submission from the output panel.
|
||||
for (var i = 1; i < _linesEntered; i++)
|
||||
else
|
||||
{
|
||||
// Remove > or . at the end of the output panel.
|
||||
OutputPanel.RemoveEntry(^1);
|
||||
}
|
||||
|
||||
_inputBuffer.Clear();
|
||||
_linesEntered = 0;
|
||||
_inputBuffer.AppendLine(code);
|
||||
_linesEntered += 1;
|
||||
|
||||
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(_inputBuffer.ToString()),
|
||||
ScriptInstanceShared.ParseOptions);
|
||||
|
||||
if (!SyntaxFactory.IsCompleteSubmission(tree))
|
||||
{
|
||||
if (_linesEntered == 1)
|
||||
{
|
||||
OutputPanel.AddText($"> {code}");
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputPanel.AddText($". {code}");
|
||||
}
|
||||
|
||||
OutputPanel.AddText(".");
|
||||
return;
|
||||
}
|
||||
|
||||
code = _inputBuffer.ToString().Trim();
|
||||
|
||||
// Remove echo of partial submission from the output panel.
|
||||
for (var i = 1; i < _linesEntered; i++)
|
||||
{
|
||||
OutputPanel.RemoveEntry(^1);
|
||||
}
|
||||
|
||||
_inputBuffer.Clear();
|
||||
_linesEntered = 0;
|
||||
}
|
||||
|
||||
Script newScript;
|
||||
|
||||
@@ -135,6 +158,8 @@ namespace Robust.Client.Console
|
||||
|
||||
OutputPanel.AddMessage(msg);
|
||||
OutputPanel.AddText(">");
|
||||
|
||||
PromptAutoImports(e.Diagnostics, code);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -148,13 +173,23 @@ 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);
|
||||
}
|
||||
|
||||
OutputPanel.AddText(">");
|
||||
}
|
||||
|
||||
private void PromptAutoImports(IEnumerable<Diagnostic> diags, string code)
|
||||
{
|
||||
if (!ScriptInstanceShared.CalcAutoImports(_reflectionManager, diags, out var found))
|
||||
return;
|
||||
|
||||
OutputPanel.AddText($"Auto-import {string.Join(", ", found)} (enter 'y')?");
|
||||
|
||||
_autoImportRepeatBuffer = (found.ToArray(), code);
|
||||
}
|
||||
|
||||
private sealed class ScriptGlobalsImpl : ScriptGlobals
|
||||
{
|
||||
private readonly ScriptConsoleClient _owner;
|
||||
@@ -180,7 +215,7 @@ namespace Robust.Client.Console
|
||||
|
||||
public override void show(object obj)
|
||||
{
|
||||
write(CSharpObjectFormatter.Instance.FormatObject(obj));
|
||||
write(ScriptInstanceShared.SafeFormat(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ namespace Robust.Client.Console
|
||||
{
|
||||
private readonly IReflectionManager _reflectionManager;
|
||||
|
||||
protected override Vector2? CustomSize => (300, 300);
|
||||
|
||||
private readonly VBoxContainer _watchesVBox;
|
||||
private readonly LineEdit _addWatchEdit;
|
||||
private readonly Button _addWatchButton;
|
||||
@@ -37,12 +35,12 @@ namespace Robust.Client.Console
|
||||
|
||||
var mainVBox = new VBoxContainer
|
||||
{
|
||||
CustomMinimumSize = (500, 300),
|
||||
MinSize = (500, 300),
|
||||
Children =
|
||||
{
|
||||
(_watchesVBox = new VBoxContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.FillExpand
|
||||
VerticalExpand = true
|
||||
}),
|
||||
new HBoxContainer
|
||||
{
|
||||
@@ -50,7 +48,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
(_addWatchEdit = new HistoryLineEdit
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
HorizontalExpand = true,
|
||||
PlaceHolder = Loc.GetString("Add watch (C# interactive)")
|
||||
}),
|
||||
(_addWatchButton = new Button
|
||||
@@ -66,6 +64,8 @@ namespace Robust.Client.Console
|
||||
_addWatchEdit.OnTextEntered += _ => AddWatch();
|
||||
|
||||
Contents.AddChild(mainVBox);
|
||||
|
||||
SetSize = (300, 300);
|
||||
}
|
||||
|
||||
private void AddWatch()
|
||||
@@ -113,7 +113,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
(_outputLabel = new Label
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
HorizontalExpand = true,
|
||||
ClipText = true
|
||||
}),
|
||||
(delButton = new Button
|
||||
@@ -176,7 +176,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
Text = message,
|
||||
ClipText = true,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
HorizontalExpand = true
|
||||
},
|
||||
(delButton = new Button {Text = Loc.GetString("Remove")})
|
||||
}
|
||||
|
||||
@@ -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,7 +27,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timers;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -44,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!;
|
||||
@@ -62,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;
|
||||
@@ -153,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);
|
||||
@@ -162,7 +166,8 @@ namespace Robust.Client
|
||||
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
|
||||
_serializer.Initialize();
|
||||
_inputManager.Initialize();
|
||||
_consoleHost.Initialize();
|
||||
_console.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
|
||||
_prototypeManager.Resync();
|
||||
_mapManager.Initialize();
|
||||
@@ -318,6 +323,7 @@ namespace Robust.Client
|
||||
logManager.GetSawmill("discord").Level = LogLevel.Warning;
|
||||
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
|
||||
logManager.GetSawmill("szr").Level = LogLevel.Info;
|
||||
logManager.GetSawmill("loc").Level = LogLevel.Error;
|
||||
|
||||
#if DEBUG_ONLY_FCE_INFO
|
||||
#if DEBUG_ONLY_FCE_LOG
|
||||
@@ -376,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,4 +1,5 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Physics;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -23,9 +24,13 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
Register<PhysicsComponent>();
|
||||
RegisterReference<PhysicsComponent, IPhysBody>();
|
||||
RegisterReference<PhysicsComponent, IPhysicsComponent>();
|
||||
|
||||
Register<CollisionWakeComponent>();
|
||||
|
||||
Register<ContainerManagerComponent>();
|
||||
RegisterReference<ContainerManagerComponent, IContainerManager>();
|
||||
|
||||
RegisterIgnore("KeyBindingInput");
|
||||
Register<PointLightComponent>();
|
||||
|
||||
Register<InputComponent>();
|
||||
|
||||
@@ -50,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.DataField(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,11 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedIgnorePauseComponent))]
|
||||
public sealed class IgnorePauseComponent : SharedIgnorePauseComponent
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,17 @@ 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
|
||||
{
|
||||
public class PointLightComponent : Component
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IPointLightComponent))]
|
||||
public class PointLightComponent : Component, IPointLightComponent, ISerializationHooks
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
public override string Name => "PointLight";
|
||||
public override uint? NetID => NetIDs.POINT_LIGHT;
|
||||
|
||||
@@ -65,6 +70,21 @@ namespace Robust.Client.GameObjects
|
||||
set => _rotation = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The resource path to the mask texture the light will use.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string? MaskPath
|
||||
{
|
||||
get => _maskPath;
|
||||
set
|
||||
{
|
||||
_maskPath = value;
|
||||
UpdateMask();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a mask texture that will be applied to the light while rendering.
|
||||
/// The mask's red channel will be linearly multiplied.p
|
||||
@@ -115,16 +135,26 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private float _radius = 5;
|
||||
[DataField("radius")]
|
||||
private float _radius = 5f;
|
||||
[DataField("nestedvisible")]
|
||||
private bool _visibleNested = true;
|
||||
private bool _lightOnParent = false;
|
||||
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>
|
||||
/// Radius, in meters.
|
||||
@@ -141,12 +171,34 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMask()
|
||||
{
|
||||
if (_maskPath is not null)
|
||||
Mask = _resourceCache.GetResource<TextureResource>(_maskPath);
|
||||
else
|
||||
Mask = null;
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
if (_maskPath != null)
|
||||
{
|
||||
Mask = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(_maskPath);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
UpdateMask();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleMessage(ComponentMessage message, IComponent? component)
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
if ((message is ParentChangedMessage msg))
|
||||
if (message is ParentChangedMessage msg)
|
||||
{
|
||||
HandleTransformParentChanged(msg);
|
||||
}
|
||||
@@ -170,23 +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);
|
||||
|
||||
if (serializer.Reading && serializer.TryReadDataField<string>("mask", out var value))
|
||||
{
|
||||
Mask = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(value);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Animations;
|
||||
@@ -49,8 +49,26 @@ namespace Robust.Client.GameObjects
|
||||
/// Rotation transformations on individual layers still apply.
|
||||
/// If false, all layers get locked to south and rotation is a transformation.
|
||||
/// </summary>
|
||||
[Obsolete("Use NoRotation and/or DirectionOverride")]
|
||||
bool Directional { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All sprite rotation is locked, and will always be drawn upright on
|
||||
/// the screen, regardless of world or view orientation.
|
||||
/// </summary>
|
||||
bool NoRotation {get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables overriding the calculated directional RSI state for this sprite.
|
||||
/// The state to use is defined in <see cref="DirectionOverride"/>.
|
||||
/// </summary>
|
||||
bool EnableDirectionOverride { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The directional RSI state that will always be displayed, regardless of orientation.
|
||||
/// </summary>
|
||||
Direction DirectionOverride { get; set; }
|
||||
|
||||
// NOTE: The below are ALL designed to NOT throw exceptions ever,
|
||||
// instead making a bunch of noisy error logs.
|
||||
|
||||
@@ -64,6 +82,8 @@ namespace Robust.Client.GameObjects
|
||||
uint RenderOrder { get; set; }
|
||||
bool IsInert { get; }
|
||||
|
||||
Matrix3 GetLocalMatrix();
|
||||
|
||||
/// <summary>
|
||||
/// Sets a layer key to the layer map, creating it if it does not exist.
|
||||
/// </summary>
|
||||
@@ -202,5 +222,12 @@ namespace Robust.Client.GameObjects
|
||||
ISpriteLayer this[object layerKey] { get; }
|
||||
|
||||
IEnumerable<ISpriteLayer> AllLayers { get; }
|
||||
|
||||
int GetLayerDirectionCount(ISpriteLayer layer);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate sprite bounding box in world-space coordinates.
|
||||
/// </summary>
|
||||
Box2 CalculateBoundingBox();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +26,17 @@ namespace Robust.Client.GameObjects
|
||||
RSI.State.Direction EffectiveDirection(Angle worldRotation);
|
||||
|
||||
Vector2 LocalToLayer(Vector2 localPos);
|
||||
|
||||
/// <summary>
|
||||
/// Layer size in pixels.
|
||||
/// Don't account layer scale or sprite world transform.
|
||||
/// </summary>
|
||||
Vector2i PixelSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Calculate layer bounding box in sprite local-space coordinates.
|
||||
/// </summary>
|
||||
/// <returns>Bounding box in sprite local-space coordinates.</returns>
|
||||
Box2 CalculateBoundingBox();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
@@ -101,24 +112,159 @@ namespace Robust.Client.GameObjects
|
||||
/// If false, all layers get locked to south and rotation is a transformation.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
[Obsolete("Use NoRotation and/or DirectionOverride")]
|
||||
public bool Directional
|
||||
{
|
||||
get => _directional;
|
||||
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;
|
||||
}
|
||||
@@ -146,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; }
|
||||
|
||||
@@ -157,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; }
|
||||
|
||||
@@ -169,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";
|
||||
@@ -180,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.
|
||||
@@ -197,6 +384,9 @@ namespace Robust.Client.GameObjects
|
||||
rotation = other.rotation;
|
||||
scale = other.scale;
|
||||
drawDepth = other.drawDepth;
|
||||
_screenLock = other._screenLock;
|
||||
_overrideDirection = other._overrideDirection;
|
||||
_enableOverrideDirection = other._enableOverrideDirection;
|
||||
Layers = new List<Layer>(other.Layers.Count);
|
||||
foreach (var otherLayer in other.Layers)
|
||||
{
|
||||
@@ -218,6 +408,11 @@ namespace Robust.Client.GameObjects
|
||||
RenderOrder = other.RenderOrder;
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrix()
|
||||
{
|
||||
return Matrix3.CreateTransform(in offset, in rotation, in scale);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LayerMapSet(object key, int layer)
|
||||
{
|
||||
@@ -936,6 +1131,31 @@ namespace Robust.Client.GameObjects
|
||||
LayerSetAutoAnimated(layer, autoAnimated);
|
||||
}
|
||||
|
||||
public void LayerSetOffset(int layer, Vector2 layerOffset)
|
||||
{
|
||||
if (Layers.Count <= layer)
|
||||
{
|
||||
Logger.ErrorS(LogCategory,
|
||||
"Layer with index '{0}' does not exist, cannot set offset! Trace:\n{1}",
|
||||
layer, Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
Layers[layer].SetOffset(layerOffset);
|
||||
}
|
||||
|
||||
public void LayerSetOffset(object layerKey, Vector2 layerOffset)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer))
|
||||
{
|
||||
Logger.ErrorS(LogCategory, "Layer with key '{0}' does not exist, cannot set offset! Trace:\n{1}",
|
||||
layerKey, Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
LayerSetOffset(layer, layerOffset);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public RSI.StateId LayerGetState(int layer)
|
||||
{
|
||||
@@ -965,40 +1185,65 @@ namespace Robust.Client.GameObjects
|
||||
public ISpriteLayer this[object layerKey] => this[LayerMap[layerKey]];
|
||||
public IEnumerable<ISpriteLayer> AllLayers => Layers;
|
||||
|
||||
internal void Render(DrawingHandleWorld drawingHandle, in Matrix3 worldTransform, Angle worldRotation,
|
||||
Direction? overrideDirection = null)
|
||||
// Lobby SpriteView rendering path
|
||||
internal void Render(DrawingHandleWorld drawingHandle, Angle worldRotation, Direction? overrideDirection = null)
|
||||
{
|
||||
var angle = Rotation;
|
||||
if (Directional)
|
||||
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
|
||||
}
|
||||
|
||||
[DataField("noRot")]
|
||||
private bool _screenLock = true;
|
||||
|
||||
[DataField("overrideDir")]
|
||||
private Direction _overrideDirection = Direction.East;
|
||||
|
||||
[DataField("enableOverrideDir")]
|
||||
private bool _enableOverrideDirection;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool NoRotation { get => _screenLock; set => _screenLock = value; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Direction DirectionOverride { get => _overrideDirection; set => _overrideDirection = value; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool EnableDirectionOverride { get => _enableOverrideDirection; set => _enableOverrideDirection = value; }
|
||||
|
||||
// Sprite rendering path
|
||||
internal void Render(DrawingHandleWorld drawingHandle, in Angle worldRotation, in Vector2 worldPosition)
|
||||
{
|
||||
Direction? overrideDir = null;
|
||||
if (_enableOverrideDirection)
|
||||
{
|
||||
angle -= worldRotation;
|
||||
overrideDir = _overrideDirection;
|
||||
}
|
||||
|
||||
RenderInternal(drawingHandle, worldRotation, worldPosition, overrideDir);
|
||||
}
|
||||
|
||||
private void CalcModelMatrix(int numDirs, Angle worldRotation, Vector2 worldPosition, out Matrix3 modelMatrix)
|
||||
{
|
||||
Angle angle;
|
||||
|
||||
if (_screenLock)
|
||||
{
|
||||
angle = Angle.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
angle -= new Angle(MathHelper.PiOver2);
|
||||
angle = CalcRectWorldAngle(worldRotation, numDirs);
|
||||
}
|
||||
|
||||
var mOffset = Matrix3.CreateTranslation(Offset);
|
||||
var mRotation = Matrix3.CreateRotation(angle);
|
||||
Matrix3.Multiply(ref mRotation, ref mOffset, out var transform);
|
||||
|
||||
// Only apply scale if needed.
|
||||
if(!Scale.EqualsApprox(Vector2.One)) transform.Multiply(Matrix3.CreateScale(Scale));
|
||||
|
||||
transform.Multiply(worldTransform);
|
||||
|
||||
RenderInternal(drawingHandle, worldRotation, overrideDirection, transform);
|
||||
var sWorldRotation = angle;
|
||||
modelMatrix = Matrix3.CreateTransform(in worldPosition, in sWorldRotation);
|
||||
}
|
||||
|
||||
internal void Render(DrawingHandleWorld drawingHandle, Angle worldRotation, Direction? overrideDirection = null)
|
||||
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
|
||||
{
|
||||
RenderInternal(drawingHandle, worldRotation, overrideDirection, Matrix3.Identity);
|
||||
}
|
||||
|
||||
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle worldRotation, Direction? overrideDirection,
|
||||
in Matrix3 transform)
|
||||
{
|
||||
drawingHandle.SetTransform(transform);
|
||||
var localMatrix = GetLocalMatrix();
|
||||
|
||||
foreach (var layer in Layers)
|
||||
{
|
||||
@@ -1007,26 +1252,77 @@ namespace Robust.Client.GameObjects
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: Implement layer-specific rotation and scale.
|
||||
// Oh and when you do update Layer.LocalToLayer so content doesn't break.
|
||||
var numDirs = GetLayerDirectionCount(layer);
|
||||
|
||||
var texture = GetRenderTexture(layer, worldRotation, overrideDirection);
|
||||
CalcModelMatrix(numDirs, worldRotation, worldPosition, out var modelMatrix);
|
||||
Matrix3.Multiply(ref localMatrix, ref modelMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
if (layer.Shader != null)
|
||||
{
|
||||
drawingHandle.UseShader(layer.Shader);
|
||||
}
|
||||
|
||||
drawingHandle.DrawTexture(texture, -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter),
|
||||
color * layer.Color);
|
||||
|
||||
if (layer.Shader != null)
|
||||
{
|
||||
drawingHandle.UseShader(null);
|
||||
}
|
||||
RenderLayer(drawingHandle, layer, worldRotation, overrideDirection);
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderLayer(DrawingHandleWorld drawingHandle, Layer layer, Angle worldRotation, Direction? overrideDirection)
|
||||
{
|
||||
var texture = GetRenderTexture(layer, worldRotation, overrideDirection);
|
||||
|
||||
if (layer.Shader != null)
|
||||
{
|
||||
drawingHandle.UseShader(layer.Shader);
|
||||
}
|
||||
|
||||
var layerColor = color * layer.Color;
|
||||
|
||||
var position = -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
|
||||
var textureSize = texture.Size / (float) EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(position, textureSize);
|
||||
|
||||
// TODO: Implement layer-specific rotation and scale.
|
||||
// Apply these directly to the box.
|
||||
// Oh and when you do update Layer.LocalToLayer so content doesn't break.
|
||||
|
||||
// handle.Modulate changes the color
|
||||
// drawingHandle.SetTransform() is set above, turning the quad into local space vertices
|
||||
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
|
||||
|
||||
if (layer.Shader != null)
|
||||
{
|
||||
drawingHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
|
||||
{
|
||||
var theta = worldAngle.Theta;
|
||||
var segSize = (MathF.PI*2) / (numDirections * 2);
|
||||
var segments = (int)(theta / segSize);
|
||||
var odd = segments % 2;
|
||||
var result = theta - (segments * segSize) - (odd * segSize);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public int GetLayerDirectionCount(ISpriteLayer layer)
|
||||
{
|
||||
if (!layer.RsiState.IsValid)
|
||||
return 1;
|
||||
|
||||
// Pull texture from RSI state instead.
|
||||
var rsi = layer.Rsi ?? BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(layer.RsiState, out var state))
|
||||
{
|
||||
state = GetFallbackState(resourceCache);
|
||||
}
|
||||
|
||||
return state.Directions switch
|
||||
{
|
||||
RSI.State.DirectionType.Dir1 => 1,
|
||||
RSI.State.DirectionType.Dir4 => 4,
|
||||
RSI.State.DirectionType.Dir8 => 8,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
|
||||
private Texture GetRenderTexture(Layer layer, Angle worldRotation, Direction? overrideDirection)
|
||||
{
|
||||
var texture = layer.Texture;
|
||||
@@ -1048,202 +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 _directional, "directional", true);
|
||||
serializer.DataFieldCached(ref _visible, "visible", true);
|
||||
|
||||
// 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();
|
||||
@@ -1314,7 +1414,6 @@ namespace Robust.Client.GameObjects
|
||||
Rotation = thestate.Rotation;
|
||||
Offset = thestate.Offset;
|
||||
Color = thestate.Color;
|
||||
Directional = thestate.Directional;
|
||||
RenderOrder = thestate.RenderOrder;
|
||||
|
||||
if (thestate.BaseRsiPath != null && BaseRSI != null)
|
||||
@@ -1371,16 +1470,28 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private RSI.State.Direction GetDir(RSI.State.DirectionType type, Angle worldRotation)
|
||||
private RSI.State.Direction GetDir(RSI.State.DirectionType rsiDirectionType, Angle worldRotation)
|
||||
{
|
||||
if (!Directional)
|
||||
|
||||
var dir = rsiDirectionType switch
|
||||
{
|
||||
return RSI.State.Direction.South;
|
||||
}
|
||||
RSI.State.DirectionType.Dir1 => Direction.South,
|
||||
RSI.State.DirectionType.Dir4 => worldRotation.GetCardinalDir(),
|
||||
RSI.State.DirectionType.Dir8 => worldRotation.GetDir(),
|
||||
_ => throw new ArgumentException($"Unknown RSI DirectionType: {rsiDirectionType}.", nameof(rsiDirectionType))
|
||||
};
|
||||
|
||||
var angle = new Angle(worldRotation);
|
||||
return angle.GetDir().Convert(type);
|
||||
return dir switch
|
||||
{
|
||||
Direction.North => RSI.State.Direction.North,
|
||||
Direction.South => RSI.State.Direction.South,
|
||||
Direction.East => RSI.State.Direction.East,
|
||||
Direction.West => RSI.State.Direction.West,
|
||||
Direction.SouthEast => RSI.State.Direction.SouthEast,
|
||||
Direction.SouthWest => RSI.State.Direction.SouthWest,
|
||||
Direction.NorthEast => RSI.State.Direction.NorthEast,
|
||||
Direction.NorthWest => RSI.State.Direction.NorthWest,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(dir), dir, null)
|
||||
};
|
||||
}
|
||||
|
||||
private void UpdateIsInert()
|
||||
@@ -1485,6 +1596,39 @@ namespace Robust.Client.GameObjects
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2 CalculateBoundingBox()
|
||||
{
|
||||
// fast check for empty sprites
|
||||
if (Layers.Count == 0)
|
||||
return new Box2();
|
||||
|
||||
// we need to calculate bounding box taking into account all nested layers
|
||||
// because layers can have offsets, scale or rotation we need to calculate a new BB
|
||||
// based on lowest bottomLeft and hightest topRight points from all layers
|
||||
var box = Layers[0].CalculateBoundingBox();
|
||||
|
||||
for (int i = 1; i < Layers.Count; i++)
|
||||
{
|
||||
var layer = Layers[i];
|
||||
var layerBB = layer.CalculateBoundingBox();
|
||||
|
||||
box = box.Union(layerBB);
|
||||
}
|
||||
|
||||
// apply sprite transformations and calculate sprite bounding box
|
||||
// we can optimize it a bit, if sprite doesn't have rotation
|
||||
var spriteBox = box.Scale(Scale);
|
||||
var spriteHasRotation = !Rotation.EqualsApprox(Angle.Zero);
|
||||
var spriteBB = spriteHasRotation ?
|
||||
new Box2Rotated(spriteBox, Rotation).CalcBoundingBox() : spriteBox;
|
||||
|
||||
// move it all to world transform system (with sprite offset)
|
||||
var worldPosition = Owner.Transform.WorldPosition;
|
||||
var worldBB = spriteBB.Translated(Offset + worldPosition);
|
||||
return worldBB;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enum to "offset" a cardinal direction.
|
||||
/// </summary>
|
||||
@@ -1511,7 +1655,7 @@ namespace Robust.Client.GameObjects
|
||||
Flip = 3,
|
||||
}
|
||||
|
||||
private class Layer : ISpriteLayer
|
||||
public class Layer : ISpriteLayer
|
||||
{
|
||||
[ViewVariables] private readonly SpriteComponent _parent;
|
||||
|
||||
@@ -1526,16 +1670,25 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Scale { get; set; } = Vector2.One;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Angle Rotation { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool Visible = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Color Color { get; set; } = Color.White;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool AutoAnimated = true;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Offset { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public DirectionOffset DirOffset { get; set; }
|
||||
|
||||
[ViewVariables]
|
||||
public RSI? ActualRsi => RSI ?? _parent.BaseRSI;
|
||||
|
||||
@@ -1569,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;
|
||||
@@ -1756,6 +1925,38 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
_parent.UpdateIsInert();
|
||||
}
|
||||
|
||||
public void SetOffset(Vector2 offset)
|
||||
{
|
||||
Offset = offset;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Vector2i PixelSize
|
||||
{
|
||||
get
|
||||
{
|
||||
var pixelSize = Vector2i.Zero;
|
||||
if (Texture != null)
|
||||
{
|
||||
pixelSize = Texture.Size;
|
||||
}
|
||||
else if (ActualRsi != null)
|
||||
{
|
||||
pixelSize = ActualRsi.Size;
|
||||
}
|
||||
|
||||
return pixelSize;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Box2 CalculateBoundingBox()
|
||||
{
|
||||
// TODO: scale & rotation for layers is currently unimplemented.
|
||||
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void IAnimationProperties.SetAnimatableProperty(string name, object value)
|
||||
@@ -1807,7 +2008,6 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1858,7 +2058,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
if (!anyTexture)
|
||||
yield return resourceCache.GetFallback<TextureResource>().Texture;
|
||||
|
||||
}
|
||||
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
@@ -1905,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;
|
||||
@@ -1914,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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -352,7 +353,10 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var component in _componentManager.GetNetComponents(createdEntity))
|
||||
{
|
||||
var state = component.GetComponentState();
|
||||
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
|
||||
|
||||
var player = _players.LocalPlayer.Session;
|
||||
var state = component.GetComponentState(player);
|
||||
|
||||
if (state.GetType() == typeof(ComponentState))
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -183,25 +183,61 @@ namespace Robust.Client.Graphics.Clyde
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
RenderTexture? entityPostRenderTarget = null;
|
||||
Vector2i roundedPos = default;
|
||||
if (entry.sprite.PostShader != null)
|
||||
{
|
||||
_renderHandle.UseRenderTarget(EntityPostRenderTarget);
|
||||
_renderHandle.Clear(new Color());
|
||||
// Calculate viewport so that the entity thinks it's drawing to the same position,
|
||||
// which is necessary for light application,
|
||||
// but it's ACTUALLY drawing into the center of the render target.
|
||||
var spritePos = entry.sprite.Owner.Transform.WorldPosition;
|
||||
var screenPos = _eyeManager.WorldToScreen(spritePos);
|
||||
var (roundedX, roundedY) = roundedPos = (Vector2i) screenPos;
|
||||
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
|
||||
flippedPos -= EntityPostRenderTarget.Size / 2;
|
||||
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
|
||||
// calculate world bounding box
|
||||
var spriteBB = entry.sprite.CalculateBoundingBox();
|
||||
var spriteLB = spriteBB.BottomLeft;
|
||||
var spriteRT = spriteBB.TopRight;
|
||||
|
||||
// finally we can calculate screen bounding in pixels
|
||||
var screenLB = _eyeManager.WorldToScreen(spriteLB);
|
||||
var screenRT = _eyeManager.WorldToScreen(spriteRT);
|
||||
|
||||
// we need to scale RT a for effects like emission or highlight
|
||||
// scale can be passed with PostShader as variable in future
|
||||
var postShadeScale = 1.25f;
|
||||
var screenSpriteSize = (Vector2i)((screenRT - screenLB) * postShadeScale).Rounded();
|
||||
screenSpriteSize.Y = -screenSpriteSize.Y;
|
||||
|
||||
// I'm not 100% sure why it works, but without it post-shader
|
||||
// can be lower or upper by 1px than original sprite depending on sprite rotation or scale
|
||||
// probably some rotation rounding error
|
||||
if (screenSpriteSize.X % 2 != 0)
|
||||
screenSpriteSize.X++;
|
||||
if (screenSpriteSize.Y % 2 != 0)
|
||||
screenSpriteSize.Y++;
|
||||
|
||||
// check that sprite size is valid
|
||||
if (screenSpriteSize.X > 0 && screenSpriteSize.Y > 0)
|
||||
{
|
||||
// create new render texture with correct sprite size
|
||||
entityPostRenderTarget = CreateRenderTarget(screenSpriteSize,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
|
||||
name: nameof(entityPostRenderTarget));
|
||||
_renderHandle.UseRenderTarget(entityPostRenderTarget);
|
||||
_renderHandle.Clear(new Color());
|
||||
|
||||
// Calculate viewport so that the entity thinks it's drawing to the same position,
|
||||
// which is necessary for light application,
|
||||
// but it's ACTUALLY drawing into the center of the render target.
|
||||
var spritePos = spriteBB.Center;
|
||||
var screenPos = _eyeManager.WorldToScreen(spritePos);
|
||||
var (roundedX, roundedY) = roundedPos = (Vector2i)screenPos;
|
||||
var flippedPos = new Vector2i(roundedX, screenSize.Y - roundedY);
|
||||
flippedPos -= entityPostRenderTarget.Size / 2;
|
||||
_renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, screenSize));
|
||||
}
|
||||
}
|
||||
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, entry.worldMatrix, entry.worldRotation);
|
||||
var matrix = entry.worldMatrix;
|
||||
var worldPosition = new Vector2(matrix.R0C2, matrix.R1C2);
|
||||
entry.sprite.Render(_renderHandle.DrawingHandleWorld, in entry.worldRotation, in worldPosition);
|
||||
|
||||
if (entry.sprite.PostShader != null)
|
||||
if (entry.sprite.PostShader != null && entityPostRenderTarget != null)
|
||||
{
|
||||
var oldProj = _currentMatrixProj;
|
||||
var oldView = _currentMatrixView;
|
||||
@@ -214,11 +250,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.SetProjView(proj, view);
|
||||
_renderHandle.SetModelTransform(Matrix3.Identity);
|
||||
|
||||
var rounded = roundedPos - EntityPostRenderTarget.Size / 2;
|
||||
var rounded = roundedPos - entityPostRenderTarget.Size / 2;
|
||||
|
||||
var box = Box2i.FromDimensions(rounded, EntityPostRenderTarget.Size);
|
||||
var box = Box2i.FromDimensions(rounded, entityPostRenderTarget.Size);
|
||||
|
||||
_renderHandle.DrawTextureScreen(EntityPostRenderTarget.Texture,
|
||||
_renderHandle.DrawTextureScreen(entityPostRenderTarget.Texture,
|
||||
box.BottomLeft, box.BottomRight, box.TopLeft, box.TopRight,
|
||||
Color.White, null);
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -775,12 +775,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var worldTransform = transform.WorldMatrix;
|
||||
var box = occluder.BoundingBox;
|
||||
|
||||
// So uh, angle 0 = east... Apparently...
|
||||
// We account for that here so I don't go insane.
|
||||
var (tlX, tlY) = worldTransform.Transform(box.BottomLeft);
|
||||
var (trX, trY) = worldTransform.Transform(box.TopLeft);
|
||||
var (brX, brY) = worldTransform.Transform(box.TopRight);
|
||||
var (blX, blY) = worldTransform.Transform(box.BottomRight);
|
||||
var (tlX, tlY) = worldTransform.Transform(box.TopLeft);
|
||||
var (trX, trY) = worldTransform.Transform(box.TopRight);
|
||||
var (brX, brY) = worldTransform.Transform(box.BottomRight);
|
||||
var (blX, blY) = worldTransform.Transform(box.BottomLeft);
|
||||
|
||||
// Faces.
|
||||
var faceN = new Vector4(tlX, tlY, trX, trY);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -37,28 +37,55 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.DrawSetProjViewTransform(proj, view);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a sprite to the screen. The coordinate system is left handed.
|
||||
/// Make sure to set <see cref="DrawSetModelTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="bl">Bottom left vertex of the quad in object space.</param>
|
||||
/// <param name="br">Bottom right vertex of the quad in object space.</param>
|
||||
/// <param name="tl">Top left vertex of the quad in object space.</param>
|
||||
/// <param name="tr">Top right vertex of the quad in object space.</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param
|
||||
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
|
||||
public void DrawTextureScreen(Texture texture, Vector2 bl, Vector2 br, Vector2 tl, Vector2 tr,
|
||||
in Color modulate, in UIBox2? subRegion)
|
||||
{
|
||||
var clydeTexture = ExtractTexture(texture, subRegion, out var csr);
|
||||
var clydeTexture = ExtractTexture(texture, in subRegion, out var csr);
|
||||
|
||||
var (w, h) = clydeTexture.Size;
|
||||
var sr = new Box2(csr.Left / w, (h - csr.Top) / h, csr.Right / w, (h - csr.Bottom) / h);
|
||||
|
||||
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, modulate, sr);
|
||||
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a sprite to the world. The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawSetModelTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="bl">Bottom left vertex of the quad in object space.</param>
|
||||
/// <param name="br">Bottom right vertex of the quad in object space.</param>
|
||||
/// <param name="tl">Top left vertex of the quad in object space.</param>
|
||||
/// <param name="tr">Top right vertex of the quad in object space.</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param>
|
||||
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
|
||||
public void DrawTextureWorld(Texture texture, Vector2 bl, Vector2 br, Vector2 tl, Vector2 tr,
|
||||
Color modulate, in UIBox2? subRegion)
|
||||
{
|
||||
var clydeTexture = ExtractTexture(texture, subRegion, out var csr);
|
||||
var clydeTexture = ExtractTexture(texture, in subRegion, out var csr);
|
||||
|
||||
var (w, h) = clydeTexture.Size;
|
||||
var sr = new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
|
||||
|
||||
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, modulate, sr);
|
||||
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a subRegion (px) into texture coords (0-1) of a given texture (cells of the textureAtlas).
|
||||
/// </summary>
|
||||
private static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
|
||||
{
|
||||
if (texture is AtlasTexture atlas)
|
||||
@@ -383,22 +410,40 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public override void DrawTextureRectRegion(Texture texture, Box2 rect, UIBox2? subRegion = null,
|
||||
Color? modulate = null)
|
||||
/// <summary>
|
||||
/// Draws a sprite to the world. The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawSetModelTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param>
|
||||
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
|
||||
public override void DrawTextureRectRegion(Texture texture, Box2 quad,
|
||||
Color? modulate = null, UIBox2? subRegion = null)
|
||||
{
|
||||
var color = (modulate ?? Color.White) * Modulate;
|
||||
|
||||
_renderHandle.DrawTextureWorld(texture, rect.BottomLeft, rect.BottomRight,
|
||||
rect.TopLeft, rect.TopRight, color, subRegion);
|
||||
_renderHandle.DrawTextureWorld(texture, quad.BottomLeft, quad.BottomRight,
|
||||
quad.TopLeft, quad.TopRight, color, in subRegion);
|
||||
}
|
||||
|
||||
public override void DrawTextureRectRegion(Texture texture, in Box2Rotated rect,
|
||||
UIBox2? subRegion = null, Color? modulate = null)
|
||||
/// <summary>
|
||||
/// Draws a sprite to the world. The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawSetModelTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param>
|
||||
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
|
||||
public override void DrawTextureRectRegion(Texture texture, in Box2Rotated quad,
|
||||
Color? modulate = null, UIBox2? subRegion = null)
|
||||
{
|
||||
var color = (modulate ?? Color.White) * Modulate;
|
||||
|
||||
_renderHandle.DrawTextureWorld(texture, rect.BottomLeft, rect.BottomRight,
|
||||
rect.TopLeft, rect.TopRight, color, subRegion);
|
||||
_renderHandle.DrawTextureWorld(texture, quad.BottomLeft, quad.BottomRight,
|
||||
quad.TopLeft, quad.TopRight, color, in subRegion);
|
||||
}
|
||||
|
||||
public override void DrawPrimitives(DrawPrimitiveTopology primitiveTopology,
|
||||
|
||||
@@ -477,10 +477,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_currentMatrixView = view;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws a texture quad to the screen.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="bl">Bottom left vertex of the quad in object space.</param>
|
||||
/// <param name="br">Bottom right vertex of the quad in object space.</param>
|
||||
/// <param name="tl">Top left vertex of the quad in object space.</param>
|
||||
/// <param name="tr">Top right vertex of the quad in object space.</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param>
|
||||
/// <param name="texCoords">The four corners of the texture coordinates, matching the four vertices.</param>
|
||||
private void DrawTexture(ClydeHandle texture, Vector2 bl, Vector2 br, Vector2 tl, Vector2 tr, in Color modulate,
|
||||
in Box2 sr)
|
||||
in Box2 texCoords)
|
||||
{
|
||||
EnsureBatchState(texture, modulate, true, GetQuadBatchPrimitiveType(), _queuedShader);
|
||||
EnsureBatchState(texture, in modulate, true, GetQuadBatchPrimitiveType(), _queuedShader);
|
||||
|
||||
bl = _currentMatrixModel.Transform(bl);
|
||||
br = _currentMatrixModel.Transform(br);
|
||||
@@ -489,10 +499,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// TODO: split batch if necessary.
|
||||
var vIdx = BatchVertexIndex;
|
||||
BatchVertexData[vIdx + 0] = new Vertex2D(bl, sr.BottomLeft);
|
||||
BatchVertexData[vIdx + 1] = new Vertex2D(br, sr.BottomRight);
|
||||
BatchVertexData[vIdx + 2] = new Vertex2D(tr, sr.TopRight);
|
||||
BatchVertexData[vIdx + 3] = new Vertex2D(tl, sr.TopLeft);
|
||||
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft);
|
||||
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight);
|
||||
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight);
|
||||
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft);
|
||||
BatchVertexIndex += 4;
|
||||
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private GLFWCallbacks.WindowSizeCallback _windowSizeCallback = default!;
|
||||
private GLFWCallbacks.WindowContentScaleCallback _windowContentScaleCallback = default!;
|
||||
private GLFWCallbacks.WindowIconifyCallback _windowIconifyCallback = default!;
|
||||
private GLFWCallbacks.WindowFocusCallback _windowFocusCallback = default!;
|
||||
|
||||
private bool _glfwInitialized;
|
||||
|
||||
@@ -62,6 +63,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private Window* _glfwWindow;
|
||||
|
||||
private Vector2i _framebufferSize;
|
||||
private bool _isFocused;
|
||||
private Vector2i _windowSize;
|
||||
private Vector2i _prevWindowSize;
|
||||
private Vector2i _prevWindowPos;
|
||||
@@ -74,6 +76,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// NOTE: in engine we pretend the framebuffer size is the screen size..
|
||||
// For practical reasons like UI rendering.
|
||||
public override Vector2i ScreenSize => _framebufferSize;
|
||||
public override bool IsFocused => _isFocused;
|
||||
public Vector2 DefaultWindowScale => _windowScale;
|
||||
public Vector2 MouseScreenPosition => _lastMousePos;
|
||||
|
||||
@@ -231,6 +234,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GLFW.SetMouseButtonCallback(_glfwWindow, _mouseButtonCallback);
|
||||
GLFW.SetWindowContentScaleCallback(_glfwWindow, _windowContentScaleCallback);
|
||||
GLFW.SetWindowIconifyCallback(_glfwWindow, _windowIconifyCallback);
|
||||
GLFW.SetWindowFocusCallback(_glfwWindow, _windowFocusCallback);
|
||||
|
||||
GLFW.MakeContextCurrent(_glfwWindow);
|
||||
|
||||
@@ -548,6 +552,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGlfwWindowFocus(Window* window, bool focused)
|
||||
{
|
||||
try
|
||||
{
|
||||
_isFocused = focused;
|
||||
OnWindowFocused?.Invoke(new WindowFocusedEventArgs(focused));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
CatchCallbackException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void StoreCallbacks()
|
||||
{
|
||||
_errorCallback = OnGlfwError;
|
||||
@@ -560,6 +577,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_windowSizeCallback = OnGlfwWindowSize;
|
||||
_windowContentScaleCallback = OnGlfwWindownContentScale;
|
||||
_windowIconifyCallback = OnGlfwWindowIconify;
|
||||
_windowFocusCallback = OnGlfwWindowFocus;
|
||||
}
|
||||
|
||||
public override void SetWindowTitle(string title)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
@@ -39,8 +39,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private GLUniformBuffer<ProjViewMatrices> ProjViewUBO = default!;
|
||||
private GLUniformBuffer<UniformConstants> UniformConstantsUBO = default!;
|
||||
|
||||
private RenderTexture EntityPostRenderTarget = default!;
|
||||
|
||||
private GLBuffer BatchVBO = default!;
|
||||
private GLBuffer BatchEBO = default!;
|
||||
private GLHandle BatchVAO;
|
||||
@@ -88,7 +86,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public override bool Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
|
||||
_configurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
|
||||
if (!InitWindowing())
|
||||
@@ -152,6 +150,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public override event Action<WindowResizedEventArgs>? OnWindowResized;
|
||||
|
||||
public override event Action<WindowFocusedEventArgs>? OnWindowFocused;
|
||||
|
||||
public void Screenshot(ScreenshotType type, Action<Image<Rgb24>> callback)
|
||||
{
|
||||
_queuedScreenshots.Add((type, callback));
|
||||
@@ -314,10 +314,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
ProjViewUBO = new GLUniformBuffer<ProjViewMatrices>(this, BindingIndexProjView, nameof(ProjViewUBO));
|
||||
UniformConstantsUBO = new GLUniformBuffer<UniformConstants>(this, BindingIndexUniformConstants, nameof(UniformConstantsUBO));
|
||||
|
||||
EntityPostRenderTarget = CreateRenderTarget(Vector2i.One * 8 * EyeManager.PixelsPerMeter,
|
||||
new RenderTargetFormatParameters(RenderTargetColorFormat.Rgba8Srgb, true),
|
||||
name: nameof(EntityPostRenderTarget));
|
||||
|
||||
CreateMainViewport();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public IRenderWindow MainWindowRenderTarget { get; }
|
||||
public override Vector2i ScreenSize { get; } = (1280, 720);
|
||||
public Vector2 DefaultWindowScale => (1, 1);
|
||||
public override bool IsFocused => true;
|
||||
|
||||
public ShaderInstance InstanceShader(ClydeHandle handle)
|
||||
{
|
||||
@@ -79,6 +80,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
remove { }
|
||||
}
|
||||
|
||||
public override event Action<WindowFocusedEventArgs> OnWindowFocused
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
// Nada.
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Robust.Client.Graphics
|
||||
protected bool VSync { get; private set; } = true;
|
||||
|
||||
public abstract Vector2i ScreenSize { get; }
|
||||
public abstract bool IsFocused { get; }
|
||||
|
||||
public abstract void SetWindowTitle(string title);
|
||||
|
||||
public virtual bool Initialize()
|
||||
@@ -45,6 +47,8 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public abstract event Action<WindowResizedEventArgs> OnWindowResized;
|
||||
|
||||
public abstract event Action<WindowFocusedEventArgs> OnWindowFocused;
|
||||
|
||||
protected virtual void ReadConfig()
|
||||
{
|
||||
WindowMode = (WindowMode) _configurationManager.GetCVar(CVars.DisplayWindowMode);
|
||||
|
||||
@@ -19,17 +19,18 @@ namespace Robust.Client.Graphics
|
||||
Disposed = true;
|
||||
}
|
||||
|
||||
public void SetTransform(Vector2 position, Angle rotation, Vector2 scale)
|
||||
public void SetTransform(in Vector2 position, in Angle rotation, in Vector2 scale)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
var matrix = Matrix3.Identity;
|
||||
(matrix.R0C0, matrix.R1C1) = scale;
|
||||
matrix.Rotate(rotation);
|
||||
matrix.R0C2 += position.X;
|
||||
matrix.R1C2 += position.Y;
|
||||
var matrix = Matrix3.CreateTransform(in position, in rotation, in scale);
|
||||
SetTransform(in matrix);
|
||||
}
|
||||
|
||||
SetTransform(matrix);
|
||||
public void SetTransform(in Vector2 position, in Angle rotation)
|
||||
{
|
||||
var matrix = Matrix3.CreateTransform(in position, in rotation);
|
||||
SetTransform(in matrix);
|
||||
}
|
||||
|
||||
public abstract void SetTransform(in Matrix3 matrix);
|
||||
|
||||
@@ -6,15 +6,63 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
private const int Ppm = EyeManager.PixelsPerMeter;
|
||||
|
||||
/// <summary>
|
||||
/// Draws an untextured colored rectangle to the world.The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="rect">The four vertices of the quad in object space (or world if the transform is identity.).</param>
|
||||
/// <param name="color">Color of the rectangle.</param>
|
||||
/// <param name="filled">Is it filled with color, or just the border lines?</param>
|
||||
public abstract void DrawRect(Box2 rect, Color color, bool filled = true);
|
||||
|
||||
/// <summary>
|
||||
/// Draws an untextured colored rectangle to the world.The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="rect">The four vertices of the quad in object space (or world if the transform is identity.).
|
||||
/// The rotation of the rectangle is applied before the transform matrix.</param>
|
||||
/// <param name="color">Color of the rectangle.</param>
|
||||
/// <param name="filled">Is it filled with color, or just the border lines?</param>
|
||||
public abstract void DrawRect(in Box2Rotated rect, Color color, bool filled = true);
|
||||
|
||||
public abstract void DrawTextureRectRegion(Texture texture, Box2 rect, UIBox2? subRegion = null,
|
||||
Color? modulate = null);
|
||||
/// <summary>
|
||||
/// Draws a sprite to the world. The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param>
|
||||
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
|
||||
public abstract void DrawTextureRectRegion(Texture texture, Box2 quad,
|
||||
Color? modulate = null, UIBox2? subRegion = null);
|
||||
|
||||
public abstract void DrawTextureRectRegion(Texture texture, in Box2Rotated rect, UIBox2? subRegion = null,
|
||||
Color? modulate = null);
|
||||
/// <summary>
|
||||
/// Draws a sprite to the world. The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).
|
||||
/// The rotation of the rectangle is applied before the transform matrix.</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param>
|
||||
/// <param name="subRegion">The four corners of the texture sub region in px.</param>
|
||||
public abstract void DrawTextureRectRegion(Texture texture, in Box2Rotated quad,
|
||||
Color? modulate = null, UIBox2? subRegion = null);
|
||||
|
||||
/// <summary>
|
||||
/// Draws a full texture sprite to the world. The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="position">The coordinates of the quad in object space (or world if the transform is identity.).</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param>
|
||||
/// <remarks>
|
||||
/// The sprite will have it's local dimensions calculated so that it has <see cref="EyeManager.PixelsPerMeter"/> texels per meter in the world.
|
||||
/// </remarks>
|
||||
public void DrawTexture(Texture texture, Vector2 position, Color? modulate = null)
|
||||
{
|
||||
CheckDisposed();
|
||||
@@ -22,18 +70,35 @@ namespace Robust.Client.Graphics
|
||||
DrawTextureRect(texture, Box2.FromDimensions(position, texture.Size / (float) Ppm), modulate);
|
||||
}
|
||||
|
||||
public void DrawTextureRect(Texture texture, Box2 rect, Color? modulate = null)
|
||||
/// <summary>
|
||||
/// Draws a full texture sprite to the world. The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param>
|
||||
public void DrawTextureRect(Texture texture, Box2 quad, Color? modulate = null)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
DrawTextureRectRegion(texture, rect, null, modulate);
|
||||
DrawTextureRectRegion(texture, quad, modulate);
|
||||
}
|
||||
|
||||
public void DrawTextureRect(Texture texture, in Box2Rotated rect, Color? modulate = null)
|
||||
/// <summary>
|
||||
/// Draws a full texture sprite to the world. The coordinate system is right handed.
|
||||
/// Make sure to set <see cref="DrawingHandleBase.SetTransform"/>
|
||||
/// to set the model matrix if needed.
|
||||
/// </summary>
|
||||
/// <param name="texture">Texture to draw.</param>
|
||||
/// <param name="quad">The four vertices of the quad in object space (or world if the transform is identity.).
|
||||
/// The rotation of the rectangle is applied before the transform matrix.</param>
|
||||
/// <param name="modulate">A color to multiply the texture by when shading.</param>
|
||||
public void DrawTextureRect(Texture texture, in Box2Rotated quad, Color? modulate = null)
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
DrawTextureRectRegion(texture, rect, null, modulate);
|
||||
DrawTextureRectRegion(texture, in quad, modulate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Robust.Client.Graphics
|
||||
|
||||
Vector2i ScreenSize { get; }
|
||||
|
||||
bool IsFocused { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The default scale ratio for window contents, given to us by the OS.
|
||||
/// </summary>
|
||||
@@ -27,6 +29,8 @@ namespace Robust.Client.Graphics
|
||||
|
||||
event Action<WindowResizedEventArgs> OnWindowResized;
|
||||
|
||||
event Action<WindowFocusedEventArgs> OnWindowFocused;
|
||||
|
||||
Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
|
||||
TextureLoadParameters? loadParams = null);
|
||||
|
||||
|
||||
@@ -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, IIndexedPrototype
|
||||
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)
|
||||
|
||||
14
Robust.Client/Graphics/WindowFocusedEventArgs.cs
Normal file
14
Robust.Client/Graphics/WindowFocusedEventArgs.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
public class WindowFocusedEventArgs : EventArgs
|
||||
{
|
||||
public WindowFocusedEventArgs(bool focused)
|
||||
{
|
||||
Focused = focused;
|
||||
}
|
||||
|
||||
public bool Focused { get; }
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
123
Robust.Client/Prototypes/ClientPrototypeManager.cs
Normal file
123
Robust.Client/Prototypes/ClientPrototypeManager.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Robust.Client.Prototypes
|
||||
{
|
||||
public sealed class ClientPrototypeManager : PrototypeManager
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
|
||||
private readonly List<FileSystemWatcher> _watchers = new();
|
||||
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
|
||||
private CancellationTokenSource _reloadToken = new();
|
||||
private readonly HashSet<ResourcePath> _reloadQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
NetManager.RegisterNetMessage<MsgReloadPrototypes>(MsgReloadPrototypes.NAME, accept: NetMessageAccept.Server);
|
||||
|
||||
_clyde.OnWindowFocused += WindowFocusedChanged;
|
||||
|
||||
WatchResources();
|
||||
}
|
||||
|
||||
private void WindowFocusedChanged(WindowFocusedEventArgs args)
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
if (args.Focused && _reloadQueue.Count > 0)
|
||||
{
|
||||
Timer.Spawn(_reloadDelay, ReloadPrototypeQueue, _reloadToken.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
_reloadToken.Cancel();
|
||||
_reloadToken = new CancellationTokenSource();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ReloadPrototypeQueue()
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
var then = DateTime.Now;
|
||||
|
||||
var msg = NetManager.CreateNetMessage<MsgReloadPrototypes>();
|
||||
msg.Paths = _reloadQueue.ToArray();
|
||||
NetManager.ClientSendMessage(msg);
|
||||
|
||||
foreach (var path in _reloadQueue)
|
||||
{
|
||||
ReloadPrototypes(path);
|
||||
}
|
||||
|
||||
_reloadQueue.Clear();
|
||||
|
||||
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
|
||||
#endif
|
||||
}
|
||||
|
||||
private void WatchResources()
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
foreach (var path in Resources.GetContentRoots().Select(r => r.ToString())
|
||||
.Where(r => Directory.Exists(r + "/Prototypes")).Select(p => p + "/Prototypes"))
|
||||
{
|
||||
var watcher = new FileSystemWatcher(path, "*.yml")
|
||||
{
|
||||
IncludeSubdirectories = true,
|
||||
NotifyFilter = NotifyFilters.LastWrite
|
||||
};
|
||||
|
||||
watcher.Changed += (_, args) =>
|
||||
{
|
||||
switch (args.ChangeType)
|
||||
{
|
||||
case WatcherChangeTypes.Renamed:
|
||||
case WatcherChangeTypes.Deleted:
|
||||
return;
|
||||
case WatcherChangeTypes.Created:
|
||||
// case WatcherChangeTypes.Deleted:
|
||||
case WatcherChangeTypes.Changed:
|
||||
case WatcherChangeTypes.All:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
TaskManager.RunOnMainThread(() =>
|
||||
{
|
||||
var file = new ResourcePath(args.FullPath);
|
||||
|
||||
foreach (var root in IoCManager.Resolve<IResourceManager>().GetContentRoots())
|
||||
{
|
||||
if (!file.TryRelativeTo(root, out var relative))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_reloadQueue.Add(relative);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
watcher.EnableRaisingEvents = true;
|
||||
_watchers.Add(watcher);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -11,13 +11,13 @@
|
||||
</PropertyGroup>
|
||||
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.166" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
|
||||
<PackageReference Include="nfluidsynth" Version="0.3.1" />
|
||||
<PackageReference Include="NJsonSchema" Version="10.3.8" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NJsonSchema" Version="10.3.1" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
|
||||
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
|
||||
<PackageReference Include="OpenToolkit.OpenAL" Version="4.0.0-pre9.1" />
|
||||
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />
|
||||
|
||||
@@ -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)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,54 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.UserInterface
|
||||
{
|
||||
// Code and design heavily inspired by WPF/Avalonia.
|
||||
|
||||
public partial class Control
|
||||
{
|
||||
public event Action<Control>? OnMinimumSizeChanged;
|
||||
|
||||
private Vector2 _size;
|
||||
|
||||
[ViewVariables] internal Vector2? PreviousMeasure;
|
||||
[ViewVariables] internal UIBox2? PreviousArrange;
|
||||
|
||||
private float _sizeFlagsStretchRatio = 1;
|
||||
private Vector2? _calculatedMinimumSize;
|
||||
private Vector2 _customMinimumSize;
|
||||
private SizeFlags _sizeFlagsHorizontal = SizeFlags.Fill;
|
||||
private SizeFlags _sizeFlagsVertical = SizeFlags.Fill;
|
||||
private bool _layoutDirty;
|
||||
|
||||
private float _minWidth;
|
||||
private float _minHeight;
|
||||
private float _setWidth = float.NaN;
|
||||
private float _setHeight = float.NaN;
|
||||
private float _maxWidth = float.PositiveInfinity;
|
||||
private float _maxHeight = float.PositiveInfinity;
|
||||
|
||||
private bool _horizontalExpand;
|
||||
private bool _verticalExpand;
|
||||
private HAlignment _horizontalAlignment;
|
||||
private VAlignment _verticalAlignment;
|
||||
private Thickness _margin;
|
||||
private bool _isLayoutUpdateOverrideUsed;
|
||||
private bool _measuring;
|
||||
|
||||
[ViewVariables] public Vector2 DesiredSize { get; private set; }
|
||||
[ViewVariables] public Vector2i DesiredPixelSize => (Vector2i) (DesiredSize * UIScale);
|
||||
[ViewVariables] public bool IsMeasureValid { get; private set; }
|
||||
[ViewVariables] public bool IsArrangeValid { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public Thickness Margin
|
||||
{
|
||||
get => _margin;
|
||||
set => _margin = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the <see cref="UIScale"/> for this control changes.
|
||||
/// </summary>
|
||||
protected internal virtual void UIScaleChanged()
|
||||
{
|
||||
MinimumSizeChanged();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -56,7 +80,6 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
_size = value;
|
||||
Resized();
|
||||
UpdateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,12 +202,36 @@ namespace Robust.Client.UserInterface
|
||||
/// Horizontal size flags for container layout.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[Obsolete("Use HorizontalAlignment and HorizontalExpand instead.")]
|
||||
public SizeFlags SizeFlagsHorizontal
|
||||
{
|
||||
get => _sizeFlagsHorizontal;
|
||||
get
|
||||
{
|
||||
var flags = HorizontalAlignment switch
|
||||
{
|
||||
HAlignment.Stretch => SizeFlags.Fill,
|
||||
HAlignment.Left => SizeFlags.None,
|
||||
HAlignment.Center => SizeFlags.ShrinkCenter,
|
||||
HAlignment.Right => SizeFlags.ShrinkEnd,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
if (_horizontalExpand)
|
||||
flags |= SizeFlags.Expand;
|
||||
|
||||
return flags;
|
||||
}
|
||||
set
|
||||
{
|
||||
_sizeFlagsHorizontal = value;
|
||||
HorizontalExpand = (value & SizeFlags.Expand) != 0;
|
||||
HorizontalAlignment = (value & ~SizeFlags.Expand) switch
|
||||
{
|
||||
SizeFlags.None => HAlignment.Left,
|
||||
SizeFlags.Fill => HAlignment.Stretch,
|
||||
SizeFlags.ShrinkCenter => HAlignment.Center,
|
||||
SizeFlags.ShrinkEnd => HAlignment.Right,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
Parent?.UpdateLayout();
|
||||
}
|
||||
@@ -193,18 +240,87 @@ namespace Robust.Client.UserInterface
|
||||
/// <summary>
|
||||
/// Vertical size flags for container layout.
|
||||
/// </summary>
|
||||
[Obsolete("Use VerticalAlignment and VerticalExpand instead.")]
|
||||
[ViewVariables]
|
||||
public SizeFlags SizeFlagsVertical
|
||||
{
|
||||
get => _sizeFlagsVertical;
|
||||
get
|
||||
{
|
||||
var flags = _verticalAlignment switch
|
||||
{
|
||||
VAlignment.Stretch => SizeFlags.Fill,
|
||||
VAlignment.Top => SizeFlags.None,
|
||||
VAlignment.Center => SizeFlags.ShrinkCenter,
|
||||
VAlignment.Bottom => SizeFlags.ShrinkEnd,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
if (_verticalExpand)
|
||||
flags |= SizeFlags.Expand;
|
||||
|
||||
return flags;
|
||||
}
|
||||
set
|
||||
{
|
||||
_sizeFlagsVertical = value;
|
||||
VerticalExpand = (value & SizeFlags.Expand) != 0;
|
||||
VerticalAlignment = (value & ~SizeFlags.Expand) switch
|
||||
{
|
||||
SizeFlags.None => VAlignment.Top,
|
||||
SizeFlags.Fill => VAlignment.Stretch,
|
||||
SizeFlags.ShrinkCenter => VAlignment.Center,
|
||||
SizeFlags.ShrinkEnd => VAlignment.Bottom,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
Parent?.UpdateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public HAlignment HorizontalAlignment
|
||||
{
|
||||
get => _horizontalAlignment;
|
||||
set
|
||||
{
|
||||
_horizontalAlignment = value;
|
||||
InvalidateArrange();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public VAlignment VerticalAlignment
|
||||
{
|
||||
get => _verticalAlignment;
|
||||
set
|
||||
{
|
||||
_verticalAlignment = value;
|
||||
InvalidateArrange();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool HorizontalExpand
|
||||
{
|
||||
get => _horizontalExpand;
|
||||
set
|
||||
{
|
||||
_horizontalExpand = value;
|
||||
Parent?.InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool VerticalExpand
|
||||
{
|
||||
get => _verticalExpand;
|
||||
set
|
||||
{
|
||||
_verticalExpand = value;
|
||||
Parent?.InvalidateArrange();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stretch ratio used to give shared of the available space in case multiple siblings are set to expand
|
||||
/// in a container
|
||||
@@ -225,12 +341,12 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
_sizeFlagsStretchRatio = value;
|
||||
|
||||
Parent?.UpdateLayout();
|
||||
Parent?.InvalidateArrange();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A combination of <see cref="CustomMinimumSize" /> and <see cref="CalculateMinimumSize" />,
|
||||
/// A combination of <see cref="MinSize" /> and <see cref="CalculateMinimumSize" />,
|
||||
/// Whichever is greater.
|
||||
/// Use this for whenever you need the *actual* minimum size of something.
|
||||
/// </summary>
|
||||
@@ -238,24 +354,13 @@ namespace Robust.Client.UserInterface
|
||||
/// This is in virtual pixels.
|
||||
/// </remarks>
|
||||
/// <seealso cref="CombinedPixelMinimumSize"/>
|
||||
[ViewVariables]
|
||||
public Vector2 CombinedMinimumSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_calculatedMinimumSize.HasValue)
|
||||
{
|
||||
_updateMinimumSize();
|
||||
DebugTools.Assert(_calculatedMinimumSize.HasValue);
|
||||
}
|
||||
|
||||
return Vector2.ComponentMax(CustomMinimumSize, _calculatedMinimumSize!.Value);
|
||||
}
|
||||
}
|
||||
[Obsolete("Use DesiredSize and Measure()")]
|
||||
public Vector2 CombinedMinimumSize => DesiredSize;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CombinedMinimumSize"/>, in physical pixels.
|
||||
/// </summary>
|
||||
[Obsolete("Use DesiredSize and Measure()")]
|
||||
public Vector2i CombinedPixelMinimumSize => (Vector2i) (CombinedMinimumSize * UIScale);
|
||||
|
||||
/// <summary>
|
||||
@@ -264,24 +369,95 @@ namespace Robust.Client.UserInterface
|
||||
/// <seealso cref="CalculateMinimumSize" />
|
||||
/// <seealso cref="CombinedMinimumSize" />
|
||||
[ViewVariables]
|
||||
[Obsolete("Use MinSize instead.")]
|
||||
public Vector2 CustomMinimumSize
|
||||
{
|
||||
get => _customMinimumSize;
|
||||
get => (_minWidth, _minHeight);
|
||||
set => (MinWidth, MinHeight) = Vector2.ComponentMax(Vector2.Zero, value);
|
||||
}
|
||||
|
||||
public Vector2 MinSize
|
||||
{
|
||||
get => (_minWidth, _minHeight);
|
||||
set => (MinWidth, MinHeight) = Vector2.ComponentMax(Vector2.Zero, value);
|
||||
}
|
||||
|
||||
public Vector2 SetSize
|
||||
{
|
||||
get => (_setWidth, _setHeight);
|
||||
set => (SetWidth, SetHeight) = value;
|
||||
}
|
||||
|
||||
public Vector2 MaxSize
|
||||
{
|
||||
get => (_maxWidth, _maxHeight);
|
||||
set => (MaxWidth, MaxHeight) = value;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MinWidth
|
||||
{
|
||||
get => _minWidth;
|
||||
set
|
||||
{
|
||||
_customMinimumSize = Vector2.ComponentMax(Vector2.Zero, value);
|
||||
MinimumSizeChanged();
|
||||
_minWidth = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
private void _updateMinimumSize()
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MinHeight
|
||||
{
|
||||
if (_stylingDirty)
|
||||
get => _minHeight;
|
||||
set
|
||||
{
|
||||
ForceRunStyleUpdate();
|
||||
_minHeight = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
_calculatedMinimumSize = Vector2.ComponentMax(Vector2.Zero, CalculateMinimumSize());
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SetWidth
|
||||
{
|
||||
get => _setWidth;
|
||||
set
|
||||
{
|
||||
_setWidth = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float SetHeight
|
||||
{
|
||||
get => _setHeight;
|
||||
set
|
||||
{
|
||||
_setHeight = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxWidth
|
||||
{
|
||||
get => _maxWidth;
|
||||
set
|
||||
{
|
||||
_maxWidth = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float MaxHeight
|
||||
{
|
||||
get => _maxHeight;
|
||||
set
|
||||
{
|
||||
_maxHeight = value;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -289,27 +465,31 @@ namespace Robust.Client.UserInterface
|
||||
/// Do NOT call this directly to get the minimum size for layout purposes!
|
||||
/// Use <see cref="CombinedMinimumSize" /> for the ACTUAL minimum size.
|
||||
/// </summary>
|
||||
[Obsolete("Implement MeasureOverride instead")]
|
||||
protected virtual Vector2 CalculateMinimumSize()
|
||||
{
|
||||
var min = Vector2.Zero;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
min = Vector2.ComponentMax(min, child.CombinedMinimumSize);
|
||||
}
|
||||
return min;
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the GUI system that the minimum size of this control may have changed,
|
||||
/// so that say containers will re-sort it if necessary.
|
||||
/// </summary>
|
||||
[Obsolete("Use InvalidateMeasure()")]
|
||||
public void MinimumSizeChanged()
|
||||
{
|
||||
_calculatedMinimumSize = null;
|
||||
OnMinimumSizeChanged?.Invoke(this);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
Parent?.MinimumSizeChanged();
|
||||
UpdateLayout();
|
||||
public void InvalidateMeasure()
|
||||
{
|
||||
if (!IsMeasureValid)
|
||||
return;
|
||||
|
||||
IsMeasureValid = false;
|
||||
IsArrangeValid = false;
|
||||
|
||||
UserInterfaceManagerInternal.QueueMeasureUpdate(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -320,89 +500,241 @@ namespace Robust.Client.UserInterface
|
||||
/// where running the deferred layout updating system in the UI manager can be annoying.
|
||||
/// If you are forced to use this in regular code, you have found a bug.
|
||||
/// </remarks>
|
||||
[Obsolete("Call Arrange manually for unit tests or call Measure manually for early measures.")]
|
||||
public void ForceRunLayoutUpdate()
|
||||
{
|
||||
DoLayoutUpdate();
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.ForceRunLayoutUpdate();
|
||||
}
|
||||
// TODO: Fix or remove this
|
||||
if (PreviousArrange.HasValue)
|
||||
Arrange(PreviousArrange.Value);
|
||||
}
|
||||
|
||||
protected void UpdateLayout()
|
||||
public void InvalidateArrange()
|
||||
{
|
||||
if (_layoutDirty)
|
||||
if (!IsArrangeValid)
|
||||
{
|
||||
// Already queued for a layout update, don't bother.
|
||||
return;
|
||||
}
|
||||
|
||||
_layoutDirty = true;
|
||||
UserInterfaceManagerInternal.QueueLayoutUpdate(this);
|
||||
IsArrangeValid = false;
|
||||
UserInterfaceManagerInternal.QueueArrangeUpdate(this);
|
||||
}
|
||||
|
||||
protected void FitChildInPixelBox(Control child, UIBox2i pixelBox)
|
||||
[Obsolete("Use InvalidateArrange()")]
|
||||
protected void UpdateLayout()
|
||||
{
|
||||
var topLeft = pixelBox.TopLeft / UIScale;
|
||||
var bottomRight = pixelBox.BottomRight / UIScale;
|
||||
|
||||
FitChildInBox(child, new UIBox2(topLeft, bottomRight));
|
||||
InvalidateArrange();
|
||||
}
|
||||
|
||||
protected void FitChildInBox(Control child, UIBox2 box)
|
||||
public void Measure(Vector2 availableSize)
|
||||
{
|
||||
DebugTools.Assert(child.Parent == this);
|
||||
if (!IsMeasureValid || PreviousMeasure != availableSize)
|
||||
{
|
||||
IsMeasureValid = true;
|
||||
var desired = MeasureCore(availableSize);
|
||||
|
||||
var (minX, minY) = child.CombinedMinimumSize;
|
||||
var newPosX = box.Left;
|
||||
var newSizeX = minX;
|
||||
if (desired.X < 0 || desired.Y < 0 || !float.IsFinite(desired.X) || !float.IsFinite(desired.Y))
|
||||
throw new InvalidOperationException("Invalid size returned from Measure()");
|
||||
|
||||
if ((child.SizeFlagsHorizontal & SizeFlags.ShrinkEnd) != 0)
|
||||
{
|
||||
newPosX += (box.Width - minX);
|
||||
}
|
||||
else if ((child.SizeFlagsHorizontal & SizeFlags.ShrinkCenter) != 0)
|
||||
{
|
||||
newPosX += (box.Width - minX) / 2;
|
||||
}
|
||||
else if ((child.SizeFlagsHorizontal & SizeFlags.Fill) != 0)
|
||||
{
|
||||
newSizeX = Math.Max(box.Width, newSizeX);
|
||||
}
|
||||
var prev = DesiredSize;
|
||||
DesiredSize = desired;
|
||||
PreviousMeasure = availableSize;
|
||||
|
||||
var newPosY = box.Top;
|
||||
var newSizeY = minY;
|
||||
|
||||
if ((child.SizeFlagsVertical & SizeFlags.ShrinkEnd) != 0)
|
||||
{
|
||||
newPosY += (box.Height - minY);
|
||||
if (prev != desired && Parent != null && !Parent._measuring)
|
||||
Parent?.InvalidateMeasure();
|
||||
}
|
||||
else if ((child.SizeFlagsVertical & SizeFlags.ShrinkCenter) != 0)
|
||||
{
|
||||
newPosY += (box.Height - minY) / 2;
|
||||
}
|
||||
else if ((child.SizeFlagsVertical & SizeFlags.Fill) != 0)
|
||||
{
|
||||
newSizeY = Math.Max(box.Height, newSizeY);
|
||||
}
|
||||
|
||||
child.Position = new Vector2(newPosX, newPosY);
|
||||
child.Size = new Vector2(newSizeX, newSizeY);
|
||||
}
|
||||
|
||||
internal void DoLayoutUpdate()
|
||||
protected virtual Vector2 MeasureCore(Vector2 availableSize)
|
||||
{
|
||||
if (!Visible)
|
||||
return default;
|
||||
|
||||
if (_stylingDirty)
|
||||
ForceRunStyleUpdate();
|
||||
|
||||
var withoutMargin = _margin.Deflate(availableSize);
|
||||
|
||||
var constrained = ApplySizeConstraints(this, withoutMargin);
|
||||
|
||||
Vector2 measured;
|
||||
try
|
||||
{
|
||||
_measuring = true;
|
||||
measured = Vector2.ComponentMax(
|
||||
MeasureOverride(constrained),
|
||||
// For the time being keep the old CalculateMinimumSize around.
|
||||
#pragma warning disable 618
|
||||
CalculateMinimumSize());
|
||||
#pragma warning restore 618
|
||||
}
|
||||
finally
|
||||
{
|
||||
_measuring = false;
|
||||
}
|
||||
|
||||
if (!float.IsNaN(SetWidth))
|
||||
{
|
||||
measured.X = SetWidth;
|
||||
}
|
||||
|
||||
measured.X = Math.Clamp(measured.X, MinWidth, MaxWidth);
|
||||
|
||||
if (!float.IsNaN(SetHeight))
|
||||
{
|
||||
measured.Y = SetHeight;
|
||||
}
|
||||
|
||||
measured.Y = Math.Clamp(measured.Y, MinHeight, MaxHeight);
|
||||
|
||||
measured = _margin.Inflate(measured);
|
||||
return Vector2.ComponentMin(measured, availableSize);
|
||||
}
|
||||
|
||||
protected virtual Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var min = Vector2.Zero;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Measure(availableSize);
|
||||
min = Vector2.ComponentMax(min, child.DesiredSize);
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
public void ArrangePixel(UIBox2i finalRect)
|
||||
{
|
||||
var topLeft = finalRect.TopLeft / UIScale;
|
||||
var bottomRight = finalRect.BottomRight / UIScale;
|
||||
|
||||
Arrange(new UIBox2(topLeft, bottomRight));
|
||||
}
|
||||
|
||||
public void Arrange(UIBox2 finalRect)
|
||||
{
|
||||
if (!IsMeasureValid)
|
||||
Measure(PreviousMeasure ?? finalRect.Size);
|
||||
|
||||
if (!IsArrangeValid || PreviousArrange != finalRect)
|
||||
{
|
||||
IsArrangeValid = true;
|
||||
ArrangeCore(finalRect);
|
||||
PreviousArrange = finalRect;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ArrangeCore(UIBox2 finalRect)
|
||||
{
|
||||
if (!Visible)
|
||||
return;
|
||||
|
||||
var withoutMargins = _margin.Deflate(finalRect);
|
||||
var availWithoutMargins = withoutMargins.Size;
|
||||
var size = availWithoutMargins;
|
||||
var origin = withoutMargins.TopLeft;
|
||||
|
||||
if (_horizontalAlignment != HAlignment.Stretch)
|
||||
size.X = Math.Min(size.X, DesiredSize.X - _margin.SumHorizontal);
|
||||
|
||||
if (_verticalAlignment != VAlignment.Stretch)
|
||||
size.Y = Math.Min(size.Y, DesiredSize.Y - _margin.SumVertical);
|
||||
|
||||
|
||||
size = ApplySizeConstraints(this, size);
|
||||
|
||||
Size = size;
|
||||
_isLayoutUpdateOverrideUsed = true;
|
||||
#pragma warning disable 618
|
||||
LayoutUpdateOverride();
|
||||
_layoutDirty = false;
|
||||
#pragma warning restore 618
|
||||
if (!_isLayoutUpdateOverrideUsed)
|
||||
{
|
||||
var arranged = ArrangeOverride(size);
|
||||
|
||||
size = Vector2.ComponentMin(arranged, size);
|
||||
}
|
||||
|
||||
switch (HorizontalAlignment)
|
||||
{
|
||||
case HAlignment.Stretch:
|
||||
case HAlignment.Center:
|
||||
origin.X += (availWithoutMargins.X - size.X) / 2;
|
||||
break;
|
||||
case HAlignment.Right:
|
||||
origin.X += availWithoutMargins.X - size.X;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (VerticalAlignment)
|
||||
{
|
||||
case VAlignment.Stretch:
|
||||
case VAlignment.Center:
|
||||
origin.Y += (availWithoutMargins.Y - size.Y) / 2;
|
||||
break;
|
||||
case VAlignment.Bottom:
|
||||
origin.Y += availWithoutMargins.Y - size.Y;
|
||||
break;
|
||||
}
|
||||
|
||||
Position = origin;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
protected virtual void LayoutUpdateOverride()
|
||||
protected virtual Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
foreach (var child in Children)
|
||||
{
|
||||
FitChildInPixelBox(child, PixelSizeBox);
|
||||
child.Arrange(UIBox2.FromDimensions(Vector2.Zero, finalSize));
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
[Obsolete("Use Control.ArrangePixel")]
|
||||
protected void FitChildInPixelBox(Control child, UIBox2i pixelBox)
|
||||
{
|
||||
child.ArrangePixel(pixelBox);
|
||||
}
|
||||
|
||||
[Obsolete("Use Control.Arrange")]
|
||||
protected void FitChildInBox(Control child, UIBox2 box)
|
||||
{
|
||||
child.Arrange(box);
|
||||
}
|
||||
|
||||
[Obsolete("Implement ArrangeOverride instead.")]
|
||||
protected virtual void LayoutUpdateOverride()
|
||||
{
|
||||
_isLayoutUpdateOverrideUsed = false;
|
||||
}
|
||||
|
||||
private static Vector2 ApplySizeConstraints(Control control, Vector2 avail)
|
||||
{
|
||||
var minW = control._minWidth;
|
||||
var setW = control._setWidth;
|
||||
var maxW = control._maxWidth;
|
||||
|
||||
var maxConstraint = float.IsNaN(setW) ? float.PositiveInfinity : setW;
|
||||
maxW = MathHelper.Clamp(maxConstraint, minW, maxW);
|
||||
|
||||
var minConstraint = float.IsNaN(setW) ? 0 : setW;
|
||||
minW = MathHelper.Clamp(maxW, minConstraint, minW);
|
||||
|
||||
var minH = control._minHeight;
|
||||
var setH = control._setHeight;
|
||||
var maxH = control._maxHeight;
|
||||
|
||||
maxConstraint = float.IsNaN(setH) ? float.PositiveInfinity : setH;
|
||||
maxH = MathHelper.Clamp(maxConstraint, minH, maxH);
|
||||
|
||||
minConstraint = float.IsNaN(setH) ? 0 : setH;
|
||||
minH = MathHelper.Clamp(minW, minConstraint, minH);
|
||||
|
||||
return (
|
||||
Math.Clamp(avail.X, minW, maxW),
|
||||
Math.Clamp(avail.Y, minH, maxH));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -443,5 +775,21 @@ namespace Robust.Client.UserInterface
|
||||
/// </summary>
|
||||
ShrinkEnd = 8,
|
||||
}
|
||||
|
||||
public enum HAlignment
|
||||
{
|
||||
Stretch,
|
||||
Left,
|
||||
Center,
|
||||
Right
|
||||
}
|
||||
|
||||
public enum VAlignment
|
||||
{
|
||||
Stretch,
|
||||
Top,
|
||||
Center,
|
||||
Bottom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
protected virtual void StylePropertiesChanged()
|
||||
{
|
||||
MinimumSizeChanged();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
public void ForceRunStyleUpdate()
|
||||
|
||||
@@ -154,7 +154,8 @@ namespace Robust.Client.UserInterface
|
||||
_propagateVisibilityChanged(value);
|
||||
// TODO: unhardcode this.
|
||||
// Many containers ignore children if they're invisible, so that's why we're replicating that ehre.
|
||||
Parent?.MinimumSizeChanged();
|
||||
Parent?.InvalidateMeasure();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,7 +595,7 @@ namespace Robust.Client.UserInterface
|
||||
/// <param name="newChild">The new child.</param>
|
||||
protected virtual void ChildAdded(Control newChild)
|
||||
{
|
||||
MinimumSizeChanged();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -604,7 +605,7 @@ namespace Robust.Client.UserInterface
|
||||
protected virtual void Parented(Control newParent)
|
||||
{
|
||||
StylesheetUpdateRecursive();
|
||||
UpdateLayout();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -642,7 +643,7 @@ namespace Robust.Client.UserInterface
|
||||
/// <param name="child">The former child.</param>
|
||||
protected virtual void ChildRemoved(Control child)
|
||||
{
|
||||
MinimumSizeChanged();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -35,8 +35,60 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public int? SeparationOverride { get; set; }
|
||||
|
||||
protected override void LayoutUpdateOverride()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var separation = ActualSeparation;
|
||||
|
||||
var minSize = Vector2.Zero;
|
||||
var first = true;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (!child.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
child.Measure(availableSize);
|
||||
|
||||
var childSize = child.DesiredSize;
|
||||
if (Vertical)
|
||||
{
|
||||
var taken = childSize.Y;
|
||||
if (!first)
|
||||
{
|
||||
taken += separation;
|
||||
}
|
||||
|
||||
minSize.Y += taken;
|
||||
availableSize.Y = Math.Max(0, availableSize.Y - taken);
|
||||
|
||||
first = false;
|
||||
minSize.X = Math.Max(minSize.X, childSize.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
var taken = childSize.X;
|
||||
if (!first)
|
||||
{
|
||||
taken += separation;
|
||||
}
|
||||
|
||||
minSize.X += taken;
|
||||
availableSize.X = Math.Max(0, availableSize.X - taken);
|
||||
|
||||
first = false;
|
||||
|
||||
minSize.Y = Math.Max(minSize.Y, childSize.Y);
|
||||
}
|
||||
}
|
||||
|
||||
return minSize;
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var finalPixel = (Vector2i) (finalSize * UIScale);
|
||||
var separation = (int) (ActualSeparation * UIScale);
|
||||
|
||||
// Step one: figure out the sizes of all our children and whether they want to stretch.
|
||||
@@ -51,19 +103,20 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var (minX, minY) = child.CombinedPixelMinimumSize;
|
||||
|
||||
var (minX, minY) = child.DesiredPixelSize;
|
||||
int minSize;
|
||||
bool stretch;
|
||||
|
||||
if (Vertical)
|
||||
{
|
||||
minSize = minY;
|
||||
stretch = (child.SizeFlagsVertical & SizeFlags.Expand) == SizeFlags.Expand;
|
||||
stretch = child.VerticalExpand;
|
||||
}
|
||||
else
|
||||
{
|
||||
minSize = minX;
|
||||
stretch = (child.SizeFlagsHorizontal & SizeFlags.Expand) == SizeFlags.Expand;
|
||||
stretch = child.HorizontalExpand;
|
||||
}
|
||||
|
||||
if (!stretch)
|
||||
@@ -78,7 +131,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
sizeList.Add((child, minSize, minSize, stretch));
|
||||
}
|
||||
|
||||
var stretchMax = Vertical ? PixelHeight : PixelWidth;
|
||||
var stretchMax = Vertical ? finalPixel.Y : finalPixel.X;
|
||||
|
||||
stretchMax -= separation * (ChildCount - 1);
|
||||
// This is the amount of space allocated for stretchable children.
|
||||
@@ -152,62 +205,19 @@ namespace Robust.Client.UserInterface.Controls
|
||||
UIBox2i targetBox;
|
||||
if (Vertical)
|
||||
{
|
||||
targetBox = new UIBox2i(0, offset, PixelWidth, offset+size);
|
||||
targetBox = new UIBox2i(0, offset, finalPixel.X, offset + size);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetBox = new UIBox2i(offset, 0, offset+size, PixelHeight);
|
||||
targetBox = new UIBox2i(offset, 0, offset + size, finalPixel.Y);
|
||||
}
|
||||
|
||||
FitChildInPixelBox(control, targetBox);
|
||||
control.ArrangePixel(targetBox);
|
||||
|
||||
offset += size;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
{
|
||||
var separation = ActualSeparation;
|
||||
|
||||
var minWidth = 0f;
|
||||
var minHeight = 0f;
|
||||
var first = true;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (!child.Visible)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var (childWidth, childHeight) = child.CombinedMinimumSize;
|
||||
if (Vertical)
|
||||
{
|
||||
minHeight += childHeight;
|
||||
if (!first)
|
||||
{
|
||||
minHeight += separation;
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
minWidth = MathF.Max(minWidth, childWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
minWidth += childWidth;
|
||||
if (!first)
|
||||
{
|
||||
minWidth += separation;
|
||||
}
|
||||
|
||||
first = false;
|
||||
|
||||
minHeight = MathF.Max(minHeight, childHeight);
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector2(minWidth, minHeight);
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
public enum AlignMode : byte
|
||||
|
||||
@@ -7,15 +7,19 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
public class CenterContainer : Container
|
||||
{
|
||||
protected override void LayoutUpdateOverride()
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var max = Vector2.Zero;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var childSize = child.CombinedMinimumSize;
|
||||
var childPos = (Size - childSize) / 2;
|
||||
var childSize = child.DesiredSize;
|
||||
var childPos = (finalSize - childSize) / 2;
|
||||
|
||||
FitChildInBox(child, UIBox2.FromDimensions(childPos, childSize));
|
||||
child.Arrange(UIBox2.FromDimensions(childPos, childSize));
|
||||
|
||||
max = Vector2.ComponentMax(max, childSize);
|
||||
}
|
||||
return max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,13 +31,30 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LayoutUpdateOverride()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var contentBox = ActualStyleBox.GetContentBox(PixelSizeBox);
|
||||
var boxSize = ActualStyleBox.MinimumSize / UIScale;
|
||||
var childBox = Vector2.ComponentMax(availableSize - boxSize, Vector2.Zero);
|
||||
var min = Vector2.Zero;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
FitChildInPixelBox(child, (UIBox2i) contentBox);
|
||||
child.Measure(childBox);
|
||||
min = Vector2.ComponentMax(min, child.DesiredSize);
|
||||
}
|
||||
|
||||
return min + boxSize;
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var contentBox = ActualStyleBox.GetContentBox(UIBox2.FromDimensions(Vector2.Zero, finalSize * UIScale));
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.ArrangePixel((UIBox2i) contentBox);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
@@ -49,17 +66,6 @@ namespace Robust.Client.UserInterface.Controls
|
||||
style.Draw(handle, drawBox);
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
{
|
||||
var min = Vector2.Zero;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
min = Vector2.ComponentMax(min, child.CombinedMinimumSize);
|
||||
}
|
||||
|
||||
return min + ActualStyleBox.MinimumSize / UIScale;
|
||||
}
|
||||
|
||||
protected override void DrawModeChanged()
|
||||
{
|
||||
switch (DrawMode)
|
||||
|
||||
@@ -49,8 +49,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
_lineEdit = new LineEdit
|
||||
{
|
||||
CustomMinimumSize = new Vector2(40, 0),
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand
|
||||
MinSize = new Vector2(40, 0),
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
AddChild(_lineEdit);
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
set
|
||||
{
|
||||
_expandBackwards = value;
|
||||
UpdateLayout();
|
||||
InvalidateArrange();
|
||||
}
|
||||
}
|
||||
private bool _expandBackwards;
|
||||
@@ -100,10 +100,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown if the value assigned is less than or equal to 0.
|
||||
/// </exception>
|
||||
public float MaxWidth
|
||||
[Obsolete("Use MaxGridWidth")]
|
||||
public new float MaxWidth
|
||||
{
|
||||
set => MaxGridWidth = value;
|
||||
}
|
||||
|
||||
public float MaxGridWidth
|
||||
{
|
||||
set => SetMaxSize(Dimension.Column, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The max height (in virtual pixels) the grid of elements can have. This dynamically determines
|
||||
/// the number of rows based on the size of the elements. Setting this puts this grid
|
||||
@@ -120,12 +127,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// Thrown if the value assigned is less than or equal to 0.
|
||||
/// </exception>
|
||||
public float MaxHeight
|
||||
[Obsolete("Use MaxGridHeight")]
|
||||
public new float MaxHeight
|
||||
{
|
||||
set => MaxGridHeight = value;
|
||||
}
|
||||
|
||||
public float MaxGridHeight
|
||||
{
|
||||
set => SetMaxSize(Dimension.Row, value);
|
||||
}
|
||||
|
||||
|
||||
private int? _vSeparationOverride;
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
@@ -199,8 +211,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_limitType = LimitType.Count;
|
||||
|
||||
_limitedDimensionCount = value;
|
||||
MinimumSizeChanged();
|
||||
UpdateLayout();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
private void SetMaxSize(Dimension forDimension, float value)
|
||||
@@ -214,8 +225,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_limitType = LimitType.Size;
|
||||
|
||||
_limitSize = value;
|
||||
MinimumSizeChanged();
|
||||
UpdateLayout();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -263,7 +273,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
int maxMinHeight = -1;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var (minSizeX, minSizeY) = child.CombinedPixelMinimumSize;
|
||||
var (minSizeX, minSizeY) = child.DesiredPixelSize;
|
||||
maxMinWidth = Math.Max(maxMinWidth, minSizeX);
|
||||
maxMinHeight = Math.Max(maxMinHeight, minSizeY);
|
||||
}
|
||||
@@ -271,7 +281,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return new Vector2i(maxMinWidth, maxMinHeight);
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
// to make it easier to read and visualize, we're just going to use the terms "x" and "y", width, and height,
|
||||
// rows and cols,
|
||||
@@ -280,6 +290,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
// For the below convention, we pretend that columns have a limit defined, thus
|
||||
// the amount of rows is not limited (unlimited).
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
// TODO: This is not really correct in any fucking way but I CBA to fix this properly.
|
||||
child.Measure(availableSize);
|
||||
}
|
||||
|
||||
var rows = GetCount(UnlimitedDimension);
|
||||
var cols = GetCount(LimitedDimension);
|
||||
var cellSize = CellSize();
|
||||
@@ -304,7 +320,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
// also converting here to our "pretend" scenario where columns have a limit defined.
|
||||
// note if we are limiting by size rather than count, the size of each child is constant (cell size)
|
||||
var (minSizeXActual, minSizeYActual) = _limitType == LimitType.Count ? child.CombinedPixelMinimumSize : cellSize;
|
||||
var (minSizeXActual, minSizeYActual) = _limitType == LimitType.Count ? child.DesiredPixelSize : cellSize;
|
||||
var minSizeX = _limitDimension == Dimension.Column ? minSizeXActual : minSizeYActual;
|
||||
var minSizeY = _limitDimension == Dimension.Column ? minSizeYActual : minSizeXActual;
|
||||
minColWidth[column] = Math.Max(minSizeX, minColWidth[column]);
|
||||
@@ -348,9 +364,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
protected override void LayoutUpdateOverride()
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
// to make it easier to read and visualize, we're just going to use the terms "x" and "y", width, and height,
|
||||
// to make it easier to read and visualize, we're just going to use the terms "x" and "y", width, and height,
|
||||
// rows and cols,
|
||||
// but at the start of the method here we'll set those to what they actually are based
|
||||
// on the limited dimension, which might involve swapping them.
|
||||
@@ -390,19 +406,19 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
// converting here to our "pretend" scenario where columns have a limit defined
|
||||
// note if we are limiting by size rather than count, the size of each child is constant (cell size)
|
||||
var (minSizeXActual, minSizeYActual) = _limitType == LimitType.Count ? child.CombinedPixelMinimumSize : cellSize;
|
||||
var (minSizeXActual, minSizeYActual) = _limitType == LimitType.Count ? child.DesiredPixelSize : cellSize;
|
||||
var minSizeX = _limitDimension == Dimension.Column ? minSizeXActual : minSizeYActual;
|
||||
var minSizeY = _limitDimension == Dimension.Column ? minSizeYActual : minSizeXActual;
|
||||
minColWidth[column] = Math.Max(minSizeX, minColWidth[column]);
|
||||
minRowHeight[row] = Math.Max(minSizeY, minRowHeight[row]);
|
||||
var colSizeFlag = _limitDimension == Dimension.Column
|
||||
? child.SizeFlagsHorizontal
|
||||
: child.SizeFlagsVertical;
|
||||
var rowSizeFlag = UnlimitedDimension == Dimension.Column
|
||||
? child.SizeFlagsHorizontal
|
||||
: child.SizeFlagsVertical;
|
||||
colExpand[column] = colExpand[column] || (colSizeFlag & SizeFlags.Expand) != 0;
|
||||
rowExpand[row] = rowExpand[row] || (rowSizeFlag & SizeFlags.Expand) != 0;
|
||||
var colExpandFlag = _limitDimension == Dimension.Column
|
||||
? child.HorizontalExpand
|
||||
: child.VerticalExpand;
|
||||
var rowExpandFlag = UnlimitedDimension == Dimension.Column
|
||||
? child.HorizontalExpand
|
||||
: child.VerticalExpand;
|
||||
colExpand[column] = colExpand[column] || colExpandFlag;
|
||||
rowExpand[row] = rowExpand[row] || rowExpandFlag;
|
||||
|
||||
index += 1;
|
||||
}
|
||||
@@ -550,10 +566,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var boxHeight = _limitDimension == Dimension.Column ? minRowHeight[row] : minColWidth[column];
|
||||
|
||||
var box = UIBox2i.FromDimensions(left, top, boxWidth, boxHeight);
|
||||
FitChildInPixelBox(child, box);
|
||||
child.ArrangePixel(box);
|
||||
|
||||
hOffset += minColWidth[column] + hSep;
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Diagnostics.Contracts;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Maths;
|
||||
using Timer = Robust.Shared.Timers.Timer;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
@@ -43,9 +43,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
Name = "_v_scroll",
|
||||
|
||||
SizeFlagsVertical = SizeFlags.Fill,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkEnd
|
||||
|
||||
HorizontalAlignment = HAlignment.Right
|
||||
};
|
||||
AddChild(_scrollBar);
|
||||
_scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
|
||||
@@ -403,7 +401,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var size = Vector2.Zero;
|
||||
if (ActualBackground != null)
|
||||
@@ -475,6 +473,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
_scrollBar.ValueTarget -= _getScrollSpeed() * args.Delta.Y;
|
||||
_isAtBottom = _scrollBar.IsAtEnd;
|
||||
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
[Pure]
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public Label()
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter;
|
||||
VerticalAlignment = VAlignment.Center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
_text = value;
|
||||
_textDimensionCacheValid = false;
|
||||
MinimumSizeChanged();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
_clipText = value;
|
||||
RectClipContent = value;
|
||||
MinimumSizeChanged();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Fill = 3
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
if (!_textDimensionCacheValid)
|
||||
{
|
||||
@@ -267,7 +267,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
continue;
|
||||
}
|
||||
|
||||
_cachedTextWidths[_cachedTextWidths.Count-1] += metrics.Value.Advance;
|
||||
_cachedTextWidths[^1] += metrics.Value.Advance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
set
|
||||
{
|
||||
_textures = value;
|
||||
CalculateMinimumSize();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
set
|
||||
{
|
||||
_textureScale = value;
|
||||
MinimumSizeChanged();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
set
|
||||
{
|
||||
_canShrink = value;
|
||||
MinimumSizeChanged();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
if (_textures.Count == 0 || CanShrink)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
@@ -17,6 +19,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
public const float AnchorEnd = 1;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] public bool Debug { get; set; }
|
||||
|
||||
public static readonly AttachedProperty MarginLeftProperty = AttachedProperty.Create("MarginLeft",
|
||||
typeof(LayoutContainer), typeof(float), changed: LayoutPropertyChangedCallback);
|
||||
|
||||
@@ -47,6 +51,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public static readonly AttachedProperty GrowVerticalProperty = AttachedProperty.Create("GrowVertical",
|
||||
typeof(LayoutContainer), typeof(GrowDirection), changed: LayoutPropertyChangedCallback);
|
||||
|
||||
public static readonly AttachedProperty<bool> DebugProperty = AttachedProperty<bool>.Create("Debug",
|
||||
typeof(LayoutContainer));
|
||||
|
||||
|
||||
public static void SetMarginLeft(Control control, float value)
|
||||
{
|
||||
@@ -112,16 +119,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
SetMarginBottom(control, diffY + control.GetValue<float>(MarginBottomProperty));
|
||||
}
|
||||
|
||||
public static void SetSize(Control control, Vector2 size)
|
||||
[Obsolete("Change SetSize on the control instead.")]
|
||||
public new static void SetSize(Control control, Vector2 size)
|
||||
{
|
||||
var (diffX, diffY) = size - control.Size;
|
||||
|
||||
// This is just to make subsequent set calls work correctly.
|
||||
// It should get reset to this exact value next update either way.
|
||||
control.Size = size;
|
||||
|
||||
SetMarginRight(control, diffX + control.GetValue<float>(MarginRightProperty));
|
||||
SetMarginBottom(control, diffY + control.GetValue<float>(MarginBottomProperty));
|
||||
control.SetSize = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -297,8 +298,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
LayoutPresetMode resizeMode = LayoutPresetMode.MinSize,
|
||||
int margin = 0)
|
||||
{
|
||||
control.Measure(Vector2.Infinity);
|
||||
var newSize = control.Size;
|
||||
var minSize = control.CombinedMinimumSize;
|
||||
var minSize = control.DesiredSize;
|
||||
if ((resizeMode & LayoutPresetMode.KeepWidth) == 0)
|
||||
{
|
||||
newSize = new Vector2(minSize.X, newSize.Y);
|
||||
@@ -445,39 +447,131 @@ namespace Robust.Client.UserInterface.Controls
|
||||
control.SetValue(MarginBottomProperty, marginBottom);
|
||||
}
|
||||
|
||||
protected override void LayoutUpdateOverride()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var min = Vector2.Zero;
|
||||
var uiScale = UIScale;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var growH = child.GetValue<GrowDirection>(GrowHorizontalProperty);
|
||||
var growV = child.GetValue<GrowDirection>(GrowVerticalProperty);
|
||||
|
||||
var anchorMargins = CalcAnchorMargins(availableSize, uiScale, child);
|
||||
var size = availableSize;
|
||||
if (growH == GrowDirection.Constrain)
|
||||
size.X = anchorMargins.Width / uiScale;
|
||||
|
||||
if (growV == GrowDirection.Constrain)
|
||||
size.Y = anchorMargins.Height / uiScale;
|
||||
|
||||
child.Measure(size);
|
||||
min = Vector2.ComponentMax(min, child.DesiredSize);
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Arrange(CalcChildRect(finalSize, UIScale, child, out _));
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
if (!Debug)
|
||||
return;
|
||||
|
||||
var (pSizeX, pSizeY) = PixelSize;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
var anchorLeft = child.GetValue<float>(AnchorLeftProperty);
|
||||
var anchorTop = child.GetValue<float>(AnchorTopProperty);
|
||||
var anchorRight = child.GetValue<float>(AnchorRightProperty);
|
||||
var anchorBottom = child.GetValue<float>(AnchorBottomProperty);
|
||||
if (!child.GetValue(DebugProperty))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var marginLeft = child.GetValue<float>(MarginLeftProperty) * UIScale;
|
||||
var marginTop = child.GetValue<float>(MarginTopProperty) * UIScale;
|
||||
var marginRight = child.GetValue<float>(MarginRightProperty) * UIScale;
|
||||
var marginBottom = child.GetValue<float>(MarginBottomProperty) * UIScale;
|
||||
var rect = CalcChildRect(Size, UIScale, child, out var anchorSize);
|
||||
|
||||
var growHorizontal = child.GetValue<GrowDirection>(GrowHorizontalProperty);
|
||||
var growVertical = child.GetValue<GrowDirection>(GrowVerticalProperty);
|
||||
var left = rect.Left * UIScale;
|
||||
var right = rect.Right * UIScale;
|
||||
var top = rect.Top * UIScale;
|
||||
var bottom = rect.Bottom * UIScale;
|
||||
|
||||
// Calculate where the control "wants" to be by its anchors/margins.
|
||||
var left = anchorLeft * pSizeX + marginLeft;
|
||||
var top = anchorTop * pSizeY + marginTop;
|
||||
var right = anchorRight * pSizeX + marginRight;
|
||||
var bottom = anchorBottom * pSizeY + marginBottom;
|
||||
DrawVLine(anchorSize.Left, Color.Pink);
|
||||
DrawVLine(anchorSize.Right, Color.Green);
|
||||
DrawHLine(anchorSize.Top, Color.Pink);
|
||||
DrawHLine(anchorSize.Bottom, Color.Green);
|
||||
|
||||
var (wSizeX, wSizeY) = (right - left, bottom - top);
|
||||
var (minSizeX, minSizeY) = child.CombinedPixelMinimumSize;
|
||||
/*
|
||||
DrawVLine(left, Color.Orange);
|
||||
DrawVLine(right, Color.Blue);
|
||||
DrawHLine(top, Color.Orange);
|
||||
DrawHLine(bottom, Color.Blue);
|
||||
*/
|
||||
|
||||
HandleLayoutOverflow(growHorizontal, minSizeX, left, wSizeX, out var posX, out var sizeX);
|
||||
HandleLayoutOverflow(growVertical, minSizeY, top, wSizeY, out var posY, out var sizeY);
|
||||
|
||||
child.Position = new Vector2(posX, posY) / UserInterfaceManager.UIScale;
|
||||
child.Size = new Vector2(sizeX, sizeY) / UserInterfaceManager.UIScale;
|
||||
handle.DrawRect(new UIBox2(left, top, right, bottom), Color.Red, false);
|
||||
}
|
||||
|
||||
void DrawVLine(float x, Color color)
|
||||
{
|
||||
handle.DrawLine((x, 0), (x, pSizeY), color);
|
||||
}
|
||||
|
||||
void DrawHLine(float y, Color color)
|
||||
{
|
||||
handle.DrawLine((0, y), (pSizeX, y), color);
|
||||
}
|
||||
}
|
||||
|
||||
private static UIBox2 CalcAnchorMargins(Vector2 ourSize, float uiScale, Control child)
|
||||
{
|
||||
var (pSizeX, pSizeY) = ourSize * uiScale;
|
||||
|
||||
var anchorLeft = child.GetValue<float>(AnchorLeftProperty);
|
||||
var anchorTop = child.GetValue<float>(AnchorTopProperty);
|
||||
var anchorRight = child.GetValue<float>(AnchorRightProperty);
|
||||
var anchorBottom = child.GetValue<float>(AnchorBottomProperty);
|
||||
|
||||
var marginLeft = child.GetValue<float>(MarginLeftProperty) * uiScale;
|
||||
var marginTop = child.GetValue<float>(MarginTopProperty) * uiScale;
|
||||
var marginRight = child.GetValue<float>(MarginRightProperty) * uiScale;
|
||||
var marginBottom = child.GetValue<float>(MarginBottomProperty) * uiScale;
|
||||
|
||||
var left = anchorLeft * pSizeX + marginLeft;
|
||||
var top = anchorTop * pSizeY + marginTop;
|
||||
var right = anchorRight * pSizeX + marginRight;
|
||||
var bottom = anchorBottom * pSizeY + marginBottom;
|
||||
|
||||
// Yes, this can return boxes with left > right (and top > bottom).
|
||||
// This is "intentional", see comment in CalcChildRect.
|
||||
|
||||
return new UIBox2(left, top, right, bottom);
|
||||
}
|
||||
|
||||
private static UIBox2 CalcChildRect(Vector2 ourSize, float uiScale, Control child, out UIBox2 anchorSize)
|
||||
{
|
||||
// Calculate where the control "wants" to be by its anchors/margins.
|
||||
var growHorizontal = child.GetValue<GrowDirection>(GrowHorizontalProperty);
|
||||
var growVertical = child.GetValue<GrowDirection>(GrowVerticalProperty);
|
||||
|
||||
anchorSize = CalcAnchorMargins(ourSize, uiScale, child);
|
||||
|
||||
// This intentionally results in negatives if the right bound is < the left bound.
|
||||
// Which then causes HandleLayoutOverflow to CORRECTLY work from the right bound instead.
|
||||
var (wSizeX, wSizeY) = (anchorSize.Right - anchorSize.Left, anchorSize.Bottom - anchorSize.Top);
|
||||
var (minSizeX, minSizeY) = child.DesiredPixelSize;
|
||||
|
||||
HandleLayoutOverflow(growHorizontal, minSizeX, anchorSize.Left, wSizeX, out var posX, out var sizeX);
|
||||
HandleLayoutOverflow(growVertical, minSizeY, anchorSize.Top, wSizeY, out var posY, out var sizeY);
|
||||
|
||||
return UIBox2.FromDimensions(posX / uiScale, posY / uiScale, sizeX / uiScale, sizeY / uiScale);
|
||||
}
|
||||
|
||||
private static void HandleLayoutOverflow(GrowDirection direction, float minSize, float wPos, float wSize,
|
||||
@@ -485,7 +579,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
out float size)
|
||||
{
|
||||
var overflow = minSize - wSize;
|
||||
if (overflow <= 0)
|
||||
if (overflow <= 0 || direction == GrowDirection.Constrain)
|
||||
{
|
||||
pos = wPos;
|
||||
size = wSize;
|
||||
@@ -514,7 +608,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (owner.Parent is LayoutContainer container)
|
||||
{
|
||||
container.UpdateLayout();
|
||||
container.InvalidateArrange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -537,7 +631,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// <summary>
|
||||
/// The control will expand on all axes equally to reach its minimum size.
|
||||
/// </summary>
|
||||
Both
|
||||
Both,
|
||||
|
||||
/// <summary>
|
||||
/// The control will not be allowed to grow on this axis.
|
||||
/// </summary>
|
||||
Constrain,
|
||||
}
|
||||
|
||||
/// <seealso cref="Control.SetMarginsPreset" />
|
||||
|
||||
@@ -242,18 +242,22 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var font = _getFont();
|
||||
var style = _getStyleBox();
|
||||
return new Vector2(0, font.GetHeight(UIScale) / UIScale) + style.MinimumSize / UIScale;
|
||||
}
|
||||
|
||||
protected override void LayoutUpdateOverride()
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var style = _getStyleBox();
|
||||
|
||||
FitChildInPixelBox(_renderBox, (UIBox2i) style.GetContentBox(PixelSizeBox));
|
||||
_renderBox.ArrangePixel(
|
||||
(UIBox2i) style.GetContentBox(
|
||||
UIBox2.FromDimensions(Vector2.Zero, finalSize * UIScale)));
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
protected internal override void TextEntered(GUITextEventArgs args)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
[Obsolete("Set Margin directly")]
|
||||
public class MarginContainer : Container
|
||||
{
|
||||
public int? MarginBottomOverride { get; set; }
|
||||
@@ -9,36 +11,41 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public int? MarginRightOverride { get; set; }
|
||||
public int? MarginLeftOverride { get; set; }
|
||||
|
||||
protected override void LayoutUpdateOverride()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var top = MarginTopOverride ?? 0;
|
||||
var bottom = MarginBottomOverride ?? 0;
|
||||
var left = MarginLeftOverride ?? 0;
|
||||
var right = MarginRightOverride ?? 0;
|
||||
|
||||
var box = UIBox2.FromDimensions(left, top, Width - right - left, Height - bottom - top);
|
||||
var margin = GetMargin();
|
||||
var availWithoutMargin = margin.Deflate(availableSize);
|
||||
|
||||
var max = Vector2.Zero;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
FitChildInBox(child, box);
|
||||
child.Measure(availWithoutMargin);
|
||||
max = Vector2.ComponentMax(max, child.DesiredSize);
|
||||
}
|
||||
|
||||
return margin.Inflate(max);
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var box = GetMargin().Deflate(UIBox2.FromDimensions(Vector2.Zero, finalSize));
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.Arrange(box);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private Thickness GetMargin()
|
||||
{
|
||||
var top = MarginTopOverride ?? 0;
|
||||
var bottom = MarginBottomOverride ?? 0;
|
||||
var left = MarginLeftOverride ?? 0;
|
||||
var right = MarginRightOverride ?? 0;
|
||||
|
||||
var childMinSize = Vector2.Zero;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
childMinSize = Vector2.ComponentMax(child.CombinedMinimumSize, childMinSize);
|
||||
}
|
||||
|
||||
return childMinSize + (left + right, top + bottom);
|
||||
var margin = new Thickness(left, top, right, bottom);
|
||||
return margin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(_popupVBox = new VBoxContainer {CustomMinimumSize = (300, 0)})
|
||||
(_popupVBox = new VBoxContainer {MinSize = (300, 0)})
|
||||
}
|
||||
};
|
||||
_popup.OnPopupHide += PopupHidden;
|
||||
@@ -113,7 +113,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
break;
|
||||
|
||||
case MenuSeparator _:
|
||||
var control = new Control {CustomMinimumSize = (0, 6)};
|
||||
var control = new Control {MinSize = (0, 6)};
|
||||
container.AddChild(control);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -71,14 +71,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_label = new Label
|
||||
{
|
||||
StyleClasses = { StyleClassOptionButton },
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
hBox.AddChild(_label);
|
||||
|
||||
var textureRect = new TextureRect
|
||||
{
|
||||
StyleClasses = { StyleClassOptionTriangle },
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
};
|
||||
hBox.AddChild(textureRect);
|
||||
}
|
||||
@@ -112,7 +112,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (show)
|
||||
{
|
||||
var globalPos = GlobalPosition;
|
||||
var (minX, minY) = _popupVBox.CombinedMinimumSize;
|
||||
_popupVBox.Measure(Vector2.Infinity);
|
||||
var (minX, minY) = _popupVBox.DesiredSize;
|
||||
var box = UIBox2.FromDimensions(globalPos, (Math.Max(minX, Width), minY));
|
||||
UserInterfaceManager.ModalRoot.AddChild(_popup);
|
||||
_popup.Open(box);
|
||||
|
||||
@@ -60,14 +60,14 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_label = new Label
|
||||
{
|
||||
StyleClasses = { StyleClassOptionButton },
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
hBox.AddChild(_label);
|
||||
|
||||
_triangle = new TextureRect
|
||||
{
|
||||
StyleClasses = { StyleClassOptionTriangle },
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
Visible = !HideTriangle
|
||||
};
|
||||
hBox.AddChild(_triangle);
|
||||
@@ -118,7 +118,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (show)
|
||||
{
|
||||
var globalPos = GlobalPosition;
|
||||
var (minX, minY) = _popupVBox.CombinedMinimumSize;
|
||||
_popupVBox.Measure(Vector2.Infinity);
|
||||
var (minX, minY) = _popupVBox.DesiredSize;
|
||||
var box = UIBox2.FromDimensions(globalPos, (Math.Max(minX, Width), minY));
|
||||
UserInterfaceManager.ModalRoot.AddChild(_popup);
|
||||
_popup.Open(box);
|
||||
|
||||
@@ -31,8 +31,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_scrollBar = new VScrollBar
|
||||
{
|
||||
Name = "_v_scroll",
|
||||
SizeFlagsVertical = SizeFlags.Fill,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkEnd
|
||||
HorizontalAlignment = HAlignment.Right
|
||||
};
|
||||
AddChild(_scrollBar);
|
||||
_scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
|
||||
@@ -44,7 +43,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
set
|
||||
{
|
||||
_styleBoxOverride = value;
|
||||
MinimumSizeChanged();
|
||||
InvalidateMeasure();
|
||||
_invalidateEntries();
|
||||
}
|
||||
}
|
||||
@@ -168,7 +167,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_invalidateEntries();
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
return _getStyleBox()?.MinimumSize ?? Vector2.Zero;
|
||||
}
|
||||
|
||||
@@ -17,26 +17,32 @@ namespace Robust.Client.UserInterface.Controls
|
||||
style?.Draw(handle, PixelSizeBox);
|
||||
}
|
||||
|
||||
protected override void LayoutUpdateOverride()
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var contentBox = _getStyleBox()?.GetContentBox(PixelSizeBox) ?? PixelSizeBox;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
FitChildInPixelBox(child, (UIBox2i) contentBox);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
{
|
||||
var styleSize = _getStyleBox()?.MinimumSize ?? Vector2.Zero;
|
||||
var styleSize = (_getStyleBox()?.MinimumSize ?? Vector2.Zero) / UIScale;
|
||||
var measureSize = Vector2.ComponentMax(availableSize - styleSize, Vector2.Zero);
|
||||
var childSize = Vector2.Zero;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
childSize = Vector2.ComponentMax(childSize, child.CombinedMinimumSize);
|
||||
child.Measure(measureSize);
|
||||
childSize = Vector2.ComponentMax(childSize, child.DesiredSize);
|
||||
}
|
||||
|
||||
return styleSize / UIScale + childSize;
|
||||
return styleSize + childSize;
|
||||
}
|
||||
|
||||
protected override Vector2 ArrangeOverride(Vector2 finalSize)
|
||||
{
|
||||
var pixelSize = finalSize * UIScale;
|
||||
var ourSize = UIBox2.FromDimensions(Vector2.Zero, pixelSize);
|
||||
var contentBox = _getStyleBox()?.GetContentBox(ourSize) ?? ourSize;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
child.ArrangePixel((UIBox2i) contentBox);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user