Compare commits

...

54 Commits

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

* Paul's a good boy

* Build working

* Ingame and not lagging to hell

* Why didn't you commit ahhhhh

* Hard collisions working

* Solver parity

* Decent broadphase work done

* BroadPhase outline done

* BroadPhase working

* waiting for pvs

* Fix static PVS AABB

* Stop static bodies from awakening

* Optimise a bunch of stuff

* Even more broadphase stuff

* I'm fucking stupid

* Optimise fixture updates

* Collision solver start

* Building

* A is for Argumentative

* Fix contact caching island flags

* Circle shapes actually workeded

* Damping

* DS2 consumables only

* Slightly more stable

* Even slightlier more stablier

* VV your heart out

* Initial joint support

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

* JOINT PURGATORY

* Joints barely functional lmao

* Okay these joints slightly more functional

* Remove station FrictionJoint

* Also that

* Some Box2D ports

* Cleanup mass

* Edge shape

* Active contacts

* Fix active contacts

* Optimise active contacts even more

* Boxes be stacking

* I would die for smug oh my fucking god

* In which everything is fixed

* Distance joints working LETS GO

* Remove frequency on distancejoint

* Fix some stuff and break joints

* Crashing fixed mehbeh

* ICollideSpecial and more resilience

* auto-clear

* showbb vera

* Slap that TODO in there

* Fix restartround crash

* Random fixes

* Fix fixture networking

* Add intersection method for broadphase

* Fix contacts

* Licenses done

* Optimisations

* Fix wall clips

* Config caching for island

* allocations optimisations

* Optimise casts

* Optimise events queue for physics

* Contact manager optimisations

* Optimise controllers

* Sloth joint or something idk

* Controller graph

* Remove content cvar

* Random cleanup

* Finally remove VirtualController

* Manifold structs again

* Optimise this absolute retardation

* Optimise

* fix license

* Cleanup physics interface

* AHHHHHHHHHHHHH

* Fix collisions again

* snivybus

* Fix potential nasty manifold bug

* Tests go snivy

* Disable prediction for now

* Spans

* Fix ShapeTypes

* fixes

* ch ch changeesss

* Kinematic idea

* Prevent static bodies from waking

* Pass WorldAABB to MoveEvent

* Fix collisions

* manifold structs fucking WOOORRKKKINNGGG

* Better pushing

* Fix merge ickies

* Optimise MoveEvents

* Use event for collisions performance

* Fix content tests

* Do not research tests

* Fix most conflicts

* Paul's trying to kill me

* Maybe collisions work idk

* Make us whole again

* Smug is also trying to kill me

* nani

* shitty collisions

* Settling

* Do not research collisions

* SHIP IT

* Fix joints

* PVS moment

* Fix other assert

* Fix locker collisions

* serializable sleeptime

* Aether2D contacts

* Physics is no longer crashing (and burning)

* Add to the TODO list

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

* fixes serialization il

* copytest

* typo & misc fixes

* 139 moment

* boxing

* mesa dum

* stuff

* goodbye bad friend

* last commit before the big (4) rewrite

* adds datanodes

* kills yamlobjserializer in favor of the new system

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

* changed yamlfieldattribute namespace

* adds back iselfserialize

* refactors consts&flags

* renames everything to data(field/definition)

* adds afterserialization

* help

* dataclassgen

* fuggen help me mannen

* Fix most errors on content

* Fix engine errors except map loader

* maploader & misc fix

* misc fixes

* thing

* help

* refactors datanodes

* help me mannen

* Separate ITypeSerializer into reader and writer

* Convert all type serializers

* priority

* adds alot

* il fixes

* adds robustgen

* argh

* adds array & enum serialization

* fixes dataclasses

* adds vec2i / misc fixes

* fixes inheritance

* a very notcursed todo

* fixes some custom dataclasses

* push dis

* Remove data classes

* boutta box

* yes

* Add angle and regex serializer tests

* Make TypeSerializerTest abstract

* sets up ioc etc

* remove pushinheritance

* fixes

* Merge fixes, fix yaml hot reloading

* General fixes2

* Make enum serialization ignore case

* Fix the tag not being copied in data nodes

* Fix not properly serializing flag enums

* Fix component serialization on startup

* Implement ValueDataNode ToString

* Serialization IL fixes, fix return and string equality

* Remove async from prototype manager

* Make serializing unsupported node as enum exception more descriptive

* Fix serv3 tryread casting to serializer instead of reader

* Add constructor for invalid node type exception

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

* Fix not copying the data of non primitive types

* Fix not using the data definition found in copying

* Make ISerializationHooks require explicit implementations

* Add test for serialization inheritance

* Improve IsOverridenIn method

* Fix error message when a data definition is null

* Add method to cast a read value in Serv3Manager

* Rename IServ3Manager to ISerializationManager

* Rename usages of serv3manager, add generic copy method

* Fix IL copy method lookup

* Rename old usages of serv3manager

* Add ITypeCopier

* resistance is futile

* we will conquer this codebase

* Add copy method to all serializers

* Make primitive mismatch error message more descriptive

* bing bong im going to freacking heck

* oopsie moment

* hello are you interested in my wares

* does generic serializers under new architecture

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

* Update usgaes of generic serializers, cleanup

* does some pushinheritance logic

* finishes pushinheritance FRAMEWORK

* shed

* Add box2, color and component registry serializer tests

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

* Fixes and serializer updates

* Add serialization manager extensions

* adds pushinheritance

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

