Compare commits

...

63 Commits

Author SHA1 Message Date
Metal Gear Sloth
0936cf3c7f Fix CanCollide serialization 2021-03-08 04:06:21 +11:00
Metal Gear Sloth
43b75a69c2 Fix contact overlap 2021-03-08 03:48:30 +11:00
metalgearsloth
c17c8d7a11 Physics (#1605)
* Physics worlds

* Paul's a good boy

* Build working

* Ingame and not lagging to hell

* Why didn't you commit ahhhhh

* Hard collisions working

* Solver parity

* Decent broadphase work done

* BroadPhase outline done

* BroadPhase working

* waiting for pvs

* Fix static PVS AABB

* Stop static bodies from awakening

* Optimise a bunch of stuff

* Even more broadphase stuff

* I'm fucking stupid

* Optimise fixture updates

* Collision solver start

* Building

* A is for Argumentative

* Fix contact caching island flags

* Circle shapes actually workeded

* Damping

* DS2 consumables only

* Slightly more stable

* Even slightlier more stablier

* VV your heart out

* Initial joint support

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

* JOINT PURGATORY

* Joints barely functional lmao

* Okay these joints slightly more functional

* Remove station FrictionJoint

* Also that

* Some Box2D ports

* Cleanup mass

* Edge shape

* Active contacts

* Fix active contacts

* Optimise active contacts even more

* Boxes be stacking

* I would die for smug oh my fucking god

* In which everything is fixed

* Distance joints working LETS GO

* Remove frequency on distancejoint

* Fix some stuff and break joints

* Crashing fixed mehbeh

* ICollideSpecial and more resilience

* auto-clear

* showbb vera

* Slap that TODO in there

* Fix restartround crash

* Random fixes

* Fix fixture networking

* Add intersection method for broadphase

* Fix contacts

* Licenses done

* Optimisations

* Fix wall clips

* Config caching for island

* allocations optimisations

* Optimise casts

* Optimise events queue for physics

* Contact manager optimisations

* Optimise controllers

* Sloth joint or something idk

* Controller graph

* Remove content cvar

* Random cleanup

* Finally remove VirtualController

* Manifold structs again

* Optimise this absolute retardation

* Optimise

* fix license

* Cleanup physics interface

* AHHHHHHHHHHHHH

* Fix collisions again

* snivybus

* Fix potential nasty manifold bug

* Tests go snivy

* Disable prediction for now

* Spans

* Fix ShapeTypes

* fixes

* ch ch changeesss

* Kinematic idea

* Prevent static bodies from waking

* Pass WorldAABB to MoveEvent

* Fix collisions

* manifold structs fucking WOOORRKKKINNGGG

* Better pushing

* Fix merge ickies

* Optimise MoveEvents

* Use event for collisions performance

* Fix content tests

* Do not research tests

* Fix most conflicts

* Paul's trying to kill me

* Maybe collisions work idk

* Make us whole again

* Smug is also trying to kill me

* nani

* shitty collisions

* Settling

* Do not research collisions

* SHIP IT

* Fix joints

* PVS moment

* Fix other assert

* Fix locker collisions

* serializable sleeptime

* Aether2D contacts

* Physics is no longer crashing (and burning)

* Add to the TODO list

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

* fixes serialization il

* copytest

* typo & misc fixes

* 139 moment

* boxing

* mesa dum

* stuff

* goodbye bad friend

* last commit before the big (4) rewrite

* adds datanodes

* kills yamlobjserializer in favor of the new system

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

* changed yamlfieldattribute namespace

* adds back iselfserialize

* refactors consts&flags

* renames everything to data(field/definition)

* adds afterserialization

* help

* dataclassgen

* fuggen help me mannen

* Fix most errors on content

* Fix engine errors except map loader

* maploader & misc fix

* misc fixes

* thing

* help

* refactors datanodes

* help me mannen

* Separate ITypeSerializer into reader and writer

* Convert all type serializers

* priority

* adds alot

* il fixes

* adds robustgen

* argh

* adds array & enum serialization

* fixes dataclasses

* adds vec2i / misc fixes

* fixes inheritance

* a very notcursed todo

* fixes some custom dataclasses

* push dis

* Remove data classes

* boutta box

* yes

* Add angle and regex serializer tests

* Make TypeSerializerTest abstract

* sets up ioc etc

* remove pushinheritance

* fixes

* Merge fixes, fix yaml hot reloading

* General fixes2

* Make enum serialization ignore case

* Fix the tag not being copied in data nodes

* Fix not properly serializing flag enums

* Fix component serialization on startup

* Implement ValueDataNode ToString

* Serialization IL fixes, fix return and string equality

* Remove async from prototype manager

* Make serializing unsupported node as enum exception more descriptive

* Fix serv3 tryread casting to serializer instead of reader

* Add constructor for invalid node type exception

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

* Fix not copying the data of non primitive types

* Fix not using the data definition found in copying

* Make ISerializationHooks require explicit implementations

* Add test for serialization inheritance

* Improve IsOverridenIn method

* Fix error message when a data definition is null

* Add method to cast a read value in Serv3Manager

* Rename IServ3Manager to ISerializationManager

* Rename usages of serv3manager, add generic copy method

* Fix IL copy method lookup

* Rename old usages of serv3manager

* Add ITypeCopier

* resistance is futile

* we will conquer this codebase

* Add copy method to all serializers

* Make primitive mismatch error message more descriptive

* bing bong im going to freacking heck

* oopsie moment

* hello are you interested in my wares

* does generic serializers under new architecture

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

* Update usgaes of generic serializers, cleanup

* does some pushinheritance logic

* finishes pushinheritance FRAMEWORK

* shed

* Add box2, color and component registry serializer tests

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

* Fixes and serializer updates

* Add serialization manager extensions

* adds pushinheritance

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

* Fix grammar component serialization

* Add generic serializer tests

* thonk

* Add array serializer test

* Replace logger warning calls with exceptions

* fixes

* Move redundant methods to serialization manager extensions, cleanup

* Add array serialization

* fixes context

* more fixes

* argh

* inheritance

* this should do it

* fixes

* adds copiers & fixes some stuff

* copiers use context v1

* finishing copy context

* more context fixes

* Test fixes

* funky maps

* Fix server user interface component serialization

* Fix value tuple serialization

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

* fixes

* fixes more stuff

* yes

* Make abstract/interface skips debugs instead of warnings

* Fix typo

* Make some dictionaries readonly

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

* Add base type required and usage for MeansDataDefinition and ImplicitDataDefinitionForInheritorsAttribute

* copy by ref

* Fix exception wording

* Update data field required summary with the new forbidden docs

* Use extension in map loader

* wanna erp

* Change serializing to not use il temporarily

* Make writing work with nullable types

* pushing

* check

* cuddling slaps HARD

* Add serialization priority test

* important fix

* a serialization thing

* serializer moment

* Add validation for some type serializers

* adds context

* moar context

* fixes

* Do the thing for appearance

* yoo lmao

* push haha pp

* Temporarily make copy delegate regular c# code

* Create deserialized component registry to handle not inheriting conflicting references

* YAML LINTER BABY

* ayes

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

* Remove redundant todos

* Add summary doc to every ISerializationManager method

* icon fixes

* Add skip hook argument to readers and copiers

* Merge fixes

* Fix ordering of arguments in read and copy reflection call

* Fix user interface components deserialization

* pew pew

* i am going to HECK

* Add MustUseReturnValue to copy-over methods

* Make serialization log calls use the same sawmill

* gamin

* Fix doc errors in ISerializationManager.cs

* goodbye brave soldier

* fixes

* WIP merge fixes and entity serialization

* aaaaaaaaaaaaaaa

* aaaaaaaaaaaaaaa

* adds inheritancebehaviour

* test/datafield fixes

* forgot that one

* adds more verbose validation

* This fixes the YAML hot reloading

* Replace yield break with Enumerable.Empty

* adds copiers

* aaaaaaaaaaaaa

* array fix
priority fix
misc fixes

* fix(?)

* fix.

* funny map serialization (wip)

* funny map serialization (wip)

* Add TODO

* adds proper info the validation

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

* Improves the error message for missing fields in the linter

* Include component name in unknown component type error node

* adds alwaysrelevant usa

* fixes mapsaving

* moved surpressor to analyzers proj

* warning cleanup & moves surpressor

* removes old msbuild targets

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

This reverts commit 2ee4cc2c26.

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

* Fix nullability warnings

* Improve yaml linter message feedback

* oops moment

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

* Remove try catch from enum parsing

* Make dependency management in serialization less bad

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

* Clean up type serializers

* Improve validation messages and resourc epath checking

* Fix sprite error message

* reached perfection

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

* Moved ConnectedClient property from IPlayerSession down to ICommonSession.

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

* Converted client calls over to the new system.

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

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

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

* Moved ClientContainer to shared.

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

* ContainerManagerComponent is now implicitly registered with the attributes.

* Migrated to 2021 serialization technology.

* Existing Unit Tests work.

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

* Container Type info is now sent over the network.

* Merge client/server container systems.

* Code cleanup.

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

* Remove OldCode.
2021-03-01 15:19:59 -08:00
Pieter-Jan Briers
ab95f39f9f Localize SS14Window 2021-03-01 00:45:36 +01:00
Pieter-Jan Briers
cdd38abab5 Fix two shutdown crashes by removing IDisposable managers. 2021-02-28 23:10:03 +01:00
Pieter-Jan Briers
d751c0b3ab Revert "Physics (#1602)"
This reverts commit fefcc7cba3.
2021-02-28 18:45:18 +01:00
Pieter-Jan Briers
2ace0e9e5a Expose Patreon tier info from auth server. 2021-02-28 18:45:01 +01:00
Pieter-Jan Briers
31716f5104 Work around Roslyn scripting bug with ref structs. 2021-02-28 18:45:01 +01:00
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
Pieter-Jan Briers
6d41958853 Fix nullability of TryIndex<T>. 2021-02-23 22:25:48 +01:00
Pieter-Jan Briers
cecc4dfcf2 Improve SharedTransformSystem:
Do not fire events for deleted entities.
Optimize to remove allocations & LINQ.
2021-02-23 22:05:49 +01:00
Pieter-Jan Briers
4ac40f2e90 Make norot on sprites default for the time being.
To band aid the pulling issues.
2021-02-23 21:32:00 +01:00
Pieter-Jan Briers
3e12d44173 Bool/enum/entity handling for localization parameters. 2021-02-23 20:59:21 +01:00
RemieRichards
a42b39bd84 Adds GENDER(), PROPER() and ATTRIB() localization functions, GrammarComponent. 2021-02-23 19:53:56 +00:00
DrSmugleaf
22affccf24 Add individual layer offset (#1583)
* Add individual layer offset

* Fix error message

* Bring back layer offsetting
2021-02-23 12:55:45 +01:00
Pieter-Jan Briers
028724c47b Localization improvements:
*Allow content to define localization functions.
* Add rldloc command to reload localizations.
* Doc comments
* Error handling
* Parallelize loading of localization files, since I can only assume we'll have a lot eventually.
* Type system stuff to allow content to pass custom data types into fluent.
* Code cleanup.
2021-02-23 11:35:54 +01:00
Pieter-Jan Briers
0114bff2fc Add IFormattable to sandbox whitelist. 2021-02-23 11:27:51 +01:00
Pieter-Jan Briers
4ddbd644eb Add helper method to set up logging inside unit tests. 2021-02-23 11:27:42 +01:00
Pieter-Jan Briers
f0366531ef Inject the csi directly into my master. 2021-02-23 01:39:33 +01:00
Pieter-Jan Briers
6bd5814f4a Automatically fetch names and descs from loc. 2021-02-22 11:11:11 +01:00
Pieter-Jan Briers
78c132fdab Mute loc warnings for the time being. 2021-02-22 08:57:30 +01:00
Remie Richards
460cf57d7c Fluent Localization (#1584) 2021-02-22 00:36:02 +01:00
Pieter-Jan Briers
a3190a8aca Improvements to SpriteComponent for new angle changes: (#1589)
1. Fixes CopyFrom with new rotation parameters.
2. Adds a couple APIs for ClickableComponent content side.
2021-02-22 00:30:16 +01:00
Pieter-Jan Briers
6921fb2fbf Make UserInterfaceManager not dispose root control on game shutdown.
No real reason to do this and it only risks breaking something.
2021-02-21 23:52:09 +01:00
Fortune117
9954d571de Fix for Crash Caused by the Entity Spawn Menu (#1588) 2021-02-21 20:16:01 +01:00
Pieter-Jan Briers
17fe000a1e Fix math tests due to angle/direction changes. 2021-02-21 20:06:39 +01:00
Pieter-Jan Briers
fba415e765 Bit of work to make Direction.South == Angle.Zero.
This makes Direction no longer follow a cartesian trig circle but oh well. Also helper methods to work with this.

Math tests currently fail, pushing this so Acruid can see about fixing SpriteComponent.
2021-02-21 19:47:42 +01:00
405 changed files with 25589 additions and 11018 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

@@ -574,29 +574,22 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- name: NGetText
- name: Fluent.Net
license: |
The MIT License (MIT)
blushingpenguin and Contributors
Copyright (c) 2012 Vitaly Zilnik
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
http://www.apache.org/licenses/LICENSE-2.0
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
- name: NetSerializer
license: |

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,20 @@
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Robust.Client.Console.Commands
{
internal sealed class ReloadLocalizationsCommand : IConsoleCommand
{
public string Command => "rldloc";
public string Description => "Reloads localization (client & server)";
public string Help => "Usage: rldloc";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
IoCManager.Resolve<ILocalizationManager>().ReloadLocalizations();
shell.RemoteExecuteCommand("sudo rldloc");
}
}
}

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,5 +1,8 @@
#if CLIENT_SCRIPTING
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
@@ -36,6 +39,8 @@ namespace Robust.Client.Console
private readonly ScriptGlobals _globals;
private ScriptState? _state;
private (string[] imports, string code)? _autoImportRepeatBuffer;
public ScriptConsoleClient()
{
Title = Loc.GetString("Robust C# Interactive (CLIENT)");
@@ -54,38 +59,56 @@ namespace Robust.Client.Console
var code = InputBar.Text;
InputBar.Clear();
// Remove > or . at the end of the output panel.
OutputPanel.RemoveEntry(^1);
_inputBuffer.AppendLine(code);
_linesEntered += 1;
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(_inputBuffer.ToString()), ScriptInstanceShared.ParseOptions);
if (!SyntaxFactory.IsCompleteSubmission(tree))
if (_autoImportRepeatBuffer.HasValue && code == "y")
{
if (_linesEntered == 1)
var (imports, repeatCode) = _autoImportRepeatBuffer.Value;
var sb = new StringBuilder();
foreach (var import in imports)
{
OutputPanel.AddText($"> {code}");
sb.AppendFormat("using {0};\n", import);
}
else
{
OutputPanel.AddText($". {code}");
}
OutputPanel.AddText(".");
return;
sb.Append(repeatCode);
code = sb.ToString();
}
code = _inputBuffer.ToString().Trim();
// Remove echo of partial submission from the output panel.
for (var i = 1; i < _linesEntered; i++)
else
{
// Remove > or . at the end of the output panel.
OutputPanel.RemoveEntry(^1);
}
_inputBuffer.Clear();
_linesEntered = 0;
_inputBuffer.AppendLine(code);
_linesEntered += 1;
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(_inputBuffer.ToString()),
ScriptInstanceShared.ParseOptions);
if (!SyntaxFactory.IsCompleteSubmission(tree))
{
if (_linesEntered == 1)
{
OutputPanel.AddText($"> {code}");
}
else
{
OutputPanel.AddText($". {code}");
}
OutputPanel.AddText(".");
return;
}
code = _inputBuffer.ToString().Trim();
// Remove echo of partial submission from the output panel.
for (var i = 1; i < _linesEntered; i++)
{
OutputPanel.RemoveEntry(^1);
}
_inputBuffer.Clear();
_linesEntered = 0;
}
Script newScript;
@@ -135,6 +158,8 @@ namespace Robust.Client.Console
OutputPanel.AddMessage(msg);
OutputPanel.AddText(">");
PromptAutoImports(e.Diagnostics, code);
return;
}
@@ -148,13 +173,23 @@ namespace Robust.Client.Console
else if (ScriptInstanceShared.HasReturnValue(newScript))
{
var msg = new FormattedMessage();
msg.AddText(CSharpObjectFormatter.Instance.FormatObject(_state.ReturnValue));
msg.AddText(ScriptInstanceShared.SafeFormat(_state.ReturnValue));
OutputPanel.AddMessage(msg);
}
OutputPanel.AddText(">");
}
private void PromptAutoImports(IEnumerable<Diagnostic> diags, string code)
{
if (!ScriptInstanceShared.CalcAutoImports(_reflectionManager, diags, out var found))
return;
OutputPanel.AddText($"Auto-import {string.Join(", ", found)} (enter 'y')?");
_autoImportRepeatBuffer = (found.ToArray(), code);
}
private sealed class ScriptGlobalsImpl : ScriptGlobals
{
private readonly ScriptConsoleClient _owner;
@@ -180,7 +215,7 @@ namespace Robust.Client.Console
public override void show(object obj)
{
write(CSharpObjectFormatter.Instance.FormatObject(obj));
write(ScriptInstanceShared.SafeFormat(obj));
}
}
}

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();
@@ -318,6 +323,7 @@ namespace Robust.Client
logManager.GetSawmill("discord").Level = LogLevel.Warning;
logManager.GetSawmill("net.predict").Level = LogLevel.Info;
logManager.GetSawmill("szr").Level = LogLevel.Info;
logManager.GetSawmill("loc").Level = LogLevel.Error;
#if DEBUG_ONLY_FCE_INFO
#if DEBUG_ONLY_FCE_LOG
@@ -376,6 +382,8 @@ namespace Robust.Client
private void Cleanup()
{
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
}

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