* Fix grammar component serialization

* Add generic serializer tests

* thonk

* Add array serializer test

* Replace logger warning calls with exceptions

* fixes

* Move redundant methods to serialization manager extensions, cleanup

* Add array serialization

* fixes context

* more fixes

* argh

* inheritance

* this should do it

* fixes

* adds copiers & fixes some stuff

* copiers use context v1

* finishing copy context

* more context fixes

* Test fixes

* funky maps

* Fix server user interface component serialization

* Fix value tuple serialization

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

* fixes

* fixes more stuff

* yes

* Make abstract/interface skips debugs instead of warnings

* Fix typo

* Make some dictionaries readonly

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

* Add base type required and usage for MeansDataDefinition and ImplicitDataDefinitionForInheritorsAttribute

* copy by ref

* Fix exception wording

* Update data field required summary with the new forbidden docs

* Use extension in map loader

* wanna erp

* Change serializing to not use il temporarily

* Make writing work with nullable types

* pushing

* check

* cuddling slaps HARD

* Add serialization priority test

* important fix

* a serialization thing

* serializer moment

* Add validation for some type serializers

* adds context

* moar context

* fixes

* Do the thing for appearance

* yoo lmao

* push haha pp

* Temporarily make copy delegate regular c# code

* Create deserialized component registry to handle not inheriting conflicting references

* YAML LINTER BABY

* ayes

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

* Remove redundant todos

* Add summary doc to every ISerializationManager method

* icon fixes

* Add skip hook argument to readers and copiers

* Merge fixes

* Fix ordering of arguments in read and copy reflection call

* Fix user interface components deserialization

* pew pew

* i am going to HECK

* Add MustUseReturnValue to copy-over methods

* Make serialization log calls use the same sawmill

* gamin

* Fix doc errors in ISerializationManager.cs

* goodbye brave soldier

* fixes

* WIP merge fixes and entity serialization

* aaaaaaaaaaaaaaa

* aaaaaaaaaaaaaaa

* adds inheritancebehaviour

* test/datafield fixes

* forgot that one

* adds more verbose validation

* This fixes the YAML hot reloading

* Replace yield break with Enumerable.Empty

* adds copiers

* aaaaaaaaaaaaa

* array fix
priority fix
misc fixes

* fix(?)

* fix.

* funny map serialization (wip)

* funny map serialization (wip)

* Add TODO

* adds proper info the validation

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

* Improves the error message for missing fields in the linter

* Include component name in unknown component type error node

* adds alwaysrelevant usa

* fixes mapsaving

* moved surpressor to analyzers proj

* warning cleanup & moves surpressor

* removes old msbuild targets

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

This reverts commit 2ee4cc2c26.

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

* Fix nullability warnings

* Improve yaml linter message feedback

* oops moment

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

* Remove try catch from enum parsing

* Make dependency management in serialization less bad

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

* Clean up type serializers

* Improve validation messages and resourc epath checking

* Fix sprite error message

* reached perfection

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

* Moved ConnectedClient property from IPlayerSession down to ICommonSession.

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

* Converted client calls over to the new system.

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

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

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

* Moved ClientContainer to shared.

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

* ContainerManagerComponent is now implicitly registered with the attributes.

* Migrated to 2021 serialization technology.

* Existing Unit Tests work.

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

* Container Type info is now sent over the network.

* Merge client/server container systems.

* Code cleanup.

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

* Remove OldCode.
2021-03-01 15:19:59 -08:00
Pieter-Jan Briers
ab95f39f9f Localize SS14Window 2021-03-01 00:45:36 +01:00
Pieter-Jan Briers
cdd38abab5 Fix two shutdown crashes by removing IDisposable managers. 2021-02-28 23:10:03 +01:00
Pieter-Jan Briers
d751c0b3ab Revert "Physics (#1602)"
This reverts commit fefcc7cba3.
2021-02-28 18:45:18 +01:00
Pieter-Jan Briers
2ace0e9e5a Expose Patreon tier info from auth server. 2021-02-28 18:45:01 +01:00
Pieter-Jan Briers
31716f5104 Work around Roslyn scripting bug with ref structs. 2021-02-28 18:45:01 +01:00
metalgearsloth
fefcc7cba3 Physics (#1602)
* Physics worlds

* Paul's a good boy

* Build working

* Ingame and not lagging to hell

* Why didn't you commit ahhhhh

* Hard collisions working

* Solver parity

* Decent broadphase work done

* BroadPhase outline done

* BroadPhase working

* waiting for pvs

* Fix static PVS AABB

* Stop static bodies from awakening

* Optimise a bunch of stuff

* Even more broadphase stuff

* I'm fucking stupid

* Optimise fixture updates

* Collision solver start

* Building

* A is for Argumentative

* Fix contact caching island flags

* Circle shapes actually workeded

* Damping

* DS2 consumables only

* Slightly more stable

* Even slightlier more stablier

* VV your heart out

* Initial joint support

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

* JOINT PURGATORY

* Joints barely functional lmao

* Okay these joints slightly more functional

* Remove station FrictionJoint

* Also that

* Some Box2D ports

* Cleanup mass

* Edge shape

* Active contacts

* Fix active contacts

* Optimise active contacts even more

* Boxes be stacking

* I would die for smug oh my fucking god

* In which everything is fixed

* Distance joints working LETS GO

* Remove frequency on distancejoint

* Fix some stuff and break joints

* Crashing fixed mehbeh

* ICollideSpecial and more resilience

* auto-clear

* showbb vera

* Slap that TODO in there

* Fix restartround crash

* Random fixes

* Fix fixture networking

* Add intersection method for broadphase

* Fix contacts

* Licenses done

* Optimisations

* Fix wall clips

* Config caching for island

* allocations optimisations

* Optimise casts

* Optimise events queue for physics

* Contact manager optimisations

* Optimise controllers

* Sloth joint or something idk

* Controller graph

* Remove content cvar

* Random cleanup

* Finally remove VirtualController

* Manifold structs again

* Optimise this absolute retardation

* Optimise

* fix license

* Cleanup physics interface

* AHHHHHHHHHHHHH

* Fix collisions again

* snivybus

* Fix potential nasty manifold bug

* Tests go snivy

Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-01 03:09:36 +11:00
Paul
30df989e8d Merge branch 'serialization_v3_nodataclasses' of https://github.com/PaulRitter/RobustToolbox into master 2021-02-28 15:55:26 +01:00
Acruid
86bfea6bd4 ICommonSession Improvements + Player Filter (#1600)
* Removed IBaseSession, pushed all members down to ICommonSession.

* Pulled all members of client IPlayerSession into ICommonSession.
Marked client IPlayerSession as obsolete, use the base ICommonSession.

* Restricted setter access for properties in ICommonSession, only engine should be setting them.

* Fixed ping implementation on server.

* Moved AttachedEntityUid to ICommonSession.

* Added a shared IPlayerManager and pulled some common properties down to it.

* Added a shared player Filter class that holds a set of recipients in a networked call. Very useful for selecting recipients in a shared context.
2021-02-27 20:42:54 -08:00
Paul
d890f168c2 Spawner windows remember positions - engine commit 2021-02-27 12:27:46 +01:00
Paul
f888a810bf fixes that pesky warning 2021-02-27 11:58:51 +01:00
tmtmtl30
16249a4dde doubles default gain value (#1593) 2021-02-26 20:54:43 -08:00
Manel Navola
e33488ba55 Implemented erasing rectangular areas (#1419)
* Added support for erasing rectangular areas

* Apply suggestions from code review

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>

* Switched sending start coordinate + end coordinate to sending start coordinate + rect selection size for preventing different parented positions, general code improvements

* Rewritten certain code part so the checks pass

* Added unshaded shader to rect erasing

* Tweaked alpha of erasing rectangle for better visualizing

Co-authored-by: Manel Navola <ManelNavola@users.noreply.github.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2021-02-26 20:52:12 -08:00
RemieRichards
bfe6eeddb1 Localization Fixes. Stop double-localizing localizationID-sourced names, PROPER returning "True" or "False" instead of "true" or "false" 2021-02-27 01:04:04 +00:00
RemieRichards
7f540e741c Add myself to CODEOWNERS for fluent translations. 2021-02-25 20:30:57 +00:00
Pieter-Jan Briers
b7855f5af5 Fix reloading localizations. 2021-02-25 20:47:17 +01:00
Pieter-Jan Briers
91391e1205 Update NetSerializer submodule 2021-02-25 12:06:28 +01:00
Pieter-Jan Briers
d5199ec459 Update NuGet packages. 2021-02-25 12:06:05 +01:00
Vera Aguilera Puerto
e1e6f4fd10 ContainerHelpers EmptyContainer now has an argument to attach removed entities to grid or map 2021-02-25 11:43:09 +01:00
Leo
e5b6fccf67 Add a scroll speed property to ScrollContainer (#1590)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-02-25 10:39:53 +00:00
RemberBL
95a912c329 Adds args.Handle(); into UI code for scrolling (#1595) 2021-02-25 11:08:19 +01:00
Pieter-Jan Briers
2b4833fc4e Allow content to read assembly versions in sandbox. 2021-02-24 12:18:44 +01:00
Pieter-Jan Briers
b814fc851a Fix more scrollbar DPI scaling bugs. 2021-02-24 12:18:29 +01:00
Pieter-Jan Briers
e87863203b Use DataFieldCached for AppearanceComponent.
What could go wrong?
2021-02-23 23:56:41 +01:00
Pieter-Jan Briers
33b66d9e18 Fix OpenCentered and OpenToLeft window methods. 2021-02-23 23:24:58 +01:00
Pieter-Jan Briers
fd406f7897 Selector-based VV windows have correct size.
Fixes #1594.
2021-02-23 23:10:58 +01:00
Pieter-Jan Briers
7a836d1018 Work around broken nullability.
Revert "Fix nullability errors"

This reverts commit a7f31f9ebf.

Revert "NotNullWhen()"

This reverts commit b332644d48.

Work around broken nullability.
2021-02-23 23:07:19 +01:00
Alex Evgrashin
393c15c44a Post shader will use real sprite bounding box (#1536)
Co-authored-by: Alex Evgrashin <evgrashin.adl@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-02-23 22:54:48 +01:00
Pieter-Jan Briers
a7f31f9ebf Fix nullability errors 2021-02-23 22:53:38 +01:00
RemieRichards
b332644d48 NotNullWhen() 2021-02-23 21:40:33 +00:00
RemieRichards
510f7c0e7c Merge branch 'master' of https://github.com/space-wizards/RobustToolbox into localization_grammar 2021-02-23 21:33:06 +00:00
RemieRichards
fdd05e3d3a Fix GrammarComponent gender parsing, Add tests for GENDER() function (which covers custom types (Enum) and custom functions (GENDER)) 2021-02-23 21:31:13 +00:00
RemieRichards
a42b39bd84 Adds GENDER(), PROPER() and ATTRIB() localization functions, GrammarComponent. 2021-02-23 19:53:56 +00:00
379 changed files with 24644 additions and 10063 deletions

17
.github/CODEOWNERS vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}
}
}
}

View File

@@ -1,7 +1,8 @@
using System;
using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using Robust.Client.Audio.Midi;
using Robust.Client.Console;
using Robust.Client.GameObjects;
using Robust.Client.GameStates;
@@ -26,6 +27,7 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -43,7 +45,7 @@ namespace Robust.Client
[Dependency] private readonly IUserInterfaceManagerInternal _userInterfaceManager = default!;
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
@@ -61,6 +63,7 @@ namespace Robust.Client
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
[Dependency] private readonly IAuthManager _authManager = default!;
[Dependency] private readonly IMidiManager _midiManager = default!;
private CommandLineArgs? _commandLineArgs;
private bool _disableAssemblyLoadContext;
@@ -152,6 +155,8 @@ namespace Robust.Client
_configurationManager.LoadCVarsFromAssembly(loadedModule);
}
IoCManager.Resolve<ISerializationManager>().Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
_modLoader.BroadcastRunLevel(ModRunLevel.Init);
@@ -161,7 +166,7 @@ namespace Robust.Client
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_serializer.Initialize();
_inputManager.Initialize();
_consoleHost.Initialize();
_console.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
@@ -377,6 +382,8 @@ namespace Robust.Client
private void Cleanup()
{
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -222,6 +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();
}
}

View File

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

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -17,6 +17,8 @@ using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -25,8 +27,12 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
namespace Robust.Client.GameObjects
{
public sealed class SpriteComponent : SharedSpriteComponent, ISpriteComponent,
IComponentDebug
IComponentDebug, ISerializationHooks
{
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[DataField("visible")]
private bool _visible = true;
[ViewVariables(VVAccess.ReadWrite)]
@@ -36,6 +42,7 @@ namespace Robust.Client.GameObjects
set => _visible = value;
}
[DataFieldWithConstant("drawdepth", typeof(DrawDepthTag))]
private int drawDepth = DrawDepthTag.Default;
/// <summary>
@@ -48,6 +55,7 @@ namespace Robust.Client.GameObjects
set => drawDepth = value;
}
[DataField("scale")]
private Vector2 scale = Vector2.One;
/// <summary>
@@ -61,7 +69,8 @@ namespace Robust.Client.GameObjects
set => scale = value;
}
private Angle rotation;
[DataField("rotation")]
private Angle rotation = Angle.Zero;
[Animatable]
[ViewVariables(VVAccess.ReadWrite)]
@@ -71,6 +80,7 @@ namespace Robust.Client.GameObjects
set => rotation = value;
}
[DataField("offset")]
private Vector2 offset = Vector2.Zero;
/// <summary>
@@ -84,6 +94,7 @@ namespace Robust.Client.GameObjects
set => offset = value;
}
[DataField("color")]
private Color color = Color.White;
[Animatable]
@@ -108,18 +119,152 @@ namespace Robust.Client.GameObjects
set => _directional = value;
}
[DataField("directional")]
private bool _directional = true;
[DataField("layerDatums")]
private List<PrototypeLayerData> LayerDatums
{
get
{
var layerDatums = new List<PrototypeLayerData>();
foreach (var layer in Layers)
{
layerDatums.Add(layer.ToPrototypeData());
}
return layerDatums;
}
set
{
if(value == null) return;
Layers.Clear();
foreach (var layerDatum in value)
{
var anyTextureAttempted = false;
var layer = new Layer(this);
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
try
{
layer.RSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(path).RSI;
}
catch
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from. Cannot use 'state' property. ({0})",
layerDatum.State);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (IoCManager.Resolve<IPrototypeManager>().TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist.",
layerDatum.Shader);
}
}
layer.Color = layerDatum.Color;
layer.Rotation = layerDatum.Rotation;
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
layer.Scale = layerDatum.Scale;
Layers.Add(layer);
if (layerDatum.MapKeys != null)
{
var index = Layers.Count - 1;
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (IoCManager.Resolve<IReflectionManager>().TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (LayerMap.ContainsKey(key))
{
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
LayerMap.Add(key, index);
}
}
}
_layerMapShared = true;
UpdateIsInert();
}
}
private RSI? _baseRsi;
[ViewVariables(VVAccess.ReadWrite)]
[DataField("rsi", priority: 2)]
public RSI? BaseRSI
{
get => _baseRsi;
set
{
_baseRsi = value;
if (Layers == null || value == null)
if (value == null)
{
return;
}
@@ -147,6 +292,12 @@ namespace Robust.Client.GameObjects
}
}
[DataField("sprite", readOnly: true)] private string? rsi;
[DataField("layers", readOnly: true)] private List<PrototypeLayerData> layerDatums = new ();
[DataField("state", readOnly: true)] private string? state;
[DataField("texture", readOnly: true)] private string? texture;
[ViewVariables(VVAccess.ReadWrite)]
public bool ContainerOccluded { get; set; }
@@ -158,11 +309,7 @@ namespace Robust.Client.GameObjects
[ViewVariables] private Dictionary<object, int> LayerMap = new();
[ViewVariables] private bool _layerMapShared;
[ViewVariables] private List<Layer> Layers = default!;
[Dependency] private readonly IResourceCache resourceCache = default!;
[Dependency] private readonly IPrototypeManager prototypes = default!;
[Dependency] private readonly IReflectionManager reflectionManager = default!;
[ViewVariables] private List<Layer> Layers = new();
[ViewVariables(VVAccess.ReadWrite)] public uint RenderOrder { get; set; }
@@ -170,10 +317,9 @@ namespace Robust.Client.GameObjects
private static ShaderInstance? _defaultShader;
[ViewVariables]
private ShaderInstance? DefaultShader => _defaultShader ??
(_defaultShader = prototypes
.Index<ShaderPrototype>("shaded")
.Instance());
private ShaderInstance? DefaultShader => _defaultShader ??= prototypes
.Index<ShaderPrototype>("shaded")
.Instance();
public const string LogCategory = "go.comp.sprite";
const string LayerSerializationCache = "spritelayer";
@@ -181,6 +327,46 @@ namespace Robust.Client.GameObjects
[ViewVariables(VVAccess.ReadWrite)] public bool IsInert { get; private set; }
void ISerializationHooks.AfterDeserialization()
{
{
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
try
{
BaseRSI = IoCManager.Resolve<IResourceCache>().GetResource<RSIResource>(rsiPath).RSI;
}
catch (Exception e)
{
Logger.ErrorS(SpriteComponent.LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
}
if (layerDatums.Count == 0)
{
if (state != null || texture != null)
{
layerDatums.Insert(0, new PrototypeLayerData
{
TexturePath = string.IsNullOrWhiteSpace(texture) ? null : texture,
State = string.IsNullOrWhiteSpace(state) ? null : state,
Color = Color.White,
Scale = Vector2.One,
Visible = true,
});
state = null;
texture = null;
}
}
if (layerDatums.Count != 0)
{
LayerDatums = layerDatums;
}
}
/// <summary>
/// Update this sprite component to visibly match the current state of other at the time
/// this is called. Does not keep them perpetually in sync.
@@ -1005,9 +1191,14 @@ namespace Robust.Client.GameObjects
RenderInternal(drawingHandle, worldRotation, Vector2.Zero, overrideDirection);
}
private bool _screenLock = false;
private Direction _overrideDirection = Direction.South;
private bool _enableOverrideDirection = false;
[DataField("noRot")]
private bool _screenLock = true;
[DataField("overrideDir")]
private Direction _overrideDirection = Direction.East;
[DataField("enableOverrideDir")]
private bool _enableOverrideDirection;
/// <inheritdoc />
[ViewVariables(VVAccess.ReadWrite)]
@@ -1153,205 +1344,6 @@ namespace Robust.Client.GameObjects
return texture;
}
public override void ExposeData(ObjectSerializer serializer)
{
base.ExposeData(serializer);
serializer.DataFieldCached(ref scale, "scale", Vector2.One);
serializer.DataFieldCached(ref rotation, "rotation", Angle.Zero);
serializer.DataFieldCached(ref offset, "offset", Vector2.Zero);
serializer.DataFieldCached(ref drawDepth, "drawdepth", DrawDepthTag.Default,
WithFormat.Constants<DrawDepthTag>());
serializer.DataFieldCached(ref color, "color", Color.White);
serializer.DataFieldCached(ref _visible, "visible", true);
serializer.DataFieldCached(ref _directional, "directional", true); //TODO: Kill ME
serializer.DataFieldCached(ref _screenLock, "noRot", true);
serializer.DataFieldCached(ref _enableOverrideDirection, "enableOverrideDir", false);
serializer.DataFieldCached(ref _overrideDirection, "overrideDir", Direction.East);
// TODO: Writing?
if (!serializer.Reading)
{
return;
}
{
var rsi = serializer.ReadDataField<string?>("sprite", null);
if (!string.IsNullOrWhiteSpace(rsi))
{
var rsiPath = TextureRoot / rsi;
try
{
BaseRSI = resourceCache.GetResource<RSIResource>(rsiPath).RSI;
}
catch (Exception e)
{
Logger.ErrorS(LogCategory, "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
}
}
}
List<Layer> CloneLayers(List<Layer> source)
{
var clone = new List<Layer>(source.Count);
foreach (var layer in source)
{
clone.Add(new Layer(layer, this));
}
return clone;
}
if (serializer.TryGetCacheData<List<Layer>>(LayerSerializationCache, out var layers))
{
LayerMap = serializer.GetCacheData<Dictionary<object, int>>(LayerMapSerializationCache);
_layerMapShared = true;
Layers = CloneLayers(layers);
UpdateIsInert();
return;
}
layers = new List<Layer>();
var layerMap = new Dictionary<object, int>();
var layerData =
serializer.ReadDataField("layers", new List<PrototypeLayerData>());
if(layerData.Count == 0){
var baseState = serializer.ReadDataField<string?>("state", null);
var texturePath = serializer.ReadDataField<string?>("texture", null);
if (baseState != null || texturePath != null)
{
layerData.Insert(0, new PrototypeLayerData
{
TexturePath = string.IsNullOrWhiteSpace(texturePath) ? null : texturePath,
State = string.IsNullOrWhiteSpace(baseState) ? null : baseState,
Color = Color.White,
Scale = Vector2.One,
Visible = true,
});
}
}
foreach (var layerDatum in layerData)
{
var anyTextureAttempted = false;
var layer = new Layer(this);
if (!string.IsNullOrWhiteSpace(layerDatum.RsiPath))
{
var path = TextureRoot / layerDatum.RsiPath;
try
{
layer.RSI = resourceCache.GetResource<RSIResource>(path).RSI;
}
catch
{
Logger.ErrorS(LogCategory, "Unable to load layer RSI '{0}'.", path);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.State))
{
anyTextureAttempted = true;
var theRsi = layer.RSI ?? BaseRSI;
if (theRsi == null)
{
Logger.ErrorS(LogCategory,
"Layer has no RSI to load states from."
+ "cannot use 'state' property. Prototype: '{0}'", Owner.Prototype?.ID);
}
else
{
var stateid = new RSI.StateId(layerDatum.State);
layer.State = stateid;
if (theRsi.TryGetState(stateid, out var state))
{
// Always use south because this layer will be cached in the serializer.
layer.AnimationTimeLeft = state.GetDelay(0);
}
else
{
Logger.ErrorS(LogCategory,
$"State '{stateid}' not found in RSI: '{theRsi.Path}'.",
stateid);
}
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.TexturePath))
{
anyTextureAttempted = true;
if (layer.State.IsValid)
{
Logger.ErrorS(LogCategory,
"Cannot specify 'texture' on a layer if it has an RSI state specified."
);
}
else
{
layer.Texture =
resourceCache.GetResource<TextureResource>(TextureRoot / layerDatum.TexturePath);
}
}
if (!string.IsNullOrWhiteSpace(layerDatum.Shader))
{
if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.Shader = prototype.Instance();
}
else
{
Logger.ErrorS(LogCategory,
"Shader prototype '{0}' does not exist. Prototype: '{1}'",
layerDatum.Shader, Owner.Prototype?.ID);
}
}
layer.Color = layerDatum.Color;
layer.Rotation = layerDatum.Rotation;
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = anyTextureAttempted && layerDatum.Visible;
layer.Scale = layerDatum.Scale;
layers.Add(layer);
if (layerDatum.MapKeys != null)
{
var index = layers.Count - 1;
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (reflectionManager.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
if (layerMap.ContainsKey(key))
{
Logger.ErrorS(LogCategory, "Duplicate layer map key definition: {0}", key);
continue;
}
layerMap.Add(key, index);
}
}
}
Layers = layers;
LayerMap = layerMap;
_layerMapShared = true;
serializer.SetCacheData(LayerSerializationCache, CloneLayers(Layers));
serializer.SetCacheData(LayerMapSerializationCache, layerMap);
UpdateIsInert();
}
public override void OnRemove()
{
base.OnRemove();
@@ -1604,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>
@@ -1630,7 +1655,7 @@ namespace Robust.Client.GameObjects
Flip = 3,
}
private class Layer : ISpriteLayer
public class Layer : ISpriteLayer
{
[ViewVariables] private readonly SpriteComponent _parent;
@@ -1697,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;
@@ -1889,6 +1930,33 @@ namespace Robust.Client.GameObjects
{
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)
@@ -1940,7 +2008,6 @@ namespace Robust.Client.GameObjects
}
return state;
}
}
@@ -1991,7 +2058,6 @@ namespace Robust.Client.GameObjects
if (!anyTexture)
yield return resourceCache.GetFallback<TextureResource>().Texture;
}
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
@@ -2038,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;
@@ -2047,9 +2114,9 @@ namespace Robust.Client.GameObjects
_components[typeof(ISpriteComponent)] = comp;
}
if (Prototype != null && Prototype.Components.TryGetValue(comp.Name, out var node))
if (Prototype != null && Prototype.TryGetComponent<T>(comp.Name, out var node))
{
comp.ExposeData(YamlObjectSerializer.NewReader(node));
comp = serializationManager.Copy(node, comp)!;
}
return comp;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -183,27 +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));
}
}
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;
@@ -216,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);