@@ -82,6 +82,8 @@ namespace Robust.Client.GameObjects
uint RenderOrder { get; set; }
bool IsInert { get; }
Matrix3 GetLocalMatrix();
/// <summary>
/// Sets a layer key to the layer map, creating it if it does not exist.
/// </summary>
@@ -220,5 +222,12 @@ namespace Robust.Client.GameObjects
ISpriteLayer this[object layerKey] { get; }
IEnumerable<ISpriteLayer> AllLayers { get; }
int GetLayerDirectionCount(ISpriteLayer layer);
/// <summary>
/// Calculate sprite bounding box in world-space coordinates.
/// </summary>
Box2 CalculateBoundingBox();
}
}

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.
@@ -198,6 +384,9 @@ namespace Robust.Client.GameObjects
rotation = other.rotation;
scale = other.scale;
drawDepth = other.drawDepth;
_screenLock = other._screenLock;
_overrideDirection = other._overrideDirection;
_enableOverrideDirection = other._enableOverrideDirection;
Layers = new List<Layer>(other.Layers.Count);
foreach (var otherLayer in other.Layers)
{
@@ -219,6 +408,11 @@ namespace Robust.Client.GameObjects
RenderOrder = other.RenderOrder;
}
public Matrix3 GetLocalMatrix()
{
return Matrix3.CreateTransform(in offset, in rotation, in scale);
}
/// <inheritdoc />
public void LayerMapSet(object key, int layer)
{
@@ -937,6 +1131,31 @@ namespace Robust.Client.GameObjects
LayerSetAutoAnimated(layer, autoAnimated);
}
public void LayerSetOffset(int layer, Vector2 layerOffset)
{
if (Layers.Count <= layer)
{
Logger.ErrorS(LogCategory,
"Layer with index '{0}' does not exist, cannot set offset! Trace:\n{1}",
layer, Environment.StackTrace);
return;
}
Layers[layer].SetOffset(layerOffset);
}
public void LayerSetOffset(object layerKey, Vector2 layerOffset)
{
if (!LayerMapTryGet(layerKey, out var layer))
{
Logger.ErrorS(LogCategory, "Layer with key '{0}' does not exist, cannot set offset! Trace:\n{1}",
layerKey, Environment.StackTrace);
return;
}
LayerSetOffset(layer, layerOffset);
}
/// <inheritdoc />
public RSI.StateId LayerGetState(int layer)
{
@@ -971,10 +1190,15 @@ 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)]
@@ -1019,8 +1243,8 @@ namespace Robust.Client.GameObjects
private void RenderInternal(DrawingHandleWorld drawingHandle, Angle worldRotation, Vector2 worldPosition, Direction? overrideDirection)
{
var localMatrix = Matrix3.CreateTransform(in offset, in rotation, in scale);
var localMatrix = GetLocalMatrix();
foreach (var layer in Layers)
{
if (!layer.Visible)
@@ -1028,23 +1252,7 @@ namespace Robust.Client.GameObjects
continue;
}
var dirType = GetLayerDirectionType(layer);
int numDirs;
switch (dirType)
{
case RSI.State.DirectionType.Dir1:
numDirs = 1;
break;
case RSI.State.DirectionType.Dir4:
numDirs = 4;
break;
case RSI.State.DirectionType.Dir8:
numDirs = 8;
break;
default:
throw new ArgumentOutOfRangeException();
}
var numDirs = GetLayerDirectionCount(layer);
CalcModelMatrix(numDirs, worldRotation, worldPosition, out var modelMatrix);
Matrix3.Multiply(ref localMatrix, ref modelMatrix, out var transformMatrix);
@@ -1065,14 +1273,14 @@ namespace Robust.Client.GameObjects
var layerColor = color * layer.Color;
var position = -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter);
var position = -(Vector2) texture.Size / (2f * EyeManager.PixelsPerMeter) + layer.Offset;
var textureSize = texture.Size / (float) EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(position, textureSize);
// TODO: Implement layer-specific rotation and scale.
// Apply these directly to the box.
// Oh and when you do update Layer.LocalToLayer so content doesn't break.
// handle.Modulate changes the color
// drawingHandle.SetTransform() is set above, turning the quad into local space vertices
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
@@ -1082,8 +1290,8 @@ namespace Robust.Client.GameObjects
drawingHandle.UseShader(null);
}
}
private static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
public static Angle CalcRectWorldAngle(Angle worldAngle, int numDirections)
{
var theta = worldAngle.Theta;
var segSize = (MathF.PI*2) / (numDirections * 2);
@@ -1094,19 +1302,25 @@ namespace Robust.Client.GameObjects
return result;
}
private RSI.State.DirectionType GetLayerDirectionType(Layer layer)
public int GetLayerDirectionCount(ISpriteLayer layer)
{
if (!layer.State.IsValid)
return RSI.State.DirectionType.Dir1;
if (!layer.RsiState.IsValid)
return 1;
// Pull texture from RSI state instead.
var rsi = layer.RSI ?? BaseRSI;
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
var rsi = layer.Rsi ?? BaseRSI;
if (rsi == null || !rsi.TryGetState(layer.RsiState, out var state))
{
state = GetFallbackState(resourceCache);
}
return state.Directions;
return state.Directions switch
{
RSI.State.DirectionType.Dir1 => 1,
RSI.State.DirectionType.Dir4 => 4,
RSI.State.DirectionType.Dir8 => 8,
_ => throw new ArgumentOutOfRangeException()
};
}
private Texture GetRenderTexture(Layer layer, Angle worldRotation, Direction? overrideDirection)
@@ -1121,7 +1335,7 @@ namespace Robust.Client.GameObjects
{
state = GetFallbackState(resourceCache);
}
var layerSpecificDir = layer.EffectiveDirection(state, worldRotation, overrideDirection);
texture = state.GetFrame(layerSpecificDir, layer.AnimationFrame);
}
@@ -1130,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", false);
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();
@@ -1581,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>
@@ -1607,7 +1655,7 @@ namespace Robust.Client.GameObjects
Flip = 3,
}
private class Layer : ISpriteLayer
public class Layer : ISpriteLayer
{
[ViewVariables] private readonly SpriteComponent _parent;
@@ -1622,16 +1670,25 @@ namespace Robust.Client.GameObjects
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Scale { get; set; } = Vector2.One;
[ViewVariables(VVAccess.ReadWrite)]
public Angle Rotation { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
public bool Visible = true;
[ViewVariables(VVAccess.ReadWrite)]
public Color Color { get; set; } = Color.White;
[ViewVariables(VVAccess.ReadWrite)]
public bool AutoAnimated = true;
[ViewVariables(VVAccess.ReadWrite)]
public Vector2 Offset { get; set; }
[ViewVariables]
public DirectionOffset DirOffset { get; set; }
[ViewVariables]
public RSI? ActualRsi => RSI ?? _parent.BaseRSI;
@@ -1665,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;
@@ -1852,6 +1925,38 @@ namespace Robust.Client.GameObjects
_parent.UpdateIsInert();
}
public void SetOffset(Vector2 offset)
{
Offset = offset;
}
/// <inheritdoc/>
public Vector2i PixelSize
{
get
{
var pixelSize = Vector2i.Zero;
if (Texture != null)
{
pixelSize = Texture.Size;
}
else if (ActualRsi != null)
{
pixelSize = ActualRsi.Size;
}
return pixelSize;
}
}
/// <inheritdoc/>
public Box2 CalculateBoundingBox()
{
// TODO: scale & rotation for layers is currently unimplemented.
return Box2.CenteredAround(Offset, PixelSize / EyeManager.PixelsPerMeter);
}
}
void IAnimationProperties.SetAnimatableProperty(string name, object value)
@@ -1903,7 +2008,6 @@ namespace Robust.Client.GameObjects
}
return state;
}
}
@@ -1954,7 +2058,6 @@ namespace Robust.Client.GameObjects
if (!anyTexture)
yield return resourceCache.GetFallback<TextureResource>().Texture;
}
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
@@ -2001,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;
@@ -2010,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;
@@ -775,12 +775,10 @@ namespace Robust.Client.Graphics.Clyde
var worldTransform = transform.WorldMatrix;
var box = occluder.BoundingBox;
// So uh, angle 0 = east... Apparently...
// We account for that here so I don't go insane.
var (tlX, tlY) = worldTransform.Transform(box.BottomLeft);
var (trX, trY) = worldTransform.Transform(box.TopLeft);
var (brX, brY) = worldTransform.Transform(box.TopRight);
var (blX, blY) = worldTransform.Transform(box.BottomRight);
var (tlX, tlY) = worldTransform.Transform(box.TopLeft);
var (trX, trY) = worldTransform.Transform(box.TopRight);
var (brX, brY) = worldTransform.Transform(box.BottomRight);
var (blX, blY) = worldTransform.Transform(box.BottomLeft);
// Faces.
var faceN = new Vector4(tlX, tlY, trX, trY);