View File

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

View File

@@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.ResourceManagement.ResourceTypes;
using Robust.Client.ResourceManagement;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using StencilOp = Robust.Client.Graphics.StencilOp;

View File

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

View File

@@ -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;
@@ -316,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();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,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));
}
}
}
}
}

View File

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

View File

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

View File

@@ -1,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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,13 +11,12 @@
</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="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" />
@@ -39,9 +38,6 @@
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Graphics\RSI\RSISchema.json" Condition="'$(Configuration)' == 'Debug'">
<LogicalName>Robust.Client.Graphics.RSI.RSISchema.json</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Graphics\Clyde\Shaders\*" />
</ItemGroup>

View File

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

View File

@@ -473,6 +473,8 @@ namespace Robust.Client.UserInterface.Controls
_scrollBar.ValueTarget -= _getScrollSpeed() * args.Delta.Y;
_isAtBottom = _scrollBar.IsAtEnd;
args.Handle();
}
[Pure]

View File

@@ -101,12 +101,12 @@ namespace Robust.Client.UserInterface.Controls
}
var box = _getGrabberBox();
if (!box.Contains(args.RelativePosition))
if (!box.Contains(args.RelativePixelPosition))
{
return;
}
_grabData = (args.RelativePosition, Value);
_grabData = (args.RelativePixelPosition, Value);
_updatePseudoClass();
args.Handle();
}