View File

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

View File

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

@@ -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,13 @@
</PropertyGroup>
<Import Project="..\MSBuild\Robust.DefineConstants.targets" />
<ItemGroup>
<PackageReference Include="DiscordRichPresence" Version="1.0.166" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" PrivateAssets="All" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="NJsonSchema" Version="10.3.8" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NJsonSchema" Version="10.3.1" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.2" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageReference Include="OpenToolkit.OpenAL" Version="4.0.0-pre9.1" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />

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)
@@ -239,7 +233,7 @@ namespace Robust.Client.UserInterface.CustomControls
// Calculate index of first prototype to render based on current scroll.
var height = MeasureButton.DesiredSize.Y + PrototypeListContainer.Separation;
var offset = -PrototypeList.Position.Y;
var offset = Math.Max(-PrototypeList.Position.Y, 0);
var startIndex = (int) Math.Floor(offset / height);
PrototypeList.ItemOffset = startIndex;
@@ -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

@@ -21,7 +21,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface
{
internal sealed class UserInterfaceManager : IDisposable, IUserInterfaceManagerInternal
internal sealed class UserInterfaceManager : IUserInterfaceManagerInternal
{
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
@@ -174,11 +174,6 @@ namespace Robust.Client.UserInterface
_initializeCommon();
}
public void Dispose()
{
RootControl?.Dispose();
}
public void Update(FrameEventArgs args)
{
RootControl.DoUpdate(args);

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

@@ -0,0 +1,18 @@
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Robust.Server.Console.Commands
{
internal sealed class ReloadLocalizationsCommand : IConsoleCommand
{
public string Command => "rldloc";
public string Description => "Reloads localization (client & server)";
public string Help => "Usage: rldloc";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
IoCManager.Resolve<ILocalizationManager>().ReloadLocalizations();
}
}
}

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

@@ -115,6 +115,7 @@ namespace Robust.Server
mgr.RootSawmill.AddHandler(handler);
mgr.GetSawmill("res.typecheck").Level = LogLevel.Info;
mgr.GetSawmill("go.sys").Level = LogLevel.Info;
mgr.GetSawmill("loc").Level = LogLevel.Error;
// mgr.GetSawmill("szr").Level = LogLevel.Info;
#if DEBUG_ONLY_FCE_INFO

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

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using Microsoft.CodeAnalysis;
@@ -76,7 +77,7 @@ namespace Robust.Server.Scripting
return;
}
if (!_conGroupController.CanViewVar(session))
if (!_conGroupController.CanScript(session))
{
Logger.WarningS("script", "Client {0} tried to access Scripting without permissions.", session);
_netManager.ServerSendMessage(reply, message.MsgChannel);
@@ -108,7 +109,7 @@ namespace Robust.Server.Scripting
return;
}
if (!_conGroupController.CanViewVar(session))
if (!_conGroupController.CanScript(session))
{
Logger.WarningS("script", "Client {0} tried to access Scripting without permissions.", session);
return;
@@ -125,23 +126,40 @@ namespace Robust.Server.Scripting
var code = message.Code;
instance.InputBuffer.AppendLine(code);
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(instance.InputBuffer.ToString()),
ScriptInstanceShared.ParseOptions);
if (!SyntaxFactory.IsCompleteSubmission(tree))
if (code == "y" && instance.AutoImportRepeatBuffer.HasValue)
{
replyMessage.WasComplete = false;
_netManager.ServerSendMessage(replyMessage, message.MsgChannel);
return;
var (imports, repeatCode) = instance.AutoImportRepeatBuffer.Value;
var sb = new StringBuilder();
foreach (var import in imports)
{
sb.AppendFormat("using {0};\n", import);
}
sb.Append(repeatCode);
code = sb.ToString();
replyMessage.WasComplete = true;
}
else
{
instance.InputBuffer.AppendLine(code);
replyMessage.WasComplete = true;
var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(instance.InputBuffer.ToString()),
ScriptInstanceShared.ParseOptions);
code = instance.InputBuffer.ToString().Trim();
if (!SyntaxFactory.IsCompleteSubmission(tree))
{
replyMessage.WasComplete = false;
_netManager.ServerSendMessage(replyMessage, message.MsgChannel);
return;
}
instance.InputBuffer.Clear();
replyMessage.WasComplete = true;
code = instance.InputBuffer.ToString().Trim();
instance.InputBuffer.Clear();
}
Script newScript;
@@ -188,6 +206,8 @@ namespace Robust.Server.Scripting
msg.AddText("\n");
}
PromptAutoImports(e.Diagnostics, code, msg, instance);
replyMessage.Response = msg;
_netManager.ServerSendMessage(replyMessage, message.MsgChannel);
return;
@@ -210,13 +230,28 @@ 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;
_netManager.ServerSendMessage(replyMessage, message.MsgChannel);
}
private void PromptAutoImports(
IEnumerable<Diagnostic> diags,
string code,
FormattedMessage output,
ScriptInstance instance)
{
if (!ScriptInstanceShared.CalcAutoImports(_reflectionManager, diags, out var found))
return;
output.AddText($"Auto-import {string.Join(", ", found)} (enter 'y')?");
instance.AutoImportRepeatBuffer = (found.ToArray(), code);
}
private sealed class ScriptInstance
{
public Workspace HighlightWorkspace { get; } = new AdhocWorkspace();
@@ -227,6 +262,8 @@ namespace Robust.Server.Scripting
public ScriptGlobals Globals { get; }
public ScriptState? State { get; set; }
public (string[] imports, string code)? AutoImportRepeatBuffer;
public ScriptInstance()
{
Globals = new ScriptGlobalsImpl(this);
@@ -253,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

@@ -43,9 +43,19 @@ namespace Robust.Shared.Maths
Theta = Math.Atan2(dir.Y, dir.X);
}
public static Angle FromWorldVec(Vector2 dir)
{
return new Angle(dir) + Math.PI / 2;
}
/// <summary>
/// Converts this angle to a unit direction vector.
/// </summary>
/// <remarks>
/// This is in "normal" cartesian trigonometry, with an angle of zero being (1, 0).
/// Use <see cref="ToWorldVec"/> for in-world calculations
/// where an angle of zero is usually considered "south" (0, -1).
/// </remarks>
/// <returns>Unit Direction Vector</returns>
public Vector2 ToVec()
{
@@ -54,6 +64,11 @@ namespace Robust.Shared.Maths
return new Vector2((float) x, (float) y);
}
public Vector2 ToWorldVec()
{
return (this - MathHelper.PiOver2).ToVec();
}
private const double Segment = 2 * Math.PI / 8.0; // Cut the circle into 8 pieces
private const double Offset = Segment / 2.0; // offset the pieces by 1/2 their size
@@ -260,7 +275,7 @@ namespace Robust.Shared.Maths
public static Angle operator +(Angle a, Angle b)
=> new(a.Theta + b.Theta);
public static Angle operator -(Angle a, Angle b)
=> new(a.Theta - b.Theta);

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

@@ -6,14 +6,14 @@ namespace Robust.Shared.Maths
public enum Direction : sbyte
{
Invalid = -1,
East = 0,
NorthEast = 1,
North = 2,
NorthWest = 3,
West = 4,
SouthWest = 5,
South = 6,
SouthEast = 7,
South = 0,
SouthEast = 1,
East = 2,
NorthEast = 3,
North = 4,
NorthWest = 5,
West = 6,
SouthWest = 7,
}
/// <summary>
@@ -30,7 +30,7 @@ namespace Robust.Shared.Maths
/// <returns></returns>
public static Direction GetDir(this Vector2 vec)
{
return vec.ToAngle().GetDir();
return Angle.FromWorldVec(vec).GetDir();
}
/// <summary>
@@ -86,14 +86,14 @@ namespace Robust.Shared.Maths
private static Vector2[] directionVectors = new[]
{
new Vector2(0, -1),
new Vector2(1, -1).Normalized,
new Vector2(1, 0),
new Vector2(1, 1).Normalized,
new Vector2(0, 1),
new Vector2(-1, 1).Normalized,
new Vector2(-1, 0),
new Vector2(-1, -1).Normalized,
new Vector2(0, -1),
new Vector2(1, -1).Normalized
new Vector2(-1, -1).Normalized
};
/// <summary>
/// Converts a Direction to a normalized Direction vector.
@@ -112,7 +112,12 @@ namespace Robust.Shared.Maths
/// <returns>Angle of the vector.</returns>
public static Angle ToAngle(this Vector2 vec)
{
return Math.Atan2(vec.Y, vec.X);
return new(vec);
}
public static Angle ToWorldAngle(this Vector2 vec)
{
return Angle.FromWorldVec(vec);
}
}
}

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