View File

@@ -17,6 +17,9 @@ namespace Robust.Client.UserInterface.Controls
private bool _suppressScrollValueChanged;
public int ScrollSpeedX { get; set; } = 50;
public int ScrollSpeedY { get; set; } = 50;
public ScrollContainer()
{
MouseFilter = MouseFilterMode.Pass;
@@ -197,13 +200,15 @@ namespace Robust.Client.UserInterface.Controls
if (_vScrollEnabled)
{
_vScrollBar.ValueTarget -= args.Delta.Y * 50;
_vScrollBar.ValueTarget -= args.Delta.Y * ScrollSpeedY;
}
if (_hScrollEnabled)
{
_hScrollBar.ValueTarget += args.Delta.X * 50;
_hScrollBar.ValueTarget += args.Delta.X * ScrollSpeedX;
}
args.Handle();
}
protected override void ChildAdded(Control newChild)

View File

@@ -228,7 +228,7 @@ namespace Robust.Client.UserInterface.CustomControls
// An explaination: The BadOpenGLVersionWindow was showing up off the top-left corner of the screen.
// Basically, if OpenCentered happens super-early, RootControl doesn't get time to layout children.
// But we know that this is always going to be one of the roots anyway for now.
LayoutContainer.SetPosition(this, (UserInterfaceManager.RootControl.Size - Size) / 2);
LayoutContainer.SetPosition(this, (UserInterfaceManager.RootControl.Size - SetSize) / 2);
_firstTimeOpened = false;
}
else
@@ -244,7 +244,7 @@ namespace Robust.Client.UserInterface.CustomControls
Measure(Vector2.Infinity);
SetSize = DesiredSize;
Open();
LayoutContainer.SetPosition(this, (0, (Parent!.Size.Y - Size.Y) / 2));
LayoutContainer.SetPosition(this, (0, (Parent!.Size.Y - DesiredSize.Y) / 2));
_firstTimeOpened = false;
}
else

View File

@@ -148,13 +148,6 @@ namespace Robust.Client.UserInterface.CustomControls
SearchBar.GrabKeyboardFocus();
}
public override void Close()
{
base.Close();
Dispose();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
@@ -201,6 +194,7 @@ namespace Robust.Client.UserInterface.CustomControls
private void OnEraseButtonToggled(BaseButton.ButtonToggledEventArgs args)
{
placementManager.ToggleEraser();
OverrideMenu.Disabled = args.Pressed;
}
private void BuildEntityList(string? searchStr = null)
@@ -510,6 +504,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
EraseButton.Pressed = false;
OverrideMenu.Disabled = false;
}
private class DoNotMeasure : Control

View File

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

View File

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

View File

@@ -26,12 +26,10 @@ namespace Robust.Client.Utility
{
if (cache.TryGetResource<RSIResource>(
SharedSpriteComponent.TextureRoot / rsiSpecifier.RsiPath,
out var theRsi))
out var theRsi) &&
theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
{
if (theRsi.RSI.TryGetState(rsiSpecifier.RsiState, out var state))
{
return state;
}
return state;
}
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);

View File

@@ -229,7 +229,11 @@ namespace Robust.Client.ViewVariables
public async void OpenVV(ViewVariablesObjectSelector selector)
{
var window = new SS14Window {Title = "View Variables"};
var window = new SS14Window
{
Title = "View Variables",
SetSize = _defaultWindowSize
};
var loadingLabel = new Label {Text = "Retrieving remote object data from server..."};
window.Contents.AddChild(loadingLabel);

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,10 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Prometheus;
using Robust.Server.Console;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Server.ViewVariables;
using Robust.Shared.Asynchronous;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Exceptions;
using Robust.Server.Scripting;
using Robust.Server.ServerStatus;
using Robust.Shared;
using Robust.Server.DataMetrics;
using Robust.Server.Debugging;
using Robust.Server.GameObjects;
@@ -25,12 +12,26 @@ using Robust.Server.GameStates;
using Robust.Server.Log;
using Robust.Server.Placement;
using Robust.Server.Player;
using Robust.Server.Scripting;
using Robust.Server.ServerStatus;
using Robust.Server.Utility;
using Robust.Server.ViewVariables;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Exceptions;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Serilog.Debugging;
using Serilog.Sinks.Loki;
using Stopwatch = Robust.Shared.Timing.Stopwatch;
@@ -298,6 +299,8 @@ namespace Robust.Server
_entities.Initialize();
IoCManager.Resolve<ISerializationManager>().Initialize();
// because of 'reasons' this has to be called after the last assembly is loaded
// otherwise the prototypes will be cleared
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();

View File

@@ -159,7 +159,7 @@ namespace Robust.Server.Console
_systemConsole.Print(text + "\n");
}
private static string FormatPlayerString(IBaseSession? session)
private static string FormatPlayerString(ICommonSession? session)
{
return session != null ? $"{session.Name}" : "[HOST]";
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using System.Collections.Generic;
@@ -59,6 +59,9 @@ namespace Robust.Server.Placement
case PlacementManagerMessage.RequestEntRemove:
HandleEntRemoveReq(msg.EntityUid);
break;
case PlacementManagerMessage.RequestRectRemove:
HandleRectRemoveReq(msg);
break;
}
}
@@ -202,6 +205,19 @@ namespace Robust.Server.Placement
_entityManager.DeleteEntity(entity);
}
private void HandleRectRemoveReq(MsgPlacement msg)
{
EntityCoordinates start = msg.EntityCoordinates;
Vector2 rectSize = msg.RectSize;
foreach (IEntity entity in _entityManager.GetEntitiesIntersecting(start.GetMapId(_entityManager),
new Box2(start.Position, start.Position + rectSize)))
{
if (entity.Deleted || entity.HasComponent<IMapGridComponent>() || entity.HasComponent<IActorComponent>())
continue;
entity.Delete();
}
}
/// <summary>
/// Places mob in entity placement mode with given settings.
/// </summary>

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Enums;
@@ -13,21 +13,10 @@ namespace Robust.Server.Player
/// <summary>
/// Manages each players session when connected to the server.
/// </summary>
public interface IPlayerManager
public interface IPlayerManager : Shared.Players.ISharedPlayerManager
{
/// <summary>
/// Number of players currently connected to this server.
/// Fetching this is thread safe.
/// </summary>
int PlayerCount { get; }
BoundKeyMap KeyMap { get; }
/// <summary>
/// Maximum number of players that can connect to this server at one time.
/// </summary>
int MaxPlayers { get; }
/// <summary>
/// Raised when the <see cref="SessionStatus" /> of a <see cref="IPlayerSession" /> is changed.
/// </summary>

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Players;
@@ -7,8 +7,6 @@ namespace Robust.Server.Player
{
public interface IPlayerSession : ICommonSession
{
EntityUid? AttachedEntityUid { get; }
INetChannel ConnectedClient { get; }
DateTime ConnectedTime { get; }
/// <summary>

View File

@@ -14,6 +14,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -53,6 +54,11 @@ namespace Robust.Server.Player
[ViewVariables]
private readonly Dictionary<string, NetUserId> _userIdMap = new();
/// <inheritdoc />
public IEnumerable<ICommonSession> NetworkedSessions => _sessions.Values;
/// <inheritdoc />
public IEnumerable<ICommonSession> Sessions => _sessions.Values;
/// <inheritdoc />
[ViewVariables]

View File

@@ -1,9 +1,10 @@
using Robust.Shared.GameStates;
using Robust.Shared.GameStates;
using Robust.Server.GameObjects;
using System;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.ViewVariables;
namespace Robust.Server.Player
@@ -42,11 +43,32 @@ namespace Robust.Server.Player
private SessionStatus _status = SessionStatus.Connecting;
/// <inheritdoc />
public string Name { get; }
[ViewVariables]
internal string Name { get; set; }
/// <inheritdoc />
string ICommonSession.Name
{
get => this.Name;
set => this.Name = value;
}
[ViewVariables]
public SessionStatus Status
internal short Ping
{
get => ConnectedClient.Ping;
set => throw new NotSupportedException();
}
short ICommonSession.Ping
{
get => this.Ping;
set => this.Ping = value;
}
[ViewVariables]
internal SessionStatus Status
{
get => _status;
set
@@ -62,6 +84,13 @@ namespace Robust.Server.Player
}
}
/// <inheritdoc />
SessionStatus ICommonSession.Status
{
get => this.Status;
set => this.Status = value;
}
/// <inheritdoc />
public DateTime ConnectedTime { get; private set; }

View File

@@ -10,9 +10,9 @@
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.0" />
<PackageReference Include="prometheus-net" Version="4.0.0" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.3" />
<PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="Serilog.Sinks.Loki" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.Primitives" Version="5.0.0" />
</ItemGroup>

View File

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

View File

@@ -18,6 +18,7 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
@@ -45,6 +46,7 @@ namespace Robust.Server
IoCManager.Register<IMapLoader, MapLoader>();
IoCManager.Register<IPlacementManager, PlacementManager>();
IoCManager.Register<IPlayerManager, PlayerManager>();
IoCManager.Register<ISharedPlayerManager, PlayerManager>();
IoCManager.Register<IPrototypeManager, ServerPrototypeManager>();
IoCManager.Register<IReflectionManager, ServerReflectionManager>();
IoCManager.Register<IResourceManager, ResourceManager>();

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -118,6 +118,12 @@ namespace Robust.Shared.Maths
return FromDimensions(center - size / 2, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Box2 CentredAroundZero(Vector2 size)
{
return FromDimensions(-size / 2, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool Intersects(in Box2 other)
{
@@ -236,6 +242,16 @@ namespace Robust.Shared.Maths
center + halfSize);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Box2 Scale(Vector2 scale)
{
var center = Center;
var halfSize = (Size / 2) * scale;
return new Box2(
center - halfSize,
center + halfSize);
}
/// <summary>Returns a Box2 translated by the given amount.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Box2 Translated(Vector2 point)

View File

@@ -9,7 +9,7 @@
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Condition="'$(TargetFramework)' == 'net472'" Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.2" />
</ItemGroup>

View File

@@ -43,6 +43,11 @@ namespace Robust.Shared.Maths
public static readonly Vector2 Infinity = new(float.PositiveInfinity, float.PositiveInfinity);
/// <summary>
/// A vector with NaN X and Y.
/// </summary>
public static readonly Vector2 NaN = new(float.NaN, float.NaN);
/// <summary>
/// Construct a vector from its coordinates.
/// </summary>
@@ -90,7 +95,7 @@ namespace Robust.Shared.Maths
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly Vector2 Rounded()
{
return new((float) MathF.Round(X), (float) MathF.Round(Y));
return new(MathF.Round(X), MathF.Round(Y));
}
/// <summary>

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Robust.Shared.Maths
@@ -16,12 +17,12 @@ namespace Robust.Shared.Maths
/// <summary>
/// The X component of the Vector2i.
/// </summary>
public int X;
[JsonInclude] public int X;
/// <summary>
/// The Y component of the Vector2i.
/// </summary>
public int Y;
[JsonInclude] public int Y;
/// <summary>
/// Construct a vector from its coordinates.

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