mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
167 Commits
reactjs-su
...
v0.14.7.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc32549c75 | ||
|
|
17dd3af3c7 | ||
|
|
32bdc19fe9 | ||
|
|
70fbefbe62 | ||
|
|
64cd6ea9be | ||
|
|
aa92c2444d | ||
|
|
9b19626f1b | ||
|
|
2bbe5193ee | ||
|
|
d11318f082 | ||
|
|
237f948d99 | ||
|
|
f3449be1e9 | ||
|
|
b95ffda711 | ||
|
|
0932fa0058 | ||
|
|
da2e46b81b | ||
|
|
d4a1fcc9c6 | ||
|
|
c7e7f4f28f | ||
|
|
87e191b8d7 | ||
|
|
05b21fcfcc | ||
|
|
48e4ae87d8 | ||
|
|
8b090034f1 | ||
|
|
41c183ac09 | ||
|
|
4380b95451 | ||
|
|
90fc486a20 | ||
|
|
f254153962 | ||
|
|
a2538d1905 | ||
|
|
5d7a2879a7 | ||
|
|
986adf6494 | ||
|
|
15932fb9aa | ||
|
|
3ae4bb03f5 | ||
|
|
7f47478997 | ||
|
|
a2923450ea | ||
|
|
5366b9a35b | ||
|
|
5ca37c68e2 | ||
|
|
22aa274d03 | ||
|
|
54bbe55bb9 | ||
|
|
9706576e66 | ||
|
|
b2ccd65da4 | ||
|
|
c587cd2e12 | ||
|
|
ba23a989a9 | ||
|
|
c6c69668c8 | ||
|
|
b0a4593f44 | ||
|
|
2d6e699e2c | ||
|
|
4307509402 | ||
|
|
ed165b1e4c | ||
|
|
3503300a23 | ||
|
|
3ab3d255ed | ||
|
|
fd625e0b30 | ||
|
|
53af37cd56 | ||
|
|
6bf4945181 | ||
|
|
868320c513 | ||
|
|
577b836420 | ||
|
|
4a36b52657 | ||
|
|
8dc944d22d | ||
|
|
0a10170155 | ||
|
|
dfa2c222d1 | ||
|
|
e288af162c | ||
|
|
6280820c86 | ||
|
|
102b31f83d | ||
|
|
37ca9ca19a | ||
|
|
0c151ad456 | ||
|
|
55e3f749b4 | ||
|
|
16902d7fbc | ||
|
|
c37f53e083 | ||
|
|
4cc499afdf | ||
|
|
48895efad7 | ||
|
|
eb46b04c0e | ||
|
|
442de12b99 | ||
|
|
712c1f3559 | ||
|
|
5a5cfa1eba | ||
|
|
7988386bc3 | ||
|
|
76bfe2905d | ||
|
|
23893bcacd | ||
|
|
c1da159e8f | ||
|
|
13889acb37 | ||
|
|
ffb3de3f2c | ||
|
|
87cd45fc64 | ||
|
|
1cf914813c | ||
|
|
86d61f8d03 | ||
|
|
8f638fbf9e | ||
|
|
95ac134a07 | ||
|
|
4e65f9b2a1 | ||
|
|
d60bbe9fe9 | ||
|
|
dec1495e1e | ||
|
|
dc72c6fe22 | ||
|
|
141b1205c6 | ||
|
|
65f4a09ad5 | ||
|
|
3d1545c0b9 | ||
|
|
ec26dd622b | ||
|
|
0ab3131964 | ||
|
|
588a9e9f63 | ||
|
|
f2fa930edd | ||
|
|
ec47229a37 | ||
|
|
bf5d1d58a8 | ||
|
|
8b4da24ee7 | ||
|
|
3fba108d70 | ||
|
|
35029f0eed | ||
|
|
b66ab9d7c6 | ||
|
|
5e0b745ba9 | ||
|
|
45d906ba7e | ||
|
|
24b124fb17 | ||
|
|
7cb0978468 | ||
|
|
44cb135a1d | ||
|
|
146b673203 | ||
|
|
b0d23c5665 | ||
|
|
68f89c8958 | ||
|
|
1327d6bf25 | ||
|
|
237e37ff30 | ||
|
|
4d707c86cb | ||
|
|
c7027c6e00 | ||
|
|
81ec61bcc8 | ||
|
|
649178fa54 | ||
|
|
9ff46b9aad | ||
|
|
cdcbb60ca7 | ||
|
|
d8cdb2b312 | ||
|
|
fa1c1b8e6e | ||
|
|
2ded835602 | ||
|
|
a43f04818d | ||
|
|
278dc60119 | ||
|
|
8a202be0cf | ||
|
|
58940a3cd7 | ||
|
|
aa9721d146 | ||
|
|
79f114ad5b | ||
|
|
197a6ddd9d | ||
|
|
4a4fb15e06 | ||
|
|
e7e83ce6e8 | ||
|
|
a82b293452 | ||
|
|
bb483a3a38 | ||
|
|
2ca3d0e395 | ||
|
|
bda79b1e82 | ||
|
|
4f4b754e2d | ||
|
|
214cabac43 | ||
|
|
7b6229c222 | ||
|
|
1a14a75897 | ||
|
|
76b75fd9b3 | ||
|
|
b969fd22f7 | ||
|
|
8d27d091af | ||
|
|
1eb7393a60 | ||
|
|
4cf88507c2 | ||
|
|
3565d8b321 | ||
|
|
7094c29b2e | ||
|
|
63004b270f | ||
|
|
6714a99b38 | ||
|
|
4c3b8df1e7 | ||
|
|
4bb695121f | ||
|
|
09fd47c421 | ||
|
|
d201d9c688 | ||
|
|
fd1e25c584 | ||
|
|
6bb66ae70e | ||
|
|
cc82d6b1d9 | ||
|
|
956be749b6 | ||
|
|
6585a00608 | ||
|
|
c0525f710f | ||
|
|
d3672807d2 | ||
|
|
60f18d5f36 | ||
|
|
e72d3de256 | ||
|
|
ba9846b9c4 | ||
|
|
09586284dc | ||
|
|
a1ee4374b2 | ||
|
|
4de6f25f11 | ||
|
|
582d8a5587 | ||
|
|
ec53b04f99 | ||
|
|
950fc94408 | ||
|
|
58d12e6e09 | ||
|
|
94323005c4 | ||
|
|
4989842057 | ||
|
|
80172636a8 | ||
|
|
8491f7be24 |
38
.github/workflows/benchmarks.yml
vendored
38
.github/workflows/benchmarks.yml
vendored
@@ -1,11 +1,13 @@
|
||||
name: Benchmarks
|
||||
#on:
|
||||
# push
|
||||
#schedule:
|
||||
# - cron: '0 5 * * *'
|
||||
#push:
|
||||
# tags:
|
||||
# - 'v*'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
concurrency: benchmarks
|
||||
|
||||
env:
|
||||
ROBUST_BENCHMARKS_ENABLE_SQL: 1
|
||||
@@ -20,14 +22,14 @@ jobs:
|
||||
name: Run Benchmarks
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Run benchmark
|
||||
run: cd Robust.Benchmarks && sudo dotnet run --filter '*' --configuration Release
|
||||
- name: Run script on centcomm
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
username: robust-benchmark-runner
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BENCHMARK_RUNNER_KEY }}
|
||||
command_timeout: 100000m
|
||||
script: |
|
||||
wget https://raw.githubusercontent.com/space-wizards/RobustToolbox/${{ github.sha }}/Tools/run_benchmarks.py
|
||||
python3 run_benchmarks.py "${{ secrets.BENCHMARKS_WRITE_ADDRESS }}" "${{ secrets.BENCHMARKS_WRITE_PORT }}" "${{ secrets.BENCHMARKS_WRITE_USER }}" "${{ secrets.BENCHMARKS_WRITE_PASSWORD }}" "${{ github.sha }}"
|
||||
rm run_benchmarks.py
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -10,9 +10,6 @@
|
||||
[submodule "Robust.LoaderApi"]
|
||||
path = Robust.LoaderApi
|
||||
url = https://github.com/space-wizards/Robust.LoaderApi.git
|
||||
[submodule "ManagedHttpListener"]
|
||||
path = ManagedHttpListener
|
||||
url = https://github.com/space-wizards/ManagedHttpListener.git
|
||||
[submodule "cefglue"]
|
||||
path = cefglue
|
||||
url = https://github.com/space-wizards/cefglue.git
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: b723fc532e...1e7fb3c2b6
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<PropertyGroup><Version>0.8.86</Version></PropertyGroup>
|
||||
<PropertyGroup><Version>0.14.7.0</Version></PropertyGroup>
|
||||
</Project>
|
||||
|
||||
Submodule ManagedHttpListener deleted from ae0539e66f
10
Resources/Locale/en-US/controls.ftl
Normal file
10
Resources/Locale/en-US/controls.ftl
Normal file
@@ -0,0 +1,10 @@
|
||||
color-selector-sliders-red = R
|
||||
color-selector-sliders-green = G
|
||||
color-selector-sliders-blue = B
|
||||
color-selector-sliders-hue = H
|
||||
color-selector-sliders-saturation = S
|
||||
color-selector-sliders-value = V
|
||||
color-selector-sliders-alpha = A
|
||||
|
||||
color-selector-sliders-rgb = RGB
|
||||
color-selector-sliders-hsv = HSV
|
||||
1
Resources/Locale/en-US/midi-commands.ftl
Normal file
1
Resources/Locale/en-US/midi-commands.ftl
Normal file
@@ -0,0 +1 @@
|
||||
midi-panic-command-description = Turns off every note for every active MIDI renderer.
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using BenchmarkDotNet.Exporters;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Mathematics;
|
||||
@@ -11,6 +12,9 @@ using BenchmarkDotNet.Reports;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Npgsql;
|
||||
using Npgsql.Internal;
|
||||
using Npgsql.Internal.TypeHandlers;
|
||||
using Npgsql.Internal.TypeHandling;
|
||||
|
||||
namespace Robust.Benchmarks.Exporters;
|
||||
|
||||
@@ -68,6 +72,13 @@ public sealed class SQLExporter : IExporter
|
||||
using var ctx = new BenchmarkContext(builder.Options);
|
||||
try
|
||||
{
|
||||
ctx.Database.OpenConnection();
|
||||
var con = (NpgsqlConnection) ctx.Database.GetDbConnection();
|
||||
con.TypeMapper.AddTypeResolverFactory(new JsonOverrideTypeHandlerResolverFactory(new JsonSerializerOptions
|
||||
{
|
||||
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals
|
||||
}));
|
||||
|
||||
ctx.Database.Migrate();
|
||||
ctx.BenchmarkRuns.Add(BenchmarkRun.FromSummary(summary, gitHash));
|
||||
ctx.SaveChanges();
|
||||
@@ -81,6 +92,45 @@ public sealed class SQLExporter : IExporter
|
||||
public string Name => "sql";
|
||||
}
|
||||
|
||||
// https://github.com/npgsql/efcore.pg/issues/1107#issuecomment-945126627
|
||||
class JsonOverrideTypeHandlerResolverFactory : TypeHandlerResolverFactory
|
||||
{
|
||||
private readonly JsonSerializerOptions _options;
|
||||
|
||||
public JsonOverrideTypeHandlerResolverFactory(JsonSerializerOptions options)
|
||||
=> _options = options;
|
||||
|
||||
public override TypeHandlerResolver Create(NpgsqlConnector connector)
|
||||
=> new JsonOverrideTypeHandlerResolver(connector, _options);
|
||||
|
||||
public override string? GetDataTypeNameByClrType(Type clrType)
|
||||
=> null;
|
||||
|
||||
public override TypeMappingInfo? GetMappingByDataTypeName(string dataTypeName)
|
||||
=> null;
|
||||
|
||||
class JsonOverrideTypeHandlerResolver : TypeHandlerResolver
|
||||
{
|
||||
readonly JsonHandler _jsonbHandler;
|
||||
|
||||
internal JsonOverrideTypeHandlerResolver(NpgsqlConnector connector, JsonSerializerOptions options)
|
||||
=> _jsonbHandler ??= new JsonHandler(
|
||||
connector.DatabaseInfo.GetPostgresTypeByName("jsonb"),
|
||||
connector.TextEncoding,
|
||||
isJsonb: true,
|
||||
options);
|
||||
|
||||
public override NpgsqlTypeHandler? ResolveByDataTypeName(string typeName)
|
||||
=> typeName == "jsonb" ? _jsonbHandler : null;
|
||||
|
||||
public override NpgsqlTypeHandler? ResolveByClrType(Type type)
|
||||
// You can add any user-defined CLR types which you want mapped to jsonb
|
||||
=> type == typeof(JsonDocument) ? _jsonbHandler : null;
|
||||
|
||||
public override TypeMappingInfo? GetMappingByDataTypeName(string dataTypeName)
|
||||
=> null; // Let the built-in resolver do this
|
||||
}
|
||||
}
|
||||
public sealed class DesignTimeContextFactoryPostgres : IDesignTimeDbContextFactory<BenchmarkContext>
|
||||
{
|
||||
public BenchmarkContext CreateDbContext(string[] args)
|
||||
@@ -101,12 +151,14 @@ public class BenchmarkContext : DbContext
|
||||
|
||||
public class BenchmarkRun
|
||||
{
|
||||
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public ulong Id { get; set; }
|
||||
public int Id { get; set; }
|
||||
public string GitHash { get; set; } = string.Empty;
|
||||
|
||||
[Column(TypeName = "timestamptz")]
|
||||
public DateTime RunDate { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Column(TypeName = "jsonb")]
|
||||
public BenchmarkRunReport[] Reports { get; set; } = Array.Empty<BenchmarkRunReport>();
|
||||
|
||||
|
||||
57
Robust.Benchmarks/Migrations/20220510131430_fix-pk.Designer.cs
generated
Normal file
57
Robust.Benchmarks/Migrations/20220510131430_fix-pk.Designer.cs
generated
Normal file
@@ -0,0 +1,57 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using Robust.Benchmarks.Exporters;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Robust.Benchmarks.Migrations
|
||||
{
|
||||
[DbContext(typeof(BenchmarkContext))]
|
||||
[Migration("20220510131430_fix-pk")]
|
||||
partial class fixpk
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.0")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Robust.Benchmarks.Exporters.BenchmarkRun", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("GitHash")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<BenchmarkRunReport[]>("Reports")
|
||||
.IsRequired()
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<DateTime>("RunDate")
|
||||
.HasColumnType("timestamptz");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("BenchmarkRuns");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Robust.Benchmarks/Migrations/20220510131430_fix-pk.cs
Normal file
51
Robust.Benchmarks/Migrations/20220510131430_fix-pk.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Robust.Benchmarks.Migrations
|
||||
{
|
||||
public partial class fixpk : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "RunDate",
|
||||
table: "BenchmarkRuns",
|
||||
type: "timestamptz",
|
||||
nullable: false,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "Date");
|
||||
|
||||
migrationBuilder.AlterColumn<int>(
|
||||
name: "Id",
|
||||
table: "BenchmarkRuns",
|
||||
type: "integer",
|
||||
nullable: false,
|
||||
oldClrType: typeof(decimal),
|
||||
oldType: "numeric(20,0)")
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "RunDate",
|
||||
table: "BenchmarkRuns",
|
||||
type: "Date",
|
||||
nullable: false,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "timestamptz");
|
||||
|
||||
migrationBuilder.AlterColumn<decimal>(
|
||||
name: "Id",
|
||||
table: "BenchmarkRuns",
|
||||
type: "numeric(20,0)",
|
||||
nullable: false,
|
||||
oldClrType: typeof(int),
|
||||
oldType: "integer")
|
||||
.OldAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,11 @@ namespace Robust.Benchmarks.Migrations
|
||||
|
||||
modelBuilder.Entity("Robust.Benchmarks.Exporters.BenchmarkRun", b =>
|
||||
{
|
||||
b.Property<decimal>("Id")
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("numeric(20,0)");
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("GitHash")
|
||||
.IsRequired()
|
||||
@@ -41,7 +43,7 @@ namespace Robust.Benchmarks.Migrations
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<DateTime>("RunDate")
|
||||
.HasColumnType("Date");
|
||||
.HasColumnType("timestamptz");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>../bin/Benchmarks</OutputPath>
|
||||
<OutputType>Exe</OutputType>
|
||||
<NoWarn>RA0003</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robust.Server\Robust.Server.csproj" />
|
||||
@@ -18,7 +19,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.0-rc.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.4" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\MSBuild\Robust.Engine.targets" />
|
||||
</Project>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Globalization;
|
||||
using Robust.Shared.IoC;
|
||||
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.Markdown.Value;
|
||||
@@ -19,10 +18,10 @@ namespace Robust.Benchmarks.Serialization
|
||||
: new ErrorNode(node, $"Failed parsing int value: {node.Value}");
|
||||
}
|
||||
|
||||
public DeserializationResult Read(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null)
|
||||
public int Read(ISerializationManager serializationManager, ValueDataNode node,
|
||||
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, int _ = default)
|
||||
{
|
||||
return new DeserializedValue<int>(int.Parse(node.Value, CultureInfo.InvariantCulture));
|
||||
return int.Parse(node.Value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, int value, bool alwaysWrite = false,
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Robust.Benchmarks.Serialization.Copy
|
||||
|
||||
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
|
||||
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
|
||||
Seed = SerializationManager.Read<SeedDataDefinition>(seedMapping);
|
||||
}
|
||||
|
||||
private const string String = "ABC";
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
|
||||
name: tobacco
|
||||
seedName: tobacco
|
||||
displayName: tobacco plant
|
||||
plantRsi: Objects/Specific/Hydroponics/tobacco.rsi
|
||||
productPrototypes:
|
||||
- LeavesTobacco
|
||||
harvestRepeat: Repeat
|
||||
@@ -36,7 +37,7 @@ namespace Robust.Benchmarks.Serialization.Definitions
|
||||
Max: 10
|
||||
PotencyDivisor: 10";
|
||||
|
||||
[DataField("id", required: true)] public string ID { get; set; } = default!;
|
||||
[IdDataFieldAttribute] public string ID { get; set; } = default!;
|
||||
|
||||
#region Tracking
|
||||
[DataField("name")] public string Name { get; set; } = string.Empty;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Benchmarks.Serialization.Definitions;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Serialization.Manager.Result;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
@@ -42,32 +41,32 @@ namespace Robust.Benchmarks.Serialization.Read
|
||||
private ValueDataNode FlagThirtyOne { get; } = new("ThirtyOne");
|
||||
|
||||
[Benchmark]
|
||||
public string? ReadString()
|
||||
public string ReadString()
|
||||
{
|
||||
return SerializationManager.ReadValue<string>(StringNode);
|
||||
return SerializationManager.Read<string>(StringNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int? ReadInteger()
|
||||
public int ReadInteger()
|
||||
{
|
||||
return SerializationManager.ReadValue<int>(IntNode);
|
||||
return SerializationManager.Read<int>(IntNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public DataDefinitionWithString? ReadDataDefinitionWithString()
|
||||
public DataDefinitionWithString ReadDataDefinitionWithString()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString>(StringDataDefNode);
|
||||
return SerializationManager.Read<DataDefinitionWithString>(StringDataDefNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public SeedDataDefinition? ReadSeedDataDefinition()
|
||||
public SeedDataDefinition ReadSeedDataDefinition()
|
||||
{
|
||||
return SerializationManager.ReadValue<SeedDataDefinition>(SeedNode);
|
||||
return SerializationManager.Read<SeedDataDefinition>(SeedNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DeserializationResult ReadFlagZero()
|
||||
public object? ReadFlagZero()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
@@ -77,7 +76,7 @@ namespace Robust.Benchmarks.Serialization.Read
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("flag")]
|
||||
public DeserializationResult ReadThirtyOne()
|
||||
public object? ReadThirtyOne()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
@@ -87,7 +86,7 @@ namespace Robust.Benchmarks.Serialization.Read
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("customTypeSerializer")]
|
||||
public DeserializationResult ReadIntegerCustomSerializer()
|
||||
public object? ReadIntegerCustomSerializer()
|
||||
{
|
||||
return SerializationManager.ReadWithTypeSerializer(
|
||||
typeof(int),
|
||||
|
||||
@@ -46,84 +46,84 @@ namespace Robust.Benchmarks.Serialization
|
||||
[BenchmarkCategory("read")]
|
||||
public string[]? ReadEmptyString()
|
||||
{
|
||||
return SerializationManager.ReadValue<string[]>(EmptyNode);
|
||||
return SerializationManager.Read<string[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public string[]? ReadOneString()
|
||||
{
|
||||
return SerializationManager.ReadValue<string[]>(OneIntNode);
|
||||
return SerializationManager.Read<string[]>(OneIntNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public string[]? ReadTenStrings()
|
||||
{
|
||||
return SerializationManager.ReadValue<string[]>(TenIntsNode);
|
||||
return SerializationManager.Read<string[]>(TenIntsNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public int[]? ReadEmptyInt()
|
||||
{
|
||||
return SerializationManager.ReadValue<int[]>(EmptyNode);
|
||||
return SerializationManager.Read<int[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public int[]? ReadOneInt()
|
||||
{
|
||||
return SerializationManager.ReadValue<int[]>(OneIntNode);
|
||||
return SerializationManager.Read<int[]>(OneIntNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public int[]? ReadTenInts()
|
||||
{
|
||||
return SerializationManager.ReadValue<int[]>(TenIntsNode);
|
||||
return SerializationManager.Read<int[]>(TenIntsNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public DataDefinitionWithString[]? ReadEmptyStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString[]>(EmptyNode);
|
||||
return SerializationManager.Read<DataDefinitionWithString[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public DataDefinitionWithString[]? ReadOneStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString[]>(OneStringDefNode);
|
||||
return SerializationManager.Read<DataDefinitionWithString[]>(OneStringDefNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public DataDefinitionWithString[]? ReadTenStringDataDefs()
|
||||
{
|
||||
return SerializationManager.ReadValue<DataDefinitionWithString[]>(TenStringDefsNode);
|
||||
return SerializationManager.Read<DataDefinitionWithString[]>(TenStringDefsNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public SealedDataDefinitionWithString[]? ReadEmptySealedStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(EmptyNode);
|
||||
return SerializationManager.Read<SealedDataDefinitionWithString[]>(EmptyNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public SealedDataDefinitionWithString[]? ReadOneSealedStringDataDef()
|
||||
{
|
||||
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(OneStringDefNode);
|
||||
return SerializationManager.Read<SealedDataDefinitionWithString[]>(OneStringDefNode);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
[BenchmarkCategory("read")]
|
||||
public SealedDataDefinitionWithString[]? ReadTenSealedStringDataDefs()
|
||||
{
|
||||
return SerializationManager.ReadValue<SealedDataDefinitionWithString[]>(TenStringDefsNode);
|
||||
return SerializationManager.Read<SealedDataDefinitionWithString[]>(TenStringDefsNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Robust.Benchmarks.Serialization.Write
|
||||
|
||||
var seedMapping = yamlStream.Documents[0].RootNode.ToDataNodeCast<SequenceDataNode>().Cast<MappingDataNode>(0);
|
||||
|
||||
Seed = SerializationManager.ReadValueOrThrow<SeedDataDefinition>(seedMapping);
|
||||
Seed = SerializationManager.Read<SeedDataDefinition>(seedMapping);
|
||||
}
|
||||
|
||||
private const string String = "ABC";
|
||||
|
||||
22
Robust.Client/Audio/Midi/Commands/MidiPanicCommand.cs
Normal file
22
Robust.Client/Audio/Midi/Commands/MidiPanicCommand.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Audio.Midi.Commands;
|
||||
|
||||
public sealed class MidiPanicCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IMidiManager _midiManager = default!;
|
||||
|
||||
public string Command => "midipanic";
|
||||
public string Description => Loc.GetString("midi-panic-command-description");
|
||||
public string Help => $"{Command}";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
foreach (var renderer in _midiManager.Renderers)
|
||||
{
|
||||
renderer.StopAllNotes();
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Robust.Client/Audio/Midi/IMidiManager.cs
Normal file
62
Robust.Client/Audio/Midi/IMidiManager.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Collections.Generic;
|
||||
using NFluidsynth;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
public interface IMidiManager
|
||||
{
|
||||
/// <summary>
|
||||
/// A read-only list of all existing MIDI Renderers.
|
||||
/// </summary>
|
||||
IReadOnlyList<IMidiRenderer> Renderers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, MIDI support is available.
|
||||
/// </summary>
|
||||
bool IsAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Volume, in db.
|
||||
/// </summary>
|
||||
float Volume { get; set; }
|
||||
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method tries to return a midi renderer ready to be used.
|
||||
/// You only need to set the <see cref="IMidiRenderer.MidiProgram"/> afterwards.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method can fail if MIDI support is not available.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// <c>null</c> if MIDI support is not available.
|
||||
/// </returns>
|
||||
IMidiRenderer? GetNewRenderer(bool mono = true);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="RobustMidiEvent"/> given a <see cref="MidiEvent"/> and a sequencer tick.
|
||||
/// </summary>
|
||||
RobustMidiEvent FromFluidEvent(MidiEvent midiEvent, uint tick);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="SequencerEvent"/> given a <see cref="RobustMidiEvent"/>.
|
||||
/// Be sure to dispose of the result after you've used it.
|
||||
/// </summary>
|
||||
SequencerEvent ToSequencerEvent(RobustMidiEvent midiEvent);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="RobustMidiEvent"/> given a <see cref="SequencerEvent"/> and a sequencer tick.
|
||||
/// </summary>
|
||||
RobustMidiEvent FromSequencerEvent(SequencerEvent midiEvent, uint tick);
|
||||
|
||||
/// <summary>
|
||||
/// Method called every frame.
|
||||
/// Should be used to update positional audio.
|
||||
/// </summary>
|
||||
/// <param name="frameTime"></param>
|
||||
void FrameUpdate(float frameTime);
|
||||
|
||||
void Shutdown();
|
||||
}
|
||||
174
Robust.Client/Audio/Midi/IMidiRenderer.cs
Normal file
174
Robust.Client/Audio/Midi/IMidiRenderer.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
public enum MidiRendererStatus : byte
|
||||
{
|
||||
None,
|
||||
Input,
|
||||
File,
|
||||
}
|
||||
|
||||
public interface IMidiRenderer : IDisposable
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The buffered audio source of this renderer.
|
||||
/// </summary>
|
||||
internal IClydeBufferedAudioSource Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this renderer has been disposed or not.
|
||||
/// </summary>
|
||||
bool Disposed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This controls whether the midi file being played will loop or not.
|
||||
/// </summary>
|
||||
bool LoopMidi { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This increases all note on velocities to 127.
|
||||
/// </summary>
|
||||
bool VolumeBoost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The midi program (instrument) the renderer is using.
|
||||
/// </summary>
|
||||
byte MidiProgram { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The instrument bank the renderer is using.
|
||||
/// </summary>
|
||||
byte MidiBank { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The soundfont currently selected by the renderer.
|
||||
/// </summary>
|
||||
uint MidiSoundfont { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current status of the renderer.
|
||||
/// "None" if the renderer isn't playing from input or a midi file.
|
||||
/// "Input" if the renderer is playing from midi input.
|
||||
/// "File" if the renderer is playing from a midi file.
|
||||
/// </summary>
|
||||
MidiRendererStatus Status { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the sound will play in stereo or mono.
|
||||
/// </summary>
|
||||
bool Mono { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to drop messages on the percussion channel.
|
||||
/// </summary>
|
||||
bool DisablePercussionChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to drop messages for program change events.
|
||||
/// </summary>
|
||||
bool DisableProgramChangeEvent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total number of ticks possible for the MIDI player.
|
||||
/// </summary>
|
||||
int PlayerTotalTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets (seeks) the current tick of the MIDI player.
|
||||
/// </summary>
|
||||
int PlayerTick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current tick of the sequencer.
|
||||
/// </summary>
|
||||
uint SequencerTick { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Time Scale of the sequencer in ticks per second. Default is 1000 for 1 tick per millisecond.
|
||||
/// </summary>
|
||||
double SequencerTimeScale { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Start listening for midi input.
|
||||
/// </summary>
|
||||
bool OpenInput();
|
||||
|
||||
/// <summary>
|
||||
/// Start playing a midi file.
|
||||
/// </summary>
|
||||
/// <param name="buffer">Bytes of the midi file</param>
|
||||
bool OpenMidi(ReadOnlySpan<byte> buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Stops listening for midi input.
|
||||
/// </summary>
|
||||
bool CloseInput();
|
||||
|
||||
/// <summary>
|
||||
/// Stops playing midi files.
|
||||
/// </summary>
|
||||
bool CloseMidi();
|
||||
|
||||
/// <summary>
|
||||
/// Stops all notes being played currently.
|
||||
/// </summary>
|
||||
void StopAllNotes();
|
||||
|
||||
/// <summary>
|
||||
/// Render and play MIDI to the audio source.
|
||||
/// </summary>
|
||||
internal void Render();
|
||||
|
||||
/// <summary>
|
||||
/// Loads a new soundfont into the renderer.
|
||||
/// </summary>
|
||||
void LoadSoundfont(string filename, bool resetPresets = false);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked whenever a new midi event is registered.
|
||||
/// </summary>
|
||||
event Action<RobustMidiEvent> OnMidiEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the midi player finishes playing a song.
|
||||
/// </summary>
|
||||
event Action OnMidiPlayerFinished;
|
||||
|
||||
/// <summary>
|
||||
/// The entity whose position will be used for positional audio.
|
||||
/// This is only used if <see cref="Mono"/> is set to True.
|
||||
/// </summary>
|
||||
EntityUid? TrackingEntity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The position that will be used for positional audio.
|
||||
/// This is only used if <see cref="Mono"/> is set to True
|
||||
/// and <see cref="TrackingEntity"/> is null.
|
||||
/// </summary>
|
||||
EntityCoordinates? TrackingCoordinates { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Send a midi event for the renderer to play.
|
||||
/// </summary>
|
||||
/// <param name="midiEvent">The midi event to be played</param>
|
||||
void SendMidiEvent(RobustMidiEvent midiEvent);
|
||||
|
||||
/// <summary>
|
||||
/// Schedule a MIDI event to be played at a later time.
|
||||
/// </summary>
|
||||
/// <param name="midiEvent">the midi event in question</param>
|
||||
/// <param name="time"></param>
|
||||
/// <param name="absolute"></param>
|
||||
void ScheduleMidiEvent(RobustMidiEvent midiEvent, uint time, bool absolute);
|
||||
|
||||
/// <summary>
|
||||
/// Actually disposes of this renderer. Do NOT use outside the MIDI thread.
|
||||
/// </summary>
|
||||
internal void InternalDispose();
|
||||
}
|
||||
155
Robust.Client/Audio/Midi/MidiManager.Conversion.cs
Normal file
155
Robust.Client/Audio/Midi/MidiManager.Conversion.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using NFluidsynth;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
internal sealed partial class MidiManager
|
||||
{
|
||||
public RobustMidiEvent FromFluidEvent(MidiEvent midiEvent, uint tick)
|
||||
{
|
||||
var status = RobustMidiEvent.MakeStatus((byte) midiEvent.Channel, (byte) midiEvent.Type);
|
||||
|
||||
// Control is always the first data byte. Value is always the second data byte. Fluidsynth's API ain't great.
|
||||
var data1 = (byte) midiEvent.Control;
|
||||
var data2 = (byte) midiEvent.Value;
|
||||
|
||||
// PitchBend is handled specially.
|
||||
if (midiEvent.Type == (int) RobustMidiCommand.PitchBend)
|
||||
{
|
||||
// We pack pitch into both data values.
|
||||
var pitch = (ushort) midiEvent.Pitch;
|
||||
data1 = (byte) pitch;
|
||||
data2 = (byte) (pitch >> 8);
|
||||
}
|
||||
|
||||
return new RobustMidiEvent(status, data1, data2, tick);
|
||||
}
|
||||
|
||||
public SequencerEvent ToSequencerEvent(RobustMidiEvent midiEvent)
|
||||
{
|
||||
var sequencerEvent = new SequencerEvent();
|
||||
|
||||
switch (midiEvent.MidiCommand)
|
||||
{
|
||||
case RobustMidiCommand.NoteOff:
|
||||
sequencerEvent.NoteOff(midiEvent.Channel, midiEvent.Key);
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.NoteOn:
|
||||
sequencerEvent.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.AfterTouch:
|
||||
sequencerEvent.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.ControlChange:
|
||||
sequencerEvent.ControlChange(midiEvent.Channel, midiEvent.Control, midiEvent.Value);
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.ProgramChange:
|
||||
sequencerEvent.ProgramChange(midiEvent.Channel, midiEvent.Program);
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.ChannelPressure:
|
||||
sequencerEvent.ChannelPressure(midiEvent.Channel, midiEvent.Value);
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.PitchBend:
|
||||
sequencerEvent.PitchBend(midiEvent.Channel, midiEvent.Pitch);
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.SystemMessage:
|
||||
switch (midiEvent.Control)
|
||||
{
|
||||
case 0x0 when midiEvent.Status == 0xFF:
|
||||
sequencerEvent.SystemReset();
|
||||
break;
|
||||
|
||||
case 0x0B:
|
||||
sequencerEvent.AllNotesOff(midiEvent.Channel);
|
||||
break;
|
||||
|
||||
default:
|
||||
_midiSawmill.Warning($"Tried to convert unsupported event to sequencer event:\n{midiEvent}");
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
_midiSawmill.Warning($"Tried to convert unsupported event to sequencer event:\n{midiEvent}");
|
||||
break;
|
||||
}
|
||||
|
||||
return sequencerEvent;
|
||||
}
|
||||
|
||||
public RobustMidiEvent FromSequencerEvent(SequencerEvent midiEvent, uint tick)
|
||||
{
|
||||
byte channel = (byte) midiEvent.Channel;
|
||||
RobustMidiCommand command = 0x0;
|
||||
byte data1 = 0;
|
||||
byte data2 = 0;
|
||||
|
||||
switch (midiEvent.Type)
|
||||
{
|
||||
case FluidSequencerEventType.NoteOn:
|
||||
command = RobustMidiCommand.NoteOn;
|
||||
data1 = (byte) midiEvent.Key;
|
||||
data2 = (byte) midiEvent.Velocity;
|
||||
break;
|
||||
|
||||
case FluidSequencerEventType.NoteOff:
|
||||
command = RobustMidiCommand.NoteOff;
|
||||
data1 = (byte) midiEvent.Key;
|
||||
break;
|
||||
|
||||
case FluidSequencerEventType.PitchBend:
|
||||
command = RobustMidiCommand.PitchBend;
|
||||
// We pack pitch into both data values
|
||||
var pitch = (ushort) midiEvent.Pitch;
|
||||
data1 = (byte) pitch;
|
||||
data2 = (byte) (pitch >> 8);
|
||||
break;
|
||||
|
||||
case FluidSequencerEventType.ProgramChange:
|
||||
command = RobustMidiCommand.ProgramChange;
|
||||
data1 = (byte) midiEvent.Program;
|
||||
break;
|
||||
|
||||
case FluidSequencerEventType.KeyPressure:
|
||||
command = RobustMidiCommand.AfterTouch;
|
||||
data1 = (byte) midiEvent.Key;
|
||||
data2 = (byte) midiEvent.Value;
|
||||
break;
|
||||
|
||||
case FluidSequencerEventType.ControlChange:
|
||||
command = RobustMidiCommand.ControlChange;
|
||||
data1 = (byte) midiEvent.Control;
|
||||
data2 = (byte) midiEvent.Value;
|
||||
break;
|
||||
|
||||
case FluidSequencerEventType.ChannelPressure:
|
||||
command = RobustMidiCommand.ChannelPressure;
|
||||
data1 = (byte) midiEvent.Value;
|
||||
break;
|
||||
|
||||
case FluidSequencerEventType.AllNotesOff:
|
||||
command = RobustMidiCommand.SystemMessage;
|
||||
data1 = 0x0B;
|
||||
break;
|
||||
|
||||
case FluidSequencerEventType.SystemReset:
|
||||
command = RobustMidiCommand.SystemMessage;
|
||||
channel = 0x0F;
|
||||
break;
|
||||
|
||||
default:
|
||||
_midiSawmill.Error($"Unsupported Sequencer Event: {tick:D8}: {SequencerEventToString(midiEvent)}");
|
||||
break;
|
||||
}
|
||||
|
||||
return new RobustMidiEvent(RobustMidiEvent.MakeStatus(channel, (byte)command), data1, data2, tick);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -15,404 +16,435 @@ 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 Robust.Shared.ViewVariables;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Robust.Client.Audio.Midi
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
public interface IMidiManager
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
[Dependency] private readonly ILogManager _logger = default!;
|
||||
|
||||
private SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
|
||||
public IReadOnlyList<IMidiRenderer> Renderers
|
||||
{
|
||||
/// <summary>
|
||||
/// This method tries to return a midi renderer ready to be used.
|
||||
/// You only need to set the <see cref="IMidiRenderer.MidiProgram"/> afterwards.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method can fail if MIDI support is not available.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// <c>null</c> if MIDI support is not available.
|
||||
/// </returns>
|
||||
IMidiRenderer? GetNewRenderer();
|
||||
|
||||
/// <summary>
|
||||
/// Method called every frame.
|
||||
/// Should be used to update positional audio.
|
||||
/// </summary>
|
||||
/// <param name="frameTime"></param>
|
||||
void FrameUpdate(float frameTime);
|
||||
|
||||
/// <summary>
|
||||
/// Volume, in db.
|
||||
/// </summary>
|
||||
float Volume { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If true, MIDI support is available.
|
||||
/// </summary>
|
||||
bool IsAvailable { get; }
|
||||
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
void Shutdown();
|
||||
get
|
||||
{
|
||||
lock (_renderers)
|
||||
{
|
||||
// Perform a copy. Sadly, we can't return a reference to the original list due to threading concerns.
|
||||
return _renderers.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MidiManager : IMidiManager
|
||||
[ViewVariables]
|
||||
public bool IsAvailable
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
|
||||
private SharedPhysicsSystem _broadPhaseSystem = default!;
|
||||
|
||||
[ViewVariables]
|
||||
public bool IsAvailable
|
||||
get
|
||||
{
|
||||
get
|
||||
{
|
||||
InitializeFluidsynth();
|
||||
InitializeFluidsynth();
|
||||
|
||||
return FluidsynthInitialized;
|
||||
}
|
||||
return FluidsynthInitialized;
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
private bool _alive = true;
|
||||
private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
// Not reliable until Fluidsynth is initialized!
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseToPercent(_volume, value))
|
||||
return;
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string[] LinuxSoundfonts =
|
||||
{
|
||||
"/usr/share/soundfonts/default.sf2",
|
||||
"/usr/share/soundfonts/FluidR3_GM.sf2",
|
||||
"/usr/share/soundfonts/freepats-general-midi.sf2",
|
||||
"/usr/share/sounds/sf2/default.sf2",
|
||||
"/usr/share/sounds/sf2/FluidR3_GM.sf2",
|
||||
"/usr/share/sounds/sf2/TimGM6mb.sf2",
|
||||
};
|
||||
|
||||
private const string WindowsSoundfont = @"C:\WINDOWS\system32\drivers\gm.dls";
|
||||
|
||||
private const string OsxSoundfont =
|
||||
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
|
||||
|
||||
private const string FallbackSoundfont = "/Midi/fallback.sf2";
|
||||
|
||||
private static ResourcePath CustomSoundfontDirectory = new ResourcePath("/soundfonts/");
|
||||
|
||||
private readonly ResourceLoaderCallbacks _soundfontLoaderCallbacks = new();
|
||||
|
||||
private bool FluidsynthInitialized;
|
||||
private bool _failedInitialize;
|
||||
|
||||
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
private void InitializeFluidsynth()
|
||||
{
|
||||
if (FluidsynthInitialized || _failedInitialize) return;
|
||||
|
||||
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
_midiSawmill = _logger.GetSawmill("midi");
|
||||
_midiSawmill.Level = LogLevel.Info;
|
||||
_sawmill = _logger.GetSawmill("midi.fluidsynth");
|
||||
_loggerDelegate = LoggerDelegate;
|
||||
|
||||
if (!_resourceManager.UserData.Exists(CustomSoundfontDirectory))
|
||||
{
|
||||
_resourceManager.UserData.CreateDir(CustomSoundfontDirectory);
|
||||
}
|
||||
// not a directory, preserve the old file and create an actual directory
|
||||
else if (!_resourceManager.UserData.IsDir(CustomSoundfontDirectory))
|
||||
{
|
||||
_resourceManager.UserData.Rename(CustomSoundfontDirectory, CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old"));
|
||||
_resourceManager.UserData.CreateDir(CustomSoundfontDirectory);
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
private bool _alive = true;
|
||||
private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
// Not reliable until Fluidsynth is initialized!
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
try
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseToPercent(_volume, value))
|
||||
return;
|
||||
NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_volumeDirty = true;
|
||||
}
|
||||
_settings = new Settings();
|
||||
_settings["synth.sample-rate"].DoubleValue = 44100;
|
||||
_settings["player.timing-source"].StringValue = "sample";
|
||||
_settings["synth.lock-memory"].IntValue = 0;
|
||||
_settings["synth.threadsafe-api"].IntValue = 1;
|
||||
_settings["synth.gain"].DoubleValue = 1.0d;
|
||||
_settings["synth.polyphony"].IntValue = 1024;
|
||||
_settings["synth.cpu-cores"].IntValue = 2;
|
||||
_settings["synth.midi-channels"].IntValue = 16;
|
||||
_settings["synth.overflow.age"].DoubleValue = 3000;
|
||||
_settings["audio.driver"].StringValue = "file";
|
||||
_settings["audio.periods"].IntValue = 8;
|
||||
_settings["audio.period-size"].IntValue = 4096;
|
||||
_settings["midi.autoconnect"].IntValue = 1;
|
||||
_settings["player.reset-synth"].IntValue = 0;
|
||||
_settings["synth.midi-bank-select"].StringValue = "gm";
|
||||
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_midiSawmill.Warning(
|
||||
"Failed to initialize fluidsynth due to exception, disabling MIDI support:\n{0}", e);
|
||||
_failedInitialize = true;
|
||||
return;
|
||||
}
|
||||
|
||||
private static readonly string[] LinuxSoundfonts =
|
||||
{
|
||||
"/usr/share/soundfonts/default.sf2",
|
||||
"/usr/share/soundfonts/FluidR3_GM.sf2",
|
||||
"/usr/share/soundfonts/freepats-general-midi.sf2",
|
||||
"/usr/share/sounds/sf2/default.sf2",
|
||||
"/usr/share/sounds/sf2/FluidR3_GM.sf2",
|
||||
"/usr/share/sounds/sf2/TimGM6mb.sf2",
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
|
||||
{
|
||||
var rLevel = level switch {
|
||||
NFluidsynth.Logger.LogLevel.Panic => LogLevel.Error,
|
||||
NFluidsynth.Logger.LogLevel.Error => LogLevel.Error,
|
||||
NFluidsynth.Logger.LogLevel.Warning => LogLevel.Warning,
|
||||
NFluidsynth.Logger.LogLevel.Information => LogLevel.Info,
|
||||
NFluidsynth.Logger.LogLevel.Debug => LogLevel.Debug,
|
||||
_ => LogLevel.Debug
|
||||
};
|
||||
_sawmill.Log(rLevel, message);
|
||||
}
|
||||
|
||||
private const string WindowsSoundfont = @"C:\WINDOWS\system32\drivers\gm.dls";
|
||||
|
||||
private const string OsxSoundfont =
|
||||
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
|
||||
|
||||
private const string FallbackSoundfont = "/Midi/fallback.sf2";
|
||||
|
||||
private readonly ResourceLoaderCallbacks _soundfontLoaderCallbacks = new();
|
||||
|
||||
private bool FluidsynthInitialized;
|
||||
private bool _failedInitialize;
|
||||
|
||||
private NFluidsynth.Logger.LoggerDelegate _loggerDelegate = default!;
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public int OcclusionCollisionMask { get; set; }
|
||||
|
||||
private void InitializeFluidsynth()
|
||||
public IMidiRenderer? GetNewRenderer(bool mono = true)
|
||||
{
|
||||
if (!FluidsynthInitialized)
|
||||
{
|
||||
if (FluidsynthInitialized || _failedInitialize) return;
|
||||
InitializeFluidsynth();
|
||||
|
||||
_volume = _cfgMan.GetCVar(CVars.MidiVolume);
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
if (!FluidsynthInitialized) // init failed
|
||||
{
|
||||
_volume = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
_midiSawmill = Logger.GetSawmill("midi");
|
||||
_sawmill = Logger.GetSawmill("midi.fluidsynth");
|
||||
_loggerDelegate = LoggerDelegate;
|
||||
|
||||
try
|
||||
{
|
||||
NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
|
||||
|
||||
_settings = new Settings();
|
||||
_settings["synth.sample-rate"].DoubleValue = 44100;
|
||||
_settings["player.timing-source"].StringValue = "sample";
|
||||
_settings["synth.lock-memory"].IntValue = 0;
|
||||
_settings["synth.threadsafe-api"].IntValue = 1;
|
||||
_settings["synth.gain"].DoubleValue = 1.0d;
|
||||
_settings["synth.polyphony"].IntValue = 1024;
|
||||
_settings["synth.cpu-cores"].IntValue = 2;
|
||||
_settings["synth.overflow.age"].DoubleValue = 3000;
|
||||
_settings["audio.driver"].StringValue = "file";
|
||||
_settings["audio.periods"].IntValue = 8;
|
||||
_settings["audio.period-size"].IntValue = 4096;
|
||||
_settings["midi.autoconnect"].IntValue = 1;
|
||||
_settings["player.reset-synth"].IntValue = 0;
|
||||
_settings["synth.midi-bank-select"].StringValue = "gm";
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.WarningS("midi",
|
||||
"Failed to initialize fluidsynth due to exception, disabling MIDI support:\n{0}", e);
|
||||
_failedInitialize = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_midiThread = new Thread(ThreadUpdate);
|
||||
_midiThread.Start();
|
||||
|
||||
_broadPhaseSystem = EntitySystem.Get<SharedPhysicsSystem>();
|
||||
FluidsynthInitialized = true;
|
||||
}
|
||||
|
||||
private void LoggerDelegate(NFluidsynth.Logger.LogLevel level, string message, IntPtr data)
|
||||
{
|
||||
var rLevel = level switch {
|
||||
NFluidsynth.Logger.LogLevel.Panic => LogLevel.Error,
|
||||
NFluidsynth.Logger.LogLevel.Error => LogLevel.Error,
|
||||
NFluidsynth.Logger.LogLevel.Warning => LogLevel.Warning,
|
||||
NFluidsynth.Logger.LogLevel.Information => LogLevel.Info,
|
||||
NFluidsynth.Logger.LogLevel.Debug => LogLevel.Debug,
|
||||
_ => LogLevel.Debug
|
||||
};
|
||||
_sawmill.Log(rLevel, message);
|
||||
}
|
||||
|
||||
public IMidiRenderer? GetNewRenderer()
|
||||
{
|
||||
if (!FluidsynthInitialized)
|
||||
{
|
||||
InitializeFluidsynth();
|
||||
|
||||
if (!FluidsynthInitialized) // init failed
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
var soundfontLoader = SoundFontLoader.NewDefaultSoundFontLoader(_settings);
|
||||
|
||||
// Just making double sure these don't get GC'd.
|
||||
// They shouldn't, MidiRenderer keeps a ref, but making sure...
|
||||
var handle = GCHandle.Alloc(soundfontLoader);
|
||||
|
||||
try
|
||||
{
|
||||
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
|
||||
|
||||
var renderer = new MidiRenderer(_settings!, soundfontLoader);
|
||||
|
||||
foreach (var file in _resourceManager.ContentFindFiles(("/Audio/MidiCustom/")))
|
||||
{
|
||||
if (file.Extension != "sf2" && file.Extension != "dls") continue;
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
renderer.LoadSoundfont(FallbackSoundfont);
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
foreach (var filepath in LinuxSoundfonts)
|
||||
{
|
||||
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath)) continue;
|
||||
|
||||
try
|
||||
{
|
||||
renderer.LoadSoundfont(filepath, true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
|
||||
renderer.LoadSoundfont(OsxSoundfont, true);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
|
||||
renderer.LoadSoundfont(WindowsSoundfont, true);
|
||||
}
|
||||
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void FrameUpdate(float frameTime)
|
||||
var soundfontLoader = SoundFontLoader.NewDefaultSoundFontLoader(_settings);
|
||||
|
||||
// Just making double sure these don't get GC'd.
|
||||
// They shouldn't, MidiRenderer keeps a ref, but making sure...
|
||||
var handle = GCHandle.Alloc(soundfontLoader);
|
||||
|
||||
try
|
||||
{
|
||||
if (!FluidsynthInitialized)
|
||||
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
|
||||
|
||||
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _clydeAudio, _taskManager, _midiSawmill);
|
||||
|
||||
foreach (var file in _resourceManager.ContentFindFiles(("/Audio/MidiCustom/")))
|
||||
{
|
||||
return;
|
||||
if (file.Extension != "sf2" && file.Extension != "dls") continue;
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
// Update positions of streams every frame.
|
||||
foreach (var renderer in _renderers)
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
renderer.LoadSoundfont(FallbackSoundfont);
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
continue;
|
||||
|
||||
if(_volumeDirty)
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
if (!renderer.Mono)
|
||||
foreach (var filepath in LinuxSoundfonts)
|
||||
{
|
||||
renderer.Source.SetGlobal();
|
||||
continue;
|
||||
}
|
||||
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath)) continue;
|
||||
|
||||
MapCoordinates? mapPos = null;
|
||||
var trackingEntity = renderer.TrackingEntity != null && !_entityManager.Deleted(renderer.TrackingEntity);
|
||||
if (trackingEntity)
|
||||
{
|
||||
renderer.TrackingCoordinates = _entityManager.GetComponent<TransformComponent>(renderer.TrackingEntity!.Value).Coordinates;
|
||||
}
|
||||
|
||||
if (renderer.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
|
||||
if (mapPos != null && mapPos.Value.MapId == _eyeManager.CurrentMap)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
|
||||
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
try
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
sourceRelative.Normalized,
|
||||
OcclusionCollisionMask),
|
||||
sourceRelative.Length,
|
||||
renderer.TrackingEntity);
|
||||
renderer.LoadSoundfont(filepath, true);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
|
||||
if (!renderer.Source.SetPosition(pos.Position))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackingEntity)
|
||||
{
|
||||
renderer.Source.SetVelocity(renderer.TrackingEntity!.Value.GlobalLinearVelocity());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_volumeDirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main method for the thread rendering the midi audio.
|
||||
/// </summary>
|
||||
private void ThreadUpdate()
|
||||
{
|
||||
while (_alive)
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
lock (_renderers)
|
||||
{
|
||||
for (var i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
if (!renderer.Disposed)
|
||||
renderer.Render();
|
||||
else
|
||||
{
|
||||
renderer.InternalDispose();
|
||||
_renderers.Remove(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Thread.Sleep(1);
|
||||
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
|
||||
renderer.LoadSoundfont(OsxSoundfont, true);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
|
||||
renderer.LoadSoundfont(WindowsSoundfont, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
_alive = false;
|
||||
_midiThread?.Join();
|
||||
_settings?.Dispose();
|
||||
// load every soundfont from the user data directory
|
||||
_midiSawmill.Debug($"loading soundfonts from {CustomSoundfontDirectory.ToRelativePath().ToString()}/*");
|
||||
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath().ToString()}/*").Item1;
|
||||
foreach (var soundfont in enumerator)
|
||||
{
|
||||
if (soundfont.Extension != "sf2" && soundfont.Extension != "dls") continue;
|
||||
_midiSawmill.Debug($"loading soundfont {soundfont}");
|
||||
renderer.LoadSoundfont(soundfont.ToString());
|
||||
}
|
||||
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
renderer?.Dispose();
|
||||
}
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
return renderer;
|
||||
}
|
||||
finally
|
||||
{
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
public void FrameUpdate(float frameTime)
|
||||
{
|
||||
if (!FluidsynthInitialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update positions of streams every frame.
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
if (renderer.Disposed)
|
||||
continue;
|
||||
|
||||
if(_volumeDirty)
|
||||
renderer.Source.SetVolume(Volume);
|
||||
|
||||
if (!renderer.Mono)
|
||||
{
|
||||
renderer.Source.SetGlobal();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (FluidsynthInitialized && !_failedInitialize)
|
||||
MapCoordinates? mapPos = null;
|
||||
var trackingEntity = renderer.TrackingEntity != null && !_entityManager.Deleted(renderer.TrackingEntity);
|
||||
if (trackingEntity)
|
||||
{
|
||||
NFluidsynth.Logger.SetLoggerMethod(null);
|
||||
renderer.TrackingCoordinates = _entityManager.GetComponent<TransformComponent>(renderer.TrackingEntity!.Value).Coordinates;
|
||||
}
|
||||
|
||||
if (renderer.TrackingCoordinates != null)
|
||||
{
|
||||
mapPos = renderer.TrackingCoordinates.Value.ToMap(_entityManager);
|
||||
}
|
||||
|
||||
if (mapPos != null && mapPos.Value.MapId == _eyeManager.CurrentMap)
|
||||
{
|
||||
var pos = mapPos.Value;
|
||||
|
||||
var sourceRelative = _eyeManager.CurrentEye.Position.Position - pos.Position;
|
||||
var occlusion = 0f;
|
||||
if (sourceRelative.Length > 0)
|
||||
{
|
||||
occlusion = _broadPhaseSystem.IntersectRayPenetration(
|
||||
pos.MapId,
|
||||
new CollisionRay(
|
||||
pos.Position,
|
||||
sourceRelative.Normalized,
|
||||
OcclusionCollisionMask),
|
||||
sourceRelative.Length,
|
||||
renderer.TrackingEntity);
|
||||
}
|
||||
|
||||
renderer.Source.SetOcclusion(occlusion);
|
||||
|
||||
if (!renderer.Source.SetPosition(pos.Position))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackingEntity)
|
||||
{
|
||||
renderer.Source.SetVelocity(renderer.TrackingEntity!.Value.GlobalLinearVelocity());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.Source.SetOcclusion(float.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is used to load soundfonts.
|
||||
/// </summary>
|
||||
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
|
||||
_volumeDirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main method for the thread rendering the midi audio.
|
||||
/// </summary>
|
||||
private void ThreadUpdate()
|
||||
{
|
||||
while (_alive)
|
||||
{
|
||||
private readonly Dictionary<int, Stream> _openStreams = new();
|
||||
private int _nextStreamId = 1;
|
||||
|
||||
public override IntPtr Open(string filename)
|
||||
lock (_renderers)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
for (var i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
var renderer = _renderers[i];
|
||||
if (!renderer.Disposed)
|
||||
renderer.Render();
|
||||
else
|
||||
{
|
||||
renderer.InternalDispose();
|
||||
_renderers.Remove(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Stream? stream;
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var resourcePath = new ResourcePath(filename);
|
||||
Thread.Sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (resourcePath.IsRooted && resourceCache.ContentFileExists(filename))
|
||||
public void Shutdown()
|
||||
{
|
||||
_alive = false;
|
||||
_midiThread?.Join();
|
||||
_settings?.Dispose();
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
foreach (var renderer in _renderers)
|
||||
{
|
||||
renderer?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (FluidsynthInitialized && !_failedInitialize)
|
||||
{
|
||||
NFluidsynth.Logger.SetLoggerMethod(null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal method to get a human-readable representation of a <see cref="SequencerEvent"/>.
|
||||
/// </summary>
|
||||
internal static string SequencerEventToString(SequencerEvent midiEvent)
|
||||
{
|
||||
// ReSharper disable once UseStringInterpolation
|
||||
return string.Format(
|
||||
"{0} chan:{1:D2} key:{2:D5} bank:{3:D2} ctrl:{4:D5} dur:{5:D5} pitch:{6:D5} prog:{7:D3} val:{8:D5} vel:{9:D5}",
|
||||
midiEvent.Type.ToString().PadLeft(22),
|
||||
midiEvent.Channel,
|
||||
midiEvent.Key,
|
||||
midiEvent.Bank,
|
||||
midiEvent.Control,
|
||||
midiEvent.Duration,
|
||||
midiEvent.Pitch,
|
||||
midiEvent.Program,
|
||||
midiEvent.Value,
|
||||
midiEvent.Velocity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is used to load soundfonts.
|
||||
/// </summary>
|
||||
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
|
||||
{
|
||||
private readonly Dictionary<int, Stream> _openStreams = new();
|
||||
private int _nextStreamId = 1;
|
||||
|
||||
public override IntPtr Open(string filename)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
Stream? stream;
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var resourcePath = new ResourcePath(filename);
|
||||
|
||||
if (resourcePath.IsRooted)
|
||||
{
|
||||
// is it in content?
|
||||
if (resourceCache.ContentFileExists(filename))
|
||||
{
|
||||
if (!resourceCache.TryContentFileRead(filename, out stream))
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
// is it in userdata?
|
||||
else if (resourceCache.UserData.Exists(resourcePath))
|
||||
{
|
||||
stream = resourceCache.UserData.OpenRead(resourcePath);
|
||||
}
|
||||
else if (File.Exists(filename))
|
||||
{
|
||||
stream = File.OpenRead(filename);
|
||||
@@ -421,73 +453,81 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
var id = _nextStreamId++;
|
||||
|
||||
_openStreams.Add(id, stream);
|
||||
|
||||
return (IntPtr) id;
|
||||
}
|
||||
else if (File.Exists(filename))
|
||||
{
|
||||
stream = File.OpenRead(filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
|
||||
{
|
||||
var length = (int) count;
|
||||
var span = new Span<byte>(buf.ToPointer(), length);
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
var id = _nextStreamId++;
|
||||
|
||||
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
|
||||
try
|
||||
_openStreams.Add(id, stream);
|
||||
|
||||
return (IntPtr) id;
|
||||
}
|
||||
|
||||
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
|
||||
{
|
||||
var length = (int) count;
|
||||
var span = new Span<byte>(buf.ToPointer(), length);
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
|
||||
try
|
||||
{
|
||||
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
|
||||
if (count < 1024)
|
||||
{
|
||||
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
|
||||
if (count < 1024)
|
||||
{
|
||||
// ReSharper disable once SuggestVarOrType_Elsewhere
|
||||
Span<byte> buffer = stackalloc byte[(int)count];
|
||||
// ReSharper disable once SuggestVarOrType_Elsewhere
|
||||
Span<byte> buffer = stackalloc byte[(int)count];
|
||||
|
||||
stream.ReadExact(buffer);
|
||||
stream.ReadExact(buffer);
|
||||
|
||||
buffer.CopyTo(span);
|
||||
}
|
||||
else
|
||||
{
|
||||
var buffer = stream.ReadExact(length);
|
||||
|
||||
buffer.CopyTo(span);
|
||||
}
|
||||
buffer.CopyTo(span);
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
var buffer = stream.ReadExact(length);
|
||||
|
||||
buffer.CopyTo(span);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override int Seek(IntPtr sfHandle, int offset, SeekOrigin origin)
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
stream.Seek(offset, origin);
|
||||
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public override int Tell(IntPtr sfHandle)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (int) stream.Position;
|
||||
}
|
||||
public override int Seek(IntPtr sfHandle, int offset, SeekOrigin origin)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
public override int Close(IntPtr sfHandle)
|
||||
{
|
||||
if (!_openStreams.Remove((int) sfHandle, out var stream))
|
||||
return -1;
|
||||
stream.Seek(offset, origin);
|
||||
|
||||
stream.Dispose();
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override int Tell(IntPtr sfHandle)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
return (int) stream.Position;
|
||||
}
|
||||
|
||||
public override int Close(IntPtr sfHandle)
|
||||
{
|
||||
if (!_openStreams.Remove((int) sfHandle, out var stream))
|
||||
return -1;
|
||||
|
||||
stream.Dispose();
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -74,7 +74,7 @@ namespace Robust.Client
|
||||
{
|
||||
if (RunLevel == ClientRunLevel.Connecting)
|
||||
{
|
||||
_net.Shutdown("Client mashing that connect button.");
|
||||
_net.Reset("Client mashing that connect button.");
|
||||
Reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Robust.Client.Console
|
||||
if (!NetManager.IsConnected) // we don't care about session on client
|
||||
return;
|
||||
|
||||
var msg = NetManager.CreateNetMessage<MsgConCmd>();
|
||||
var msg = new MsgConCmd();
|
||||
msg.Text = command;
|
||||
NetManager.ClientSendMessage(msg);
|
||||
}
|
||||
@@ -198,7 +198,7 @@ namespace Robust.Client.Console
|
||||
if (!NetManager.IsConnected)
|
||||
return;
|
||||
|
||||
var msg = NetManager.CreateNetMessage<MsgConCmdReg>();
|
||||
var msg = new MsgConCmdReg();
|
||||
NetManager.ClientSendMessage(msg);
|
||||
|
||||
_requestedCommands = true;
|
||||
|
||||
@@ -8,12 +8,12 @@ namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public string Command => "physics";
|
||||
public string Description => $"Shows a debug physics overlay. The arg supplied specifies the overlay.";
|
||||
public string Help => $"{Command} <aabbs / contactnormals / contactpoints / joints / shapeinfo / shapes>";
|
||||
public string Help => $"{Command} <aabbs / com / contactnormals / contactpoints / joints / shapeinfo / shapes>";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteLine($"Invalid number of args supplied");
|
||||
shell.WriteError($"Invalid number of args supplied");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Robust.Client.Console.Commands
|
||||
system.Flags ^= PhysicsDebugFlags.Shapes;
|
||||
break;
|
||||
default:
|
||||
shell.WriteLine($"{args[0]} is not a recognised overlay");
|
||||
shell.WriteError($"{args[0]} is not a recognised overlay");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Client.Console.Commands
|
||||
// MsgStringTableEntries is registered as NetMessageAccept.Client so the server will immediately deny it.
|
||||
// And kick us.
|
||||
var net = IoCManager.Resolve<IClientNetManager>();
|
||||
var msg = net.CreateNetMessage<MsgStringTableEntries>();
|
||||
var msg = new MsgStringTableEntries();
|
||||
msg.Entries = new MsgStringTableEntries.Entry[0];
|
||||
net.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Robust.Client.Console
|
||||
|
||||
RunButton.Disabled = true;
|
||||
|
||||
var msg = _client._netManager.CreateNetMessage<MsgScriptEval>();
|
||||
var msg = new MsgScriptEval();
|
||||
msg.ScriptSession = _session;
|
||||
msg.Code = _lastEnteredText = InputBar.Text;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Robust.Client.Console
|
||||
|
||||
protected override void Complete()
|
||||
{
|
||||
var msg = _client._netManager.CreateNetMessage<MsgScriptCompletion>();
|
||||
var msg = new MsgScriptCompletion();
|
||||
msg.ScriptSession = _session;
|
||||
msg.Code = InputBar.Text;
|
||||
msg.Cursor = InputBar.CursorPosition;
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Client.Console
|
||||
throw new InvalidOperationException("We do not have scripting permission.");
|
||||
}
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgScriptStart>();
|
||||
var msg = new MsgScriptStart();
|
||||
msg.ScriptSession = _nextSessionId++;
|
||||
_netManager.ClientSendMessage(msg);
|
||||
}
|
||||
@@ -74,7 +74,7 @@ namespace Robust.Client.Console
|
||||
{
|
||||
_activeConsoles.Remove(session);
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgScriptStop>();
|
||||
var msg = new MsgScriptStop();
|
||||
msg.ScriptSession = session;
|
||||
_netManager.ClientSendMessage(msg);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
@@ -88,6 +89,7 @@ namespace Robust.Client.Debugging
|
||||
EntityManager,
|
||||
IoCManager.Resolve<IEyeManager>(),
|
||||
IoCManager.Resolve<IInputManager>(),
|
||||
IoCManager.Resolve<IMapManager>(),
|
||||
IoCManager.Resolve<IResourceCache>(),
|
||||
this,
|
||||
Get<SharedPhysicsSystem>()));
|
||||
@@ -151,10 +153,12 @@ namespace Robust.Client.Debugging
|
||||
/// Shows the world point for each contact in the viewport.
|
||||
/// </summary>
|
||||
ContactPoints = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Shows the world normal for each contact in the viewport.
|
||||
/// </summary>
|
||||
ContactNormals = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Shows all physics shapes in the viewport.
|
||||
/// </summary>
|
||||
@@ -162,6 +166,10 @@ namespace Robust.Client.Debugging
|
||||
ShapeInfo = 1 << 3,
|
||||
Joints = 1 << 4,
|
||||
AABBs = 1 << 5,
|
||||
|
||||
/// <summary>
|
||||
/// Shows Center of Mass for all bodies in the viewport.
|
||||
/// </summary>
|
||||
COM = 1 << 6,
|
||||
}
|
||||
|
||||
@@ -170,6 +178,7 @@ namespace Robust.Client.Debugging
|
||||
private IEntityManager _entityManager = default!;
|
||||
private IEyeManager _eyeManager = default!;
|
||||
private IInputManager _inputManager = default!;
|
||||
private IMapManager _mapManager = default!;
|
||||
private DebugPhysicsSystem _debugPhysicsSystem = default!;
|
||||
private SharedPhysicsSystem _physicsSystem = default!;
|
||||
|
||||
@@ -181,11 +190,12 @@ namespace Robust.Client.Debugging
|
||||
|
||||
private HashSet<Joint> _drawnJoints = new();
|
||||
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
|
||||
public PhysicsDebugOverlay(IEntityManager entityManager, IEyeManager eyeManager, IInputManager inputManager, IMapManager mapManager, IResourceCache cache, DebugPhysicsSystem system, SharedPhysicsSystem physicsSystem)
|
||||
{
|
||||
_entityManager = entityManager;
|
||||
_eyeManager = eyeManager;
|
||||
_inputManager = inputManager;
|
||||
_mapManager = mapManager;
|
||||
_debugPhysicsSystem = system;
|
||||
_physicsSystem = physicsSystem;
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
@@ -240,26 +250,21 @@ namespace Robust.Client.Debugging
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.COM) != 0)
|
||||
{
|
||||
const float Alpha = 0.25f;
|
||||
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
Color color;
|
||||
const float Alpha = 0.25f;
|
||||
float size;
|
||||
|
||||
if (_entityManager.HasComponent<MapGridComponent>(physBody.Owner))
|
||||
{
|
||||
color = Color.Orange.WithAlpha(Alpha);
|
||||
size = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.Purple.WithAlpha(Alpha);
|
||||
size = 0.2f;
|
||||
}
|
||||
|
||||
var color = Color.Purple.WithAlpha(Alpha);
|
||||
var transform = physBody.GetTransform();
|
||||
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 0.2f, color);
|
||||
}
|
||||
|
||||
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), size, color);
|
||||
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, viewBounds))
|
||||
{
|
||||
var physBody = _entityManager.GetComponent<PhysicsComponent>(grid.GridEntityId);
|
||||
var color = Color.Orange.WithAlpha(Alpha);
|
||||
var transform = physBody.GetTransform();
|
||||
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), 1f, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
67
Robust.Client/GameController/GameController.Redialler.cs
Normal file
67
Robust.Client/GameController/GameController.Redialler.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Robust.LoaderApi;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client;
|
||||
|
||||
internal partial class GameController
|
||||
{
|
||||
// A little paranoia goes a long way.
|
||||
// This is static on purpose - the whole process shouldn't redial more than once, ever.
|
||||
private static int _hasRedialled = 0;
|
||||
|
||||
public void Redial(string address, string? text = null)
|
||||
{
|
||||
// -- ATTENTION, YE WOULD-BE TRESPASSERS! --
|
||||
// This code is the least testable code ever (because it's in-engine code that requires the Launcher), so don't make it do too much.
|
||||
// This checks for some obvious requirements and then forwards to RedialApi which does the actual work.
|
||||
// Testing of RedialApi is doable in the SS14.Launcher project, this is why it has a fake RobustToolbox build.
|
||||
// -- THANK YE, NOW KINDLY MOVE ALONG! --
|
||||
|
||||
// We don't ever want to successfully redial more than once, and we want to shutdown once we've redialled.
|
||||
// Otherwise abuse could happen.
|
||||
DebugTools.AssertNotNull(_mainLoop);
|
||||
|
||||
Logger.Info($"Attempting redial of {address}: {text ?? "no reason given"}");
|
||||
|
||||
if (!_mainLoop!.Running)
|
||||
{
|
||||
throw new Exception("Attempted a redial during shutdown, this is not acceptable - redial first and if you succeed it'll shutdown anyway.");
|
||||
}
|
||||
|
||||
if (_loaderArgs == null)
|
||||
{
|
||||
throw new Exception("Attempted a redial when the game was not run from the launcher (_loaderArgs == null)");
|
||||
}
|
||||
|
||||
if (_loaderArgs!.RedialApi == null)
|
||||
{
|
||||
throw new Exception("Attempted a redial when redialling was not supported by the loader (Outdated launcher?)");
|
||||
}
|
||||
|
||||
if (Interlocked.Increment(ref _hasRedialled) != 1)
|
||||
{
|
||||
// Don't let it overflow or anything
|
||||
Interlocked.Decrement(ref _hasRedialled);
|
||||
throw new Exception("Attempted a redial after one already succeeded or while one is in progress, this is never acceptable");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_loaderArgs!.RedialApi!.Redial(new Uri(address), text ?? "");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Interlocked.Decrement(ref _hasRedialled);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
Shutdown("Redial");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
@@ -29,6 +28,7 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
@@ -66,6 +66,7 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IAuthManager _authManager = default!;
|
||||
[Dependency] private readonly IMidiManager _midiManager = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IParallelManagerInternal _parallelMgr = default!;
|
||||
|
||||
private IWebViewManagerHook? _webViewHook;
|
||||
|
||||
@@ -131,8 +132,9 @@ namespace Robust.Client
|
||||
_inputManager.Initialize();
|
||||
_console.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDirectory(new ResourcePath("/EnginePrototypes/"));
|
||||
_prototypeManager.LoadDirectory(Options.PrototypeDirectory);
|
||||
_prototypeManager.Resync();
|
||||
_prototypeManager.ResolveResults();
|
||||
_entityManager.Initialize();
|
||||
_mapManager.Initialize();
|
||||
_gameStateManager.Initialize();
|
||||
@@ -197,7 +199,7 @@ namespace Robust.Client
|
||||
|
||||
_clyde.Ready();
|
||||
|
||||
if (!Options.DisableCommandLineConnect &&
|
||||
if (_resourceManifest!.AutoConnect &&
|
||||
(_commandLineArgs?.Connect == true || _commandLineArgs?.Launcher == true)
|
||||
&& LaunchState.ConnectEndpoint != null)
|
||||
{
|
||||
@@ -213,7 +215,7 @@ namespace Robust.Client
|
||||
{
|
||||
// Parses /manifest.yml for game-specific settings that cannot be exclusively set up by content code.
|
||||
if (!_resourceCache.TryContentFileRead("/manifest.yml", out var stream))
|
||||
return new ResourceManifestData(Array.Empty<string>(), null, null, null, null);
|
||||
return new ResourceManifestData(Array.Empty<string>(), null, null, null, null, true);
|
||||
|
||||
var yamlStream = new YamlStream();
|
||||
using (stream)
|
||||
@@ -223,7 +225,7 @@ namespace Robust.Client
|
||||
}
|
||||
|
||||
if (yamlStream.Documents.Count == 0)
|
||||
return new ResourceManifestData(Array.Empty<string>(), null, null, null, null);
|
||||
return new ResourceManifestData(Array.Empty<string>(), null, null, null, null, true);
|
||||
|
||||
if (yamlStream.Documents.Count != 1 || yamlStream.Documents[0].RootNode is not YamlMappingNode mapping)
|
||||
{
|
||||
@@ -258,7 +260,11 @@ namespace Robust.Client
|
||||
if (mapping.TryGetNode("splashLogo", out var splashNode))
|
||||
splashLogo = splashNode.AsString();
|
||||
|
||||
return new ResourceManifestData(modules, assemblyPrefix, defaultWindowTitle, windowIconSet, splashLogo);
|
||||
bool autoConnect = true;
|
||||
if (mapping.TryGetNode("autoConnect", out var autoConnectNode))
|
||||
autoConnect = autoConnectNode.AsBool();
|
||||
|
||||
return new ResourceManifestData(modules, assemblyPrefix, defaultWindowTitle, windowIconSet, splashLogo, autoConnect);
|
||||
}
|
||||
|
||||
internal bool StartupSystemSplash(GameControllerOptions options, Func<ILogHandler>? logHandlerFactory)
|
||||
@@ -326,6 +332,8 @@ namespace Robust.Client
|
||||
|
||||
ProfileOptSetup.Setup(_configurationManager);
|
||||
|
||||
_parallelMgr.Initialize();
|
||||
|
||||
_resourceCache.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
@@ -584,7 +592,8 @@ namespace Robust.Client
|
||||
string? AssemblyPrefix,
|
||||
string? DefaultWindowTitle,
|
||||
string? WindowIconSet,
|
||||
string? SplashLogo
|
||||
string? SplashLogo,
|
||||
bool AutoConnect
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +82,5 @@ namespace Robust.Client
|
||||
/// Whether to load config and user data.
|
||||
/// </summary>
|
||||
public bool LoadConfigAndUserData { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to disable command line args server auto-connecting.
|
||||
/// </summary>
|
||||
public bool DisableCommandLineConnect { get; init; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,9 +37,9 @@ namespace Robust.Client.GameObjects
|
||||
return base.CreateEntity(prototypeName, uid);
|
||||
}
|
||||
|
||||
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity)
|
||||
void IClientEntityManagerInternal.InitializeEntity(EntityUid entity, MetaDataComponent? meta = null)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
base.InitializeEntity(entity, meta);
|
||||
}
|
||||
|
||||
void IClientEntityManagerInternal.StartEntity(EntityUid entity)
|
||||
@@ -86,7 +86,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message, uint sequence)
|
||||
{
|
||||
var msg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
var msg = new MsgEntity();
|
||||
msg.Type = EntityMessageType.SystemMessage;
|
||||
msg.SystemMessage = message;
|
||||
msg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
@@ -32,11 +33,13 @@ namespace Robust.Client.GameObjects
|
||||
public const string LogCategory = "go.comp.icon";
|
||||
const string SerializationCache = "icon";
|
||||
|
||||
[Obsolete("Use SpriteSystem instead.")]
|
||||
private static IRsiStateLike TextureForConfig(IconComponent compData, IResourceCache resourceCache)
|
||||
{
|
||||
return compData.Icon?.Default ?? resourceCache.GetFallback<TextureResource>().Texture;
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem instead.")]
|
||||
public static IRsiStateLike? GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
if (!prototype.Components.TryGetValue("Icon", out var compData))
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedPointLightComponent))]
|
||||
public sealed class PointLightComponent : SharedPointLightComponent, ISerializationHooks
|
||||
public sealed class PointLightComponent : SharedPointLightComponent
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
|
||||
@@ -601,8 +601,10 @@ namespace Robust.Client.GameObjects
|
||||
private void RebuildBounds()
|
||||
{
|
||||
_bounds = new Box2();
|
||||
foreach (var layer in Layers.Where(layer => layer.Visible))
|
||||
foreach (var layer in Layers)
|
||||
{
|
||||
if (!layer.Visible) continue;
|
||||
|
||||
_bounds = _bounds.Union(layer.CalculateBoundingBox());
|
||||
}
|
||||
}
|
||||
@@ -1520,6 +1522,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem instead.")]
|
||||
internal static RSI.State GetFallbackState(IResourceCache cache)
|
||||
{
|
||||
var rsi = cache.GetResource<RSIResource>("/Textures/error.rsi").RSI;
|
||||
@@ -1968,7 +1971,7 @@ namespace Robust.Client.GameObjects
|
||||
/// <inheritdoc/>
|
||||
public Box2 CalculateBoundingBox()
|
||||
{
|
||||
var textureSize = PixelSize / EyeManager.PixelsPerMeter;
|
||||
var textureSize = (Vector2) PixelSize / EyeManager.PixelsPerMeter;
|
||||
|
||||
// If the parent has locked rotation and we don't have any rotation,
|
||||
// we can take the quick path of just making a box the size of the texture.
|
||||
|
||||
@@ -75,6 +75,10 @@ namespace Robust.Client.GameObjects
|
||||
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new[] {this, wrapped.UiKey});
|
||||
boundInterface.Open();
|
||||
_openInterfaces[wrapped.UiKey] = boundInterface;
|
||||
|
||||
var playerSession = _playerManager.LocalPlayer?.Session;
|
||||
if(playerSession != null)
|
||||
_entityManager.EventBus.RaiseLocalEvent(Owner, new BoundUIOpenedEvent(wrapped.UiKey, Owner, playerSession));
|
||||
}
|
||||
|
||||
internal void Close(object uiKey, bool remoteCall)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Physics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -33,9 +31,10 @@ namespace Robust.Client.GameObjects
|
||||
UpdatesAfter.Add(typeof(TransformSystem));
|
||||
UpdatesAfter.Add(typeof(PhysicsSystem));
|
||||
|
||||
SubscribeLocalEvent<OccluderDirtyEvent>(HandleDirtyEvent);
|
||||
SubscribeLocalEvent<OccluderDirtyEvent>(OnOccluderDirty);
|
||||
|
||||
SubscribeLocalEvent<ClientOccluderComponent, AnchorStateChangedEvent>(HandleAnchorChanged);
|
||||
SubscribeLocalEvent<ClientOccluderComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
||||
SubscribeLocalEvent<ClientOccluderComponent, ReAnchorEvent>(OnReAnchor);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
@@ -62,51 +61,58 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleAnchorChanged(EntityUid uid, ClientOccluderComponent component, ref AnchorStateChangedEvent args)
|
||||
private static void OnAnchorChanged(EntityUid uid, ClientOccluderComponent component, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
component.AnchorStateChanged();
|
||||
}
|
||||
|
||||
private void HandleDirtyEvent(OccluderDirtyEvent ev)
|
||||
private void OnReAnchor(EntityUid uid, ClientOccluderComponent component, ref ReAnchorEvent args)
|
||||
{
|
||||
component.AnchorStateChanged();
|
||||
}
|
||||
|
||||
private void OnOccluderDirty(OccluderDirtyEvent ev)
|
||||
{
|
||||
var sender = ev.Sender;
|
||||
IMapGrid? grid;
|
||||
var occluderQuery = GetEntityQuery<ClientOccluderComponent>();
|
||||
|
||||
if (EntityManager.EntityExists(sender) &&
|
||||
EntityManager.TryGetComponent(sender, out ClientOccluderComponent? iconSmooth)
|
||||
&& iconSmooth.Initialized)
|
||||
occluderQuery.HasComponent(sender))
|
||||
{
|
||||
var xform = EntityManager.GetComponent<TransformComponent>(sender);
|
||||
if (!_mapManager.TryGetGrid(xform.GridID, out var grid1))
|
||||
if (!_mapManager.TryGetGrid(xform.GridID, out grid))
|
||||
return;
|
||||
|
||||
var coords = xform.Coordinates;
|
||||
var localGrid = grid.TileIndicesFor(coords);
|
||||
|
||||
_dirtyEntities.Enqueue(sender);
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.North));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.South));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.East));
|
||||
AddValidEntities(grid1.GetInDir(coords, Direction.West));
|
||||
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(localGrid + new Vector2i(0, 1)), occluderQuery);
|
||||
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(localGrid + new Vector2i(0, -1)), occluderQuery);
|
||||
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(localGrid + new Vector2i(1, 0)), occluderQuery);
|
||||
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(localGrid + new Vector2i(-1, 0)), occluderQuery);
|
||||
}
|
||||
|
||||
// Entity is no longer valid, update around the last position it was at.
|
||||
else if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out var grid))
|
||||
else if (ev.LastPosition.HasValue && _mapManager.TryGetGrid(ev.LastPosition.Value.grid, out grid))
|
||||
{
|
||||
var pos = ev.LastPosition.Value.pos;
|
||||
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(1, 0)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(-1, 0)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, 1)));
|
||||
AddValidEntities(grid.GetAnchoredEntities(pos + new Vector2i(0, -1)));
|
||||
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, 1)), occluderQuery);
|
||||
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(0, -1)), occluderQuery);
|
||||
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(1, 0)), occluderQuery);
|
||||
AddValidEntities(grid.GetAnchoredEntitiesEnumerator(pos + new Vector2i(-1, 0)), occluderQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddValidEntities(IEnumerable<EntityUid> candidates)
|
||||
private void AddValidEntities(AnchoredEntitiesEnumerator enumerator, EntityQuery<ClientOccluderComponent> occluderQuery)
|
||||
{
|
||||
foreach (var entity in candidates)
|
||||
while (enumerator.MoveNext(out var entity))
|
||||
{
|
||||
if (EntityManager.HasComponent<ClientOccluderComponent>(entity))
|
||||
{
|
||||
_dirtyEntities.Enqueue(entity);
|
||||
}
|
||||
if (!occluderQuery.HasComponent(entity.Value)) continue;
|
||||
|
||||
_dirtyEntities.Enqueue(entity.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Utility;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class DebugEntityLookupCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => "togglelookup";
|
||||
public string Description => "Shows / hides entitylookup bounds via an overlay";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
EntitySystem.Get<DebugEntityLookupSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DebugEntityLookupSystem : EntitySystem
|
||||
{
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled == value) return;
|
||||
|
||||
_enabled = value;
|
||||
|
||||
if (_enabled)
|
||||
{
|
||||
IoCManager.Resolve<IOverlayManager>().AddOverlay(
|
||||
new EntityLookupOverlay(
|
||||
EntityManager,
|
||||
Get<EntityLookupSystem>()));
|
||||
}
|
||||
else
|
||||
{
|
||||
IoCManager.Resolve<IOverlayManager>().RemoveOverlay<EntityLookupOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
}
|
||||
|
||||
public sealed class EntityLookupOverlay : Overlay
|
||||
{
|
||||
private IEntityManager _entityManager = default!;
|
||||
private EntityLookupSystem _lookup = default!;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityLookupOverlay(IEntityManager entManager, EntityLookupSystem lookup)
|
||||
{
|
||||
_entityManager = entManager;
|
||||
_lookup = lookup;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var lookup in _lookup.FindLookupsIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var lookupXform = xformQuery.GetComponent(lookup.Owner);
|
||||
|
||||
var (_, rotation, matrix, invMatrix) = lookupXform.GetWorldPositionRotationMatrixWithInv();
|
||||
|
||||
worldHandle.SetTransform(matrix);
|
||||
|
||||
|
||||
var lookupAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
var ents = new List<EntityUid>();
|
||||
|
||||
// Gonna allocate a lot but debug overlay sooo
|
||||
lookup.Tree._b2Tree.FastQuery(ref lookupAABB, (ref EntityUid data) =>
|
||||
{
|
||||
ents.Add(data);
|
||||
});
|
||||
|
||||
foreach (var ent in ents)
|
||||
{
|
||||
if (_entityManager.Deleted(ent)) continue;
|
||||
var xform = xformQuery.GetComponent(ent);
|
||||
|
||||
//DebugTools.Assert(!ent.IsInContainer(_entityManager));
|
||||
var (entPos, entRot) = xform.GetWorldPositionRotation();
|
||||
|
||||
var lookupPos = invMatrix.Transform(entPos);
|
||||
var lookupRot = entRot - rotation;
|
||||
|
||||
var aabb = _lookup.GetAABB(ent, lookupPos, lookupRot, xform, xformQuery);
|
||||
|
||||
worldHandle.DrawRect(aabb, Color.Blue.WithAlpha(0.2f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ namespace Robust.Client.GameObjects
|
||||
worldHandle.SetTransform(worldMatrix);
|
||||
var transform = new Transform(Vector2.Zero, Angle.Zero);
|
||||
|
||||
gridInternal.GetMapChunks(viewport, out var chunkEnumerator);
|
||||
var chunkEnumerator = gridInternal.GetMapChunks(viewport);
|
||||
|
||||
while (chunkEnumerator.MoveNext(out var chunk))
|
||||
{
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
internal sealed class GridFixtureSystem : SharedGridFixtureSystem
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class ScaleVisualsSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<AppearanceChangeEvent>(OnChangeData);
|
||||
}
|
||||
|
||||
private void OnChangeData(ref AppearanceChangeEvent ev)
|
||||
{
|
||||
if (!ev.AppearanceData.TryGetValue(ScaleVisuals.Scale, out var scale) ||
|
||||
!TryComp<SpriteComponent>(ev.Component.Owner, out var spriteComponent)) return;
|
||||
|
||||
var vecScale = (Vector2)scale;
|
||||
|
||||
// Set it directly because prediction may call this multiple times.
|
||||
spriteComponent.Scale = vecScale;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
@@ -17,13 +18,13 @@ public sealed partial class SpriteSystem
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
[Pure]
|
||||
private readonly Dictionary<string, IRsiStateLike> _cachedPrototypeIcons = new();
|
||||
|
||||
public Texture Frame0(SpriteSpecifier specifier)
|
||||
{
|
||||
return RsiStateLike(specifier).Default;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public IRsiStateLike RsiStateLike(SpriteSpecifier specifier)
|
||||
{
|
||||
switch (specifier)
|
||||
@@ -35,38 +36,71 @@ public sealed partial class SpriteSystem
|
||||
return GetState(rsi);
|
||||
|
||||
case SpriteSpecifier.EntityPrototype prototypeIcon:
|
||||
if (!_proto.TryIndex<EntityPrototype>(prototypeIcon.EntityPrototypeId, out var prototype))
|
||||
{
|
||||
Logger.Error("Failed to load PrototypeIcon {0}", prototypeIcon.EntityPrototypeId);
|
||||
return SpriteComponent.GetFallbackState(_resourceCache);
|
||||
}
|
||||
|
||||
return SpriteComponent.GetPrototypeIcon(prototype, _resourceCache);
|
||||
return GetPrototypeIcon(prototypeIcon.EntityPrototypeId);
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
/// <summary>
|
||||
/// Returns an icon for a given <see cref="EntityPrototype"/> ID, or a fallback in case of an error.
|
||||
/// This method caches the result based on the prototype identifier.
|
||||
/// </summary>
|
||||
public IRsiStateLike GetPrototypeIcon(string prototype)
|
||||
{
|
||||
var icon = IconComponent.GetPrototypeIcon(prototype, _resourceCache);
|
||||
if (icon != null) return icon;
|
||||
// Check if this prototype has been cached before, and if so return the result.
|
||||
if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult))
|
||||
return cachedResult;
|
||||
|
||||
if (!prototype.Components.ContainsKey("Sprite"))
|
||||
if (!_proto.TryIndex<EntityPrototype>(prototype, out var entityPrototype))
|
||||
{
|
||||
return SpriteComponent.GetFallbackState(resourceCache);
|
||||
// The specified prototype doesn't exist, return the fallback "error" sprite.
|
||||
Logger.Error("Failed to load PrototypeIcon {0}", prototype);
|
||||
return GetFallbackState();
|
||||
}
|
||||
|
||||
// Generate the icon and cache it in case it's ever needed again.
|
||||
var result = GetPrototypeIcon(entityPrototype);
|
||||
_cachedPrototypeIcons[prototype] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an icon for a given <see cref="EntityPrototype"/> ID, or a fallback in case of an error.
|
||||
/// This method does NOT cache the result.
|
||||
/// </summary>
|
||||
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype)
|
||||
{
|
||||
// IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal.
|
||||
if (prototype.Components.TryGetValue("Icon", out var compData)
|
||||
&& compData is IconComponent {Icon: {} icon})
|
||||
{
|
||||
return icon.Default;
|
||||
}
|
||||
|
||||
// If the prototype doesn't have a SpriteComponent, then there's nothing we can do but return the fallback.
|
||||
if (!prototype.Components.ContainsKey("Sprite"))
|
||||
{
|
||||
return GetFallbackState();
|
||||
}
|
||||
|
||||
// Finally, we use spawn a dummy entity to get its icon.
|
||||
var dummy = Spawn(prototype.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
|
||||
var result = spriteComponent.Icon ?? SpriteComponent.GetFallbackState(resourceCache);
|
||||
var result = spriteComponent.Icon ?? GetFallbackState();
|
||||
Del(dummy);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public RSI.State GetFallbackState()
|
||||
{
|
||||
return _resourceCache.GetFallback<RSIResource>().RSI["error"];
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public RSI.State GetState(SpriteSpecifier.Rsi rsiSpecifier)
|
||||
{
|
||||
@@ -79,6 +113,20 @@ public sealed partial class SpriteSystem
|
||||
}
|
||||
|
||||
Logger.Error("Failed to load RSI {0}", rsiSpecifier.RsiPath);
|
||||
return SpriteComponent.GetFallbackState(_resourceCache);
|
||||
return GetFallbackState();
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs protoReloaded)
|
||||
{
|
||||
// Check if any EntityPrototype has been changed.
|
||||
if (!protoReloaded.ByType.TryGetValue(typeof(EntityPrototype), out var changedSet))
|
||||
return;
|
||||
|
||||
// Remove all changed prototypes from the cache, if they're there.
|
||||
foreach (var (prototype, _) in changedSet.Modified)
|
||||
{
|
||||
// Let's be lazy and not regenerate them until something needs them again.
|
||||
_cachedPrototypeIcons.Remove(prototype);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,16 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_proto.PrototypesReloaded += OnPrototypesReloaded;
|
||||
SubscribeLocalEvent<SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_proto.PrototypesReloaded -= OnPrototypesReloaded;
|
||||
}
|
||||
|
||||
private void QueueUpdateInert(SpriteUpdateInertEvent ev)
|
||||
{
|
||||
_inertUpdateQueue.Enqueue(ev.Sprite);
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
EntityUid CreateEntity(string? prototypeName, EntityUid uid = default);
|
||||
|
||||
void InitializeEntity(EntityUid entity);
|
||||
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);
|
||||
|
||||
void StartEntity(EntityUid entity);
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void AckGameState(GameTick sequence)
|
||||
{
|
||||
var msg = _network.CreateNetMessage<MsgStateAck>();
|
||||
var msg = new MsgStateAck();
|
||||
msg.Sequence = sequence;
|
||||
_network.ClientSendMessage(msg);
|
||||
}
|
||||
@@ -429,7 +429,7 @@ namespace Robust.Client.GameStates
|
||||
private List<EntityUid> ApplyEntityStates(ReadOnlySpan<EntityState> curEntStates, ReadOnlySpan<EntityUid> deletions,
|
||||
ReadOnlySpan<EntityState> nextEntStates)
|
||||
{
|
||||
var toApply = new Dictionary<EntityUid, (EntityState?, EntityState?)>();
|
||||
var toApply = new Dictionary<EntityUid, (EntityState?, EntityState?)>(curEntStates.Length);
|
||||
var toInitialize = new List<EntityUid>();
|
||||
var created = new List<EntityUid>();
|
||||
|
||||
@@ -548,6 +548,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
if (curState != null)
|
||||
{
|
||||
compStateWork.EnsureCapacity(curState.ComponentChanges.Span.Length);
|
||||
|
||||
foreach (var compChange in curState.ComponentChanges.Span)
|
||||
{
|
||||
if (compChange.Deleted)
|
||||
@@ -580,6 +582,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
if (nextState != null)
|
||||
{
|
||||
compStateWork.EnsureCapacity(compStateWork.Count + nextState.ComponentChanges.Span.Length);
|
||||
|
||||
foreach (var compState in nextState.ComponentChanges.Span)
|
||||
{
|
||||
if (compStateWork.TryGetValue(compState.NetID, out var state))
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(grid.GridEntityId);
|
||||
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
|
||||
grid.GetMapChunks(worldBounds, out var enumerator);
|
||||
var enumerator = grid.GetMapChunks(worldBounds);
|
||||
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var worldAABB = CalcWorldAABB(vp);
|
||||
var worldBounds = CalcWorldBounds(vp);
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, worldAABB, worldBounds);
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, vp.Eye!.Position.MapId, worldAABB, worldBounds);
|
||||
|
||||
foreach (var overlay in list)
|
||||
{
|
||||
|
||||
@@ -415,9 +415,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case float f:
|
||||
program.SetUniform(name, f);
|
||||
break;
|
||||
case float[] fArr:
|
||||
program.SetUniform(name, fArr);
|
||||
break;
|
||||
case Vector2 vector2:
|
||||
program.SetUniform(name, vector2);
|
||||
break;
|
||||
case Vector2[] vector2Arr:
|
||||
program.SetUniform(name, vector2Arr);
|
||||
break;
|
||||
case Vector3 vector3:
|
||||
program.SetUniform(name, vector3);
|
||||
break;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -395,12 +395,24 @@ namespace Robust.Client.Graphics.Clyde
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, float[] value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, Vector2 value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, Vector2[] value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, Vector3 value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
|
||||
@@ -412,10 +412,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, float[] value)
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, Vector2 value)
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, Vector2[] value)
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, Vector3 value)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
@@ -234,6 +234,19 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.Uniform1(uniformId, single);
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, float[] singles)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
GL.Uniform1(uniformId, singles.Length, singles);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, float[] singles)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
GL.Uniform1(uniformId, singles.Length, singles);
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Matrix3 matrix)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
@@ -375,6 +388,31 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, Vector2[] vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, vector);
|
||||
}
|
||||
|
||||
public void SetUniform(int uniformName, Vector2[] vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, vector);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformDirect(int slot, Vector2[] vectors)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (Vector2* ptr = &vectors[0])
|
||||
{
|
||||
GL.Uniform2(slot, vectors.Length, (float*)ptr);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformTexture(string uniformName, TextureUnit textureUnit)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Robust.Client.Graphics
|
||||
handle = renderHandle.DrawingHandleWorld;
|
||||
}
|
||||
|
||||
var args = new OverlayDrawArgs(currentSpace, vpControl, vp, handle, screenBox, worldBox, worldBounds);
|
||||
var args = new OverlayDrawArgs(currentSpace, vpControl, vp, handle, screenBox, vp.Eye!.Position.MapId, worldBox, worldBounds);
|
||||
|
||||
Draw(args);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
@@ -38,6 +39,11 @@ namespace Robust.Client.Graphics
|
||||
/// </summary>
|
||||
public readonly UIBox2i ViewportBounds;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="MapId"/> of the viewport's eye.
|
||||
/// </summary>
|
||||
public readonly MapId MapId;
|
||||
|
||||
/// <summary>
|
||||
/// AABB enclosing the area visible in the viewport.
|
||||
/// </summary>
|
||||
@@ -57,6 +63,7 @@ namespace Robust.Client.Graphics
|
||||
IClydeViewport viewport,
|
||||
DrawingHandleBase drawingHandle,
|
||||
in UIBox2i viewportBounds,
|
||||
in MapId mapId,
|
||||
in Box2 worldAabb,
|
||||
in Box2Rotated worldBounds)
|
||||
{
|
||||
@@ -65,6 +72,7 @@ namespace Robust.Client.Graphics
|
||||
Viewport = viewport;
|
||||
DrawingHandle = drawingHandle;
|
||||
ViewportBounds = viewportBounds;
|
||||
MapId = mapId;
|
||||
WorldAABB = worldAabb;
|
||||
WorldBounds = worldBounds;
|
||||
}
|
||||
|
||||
@@ -135,36 +135,42 @@ namespace Robust.Client.Graphics
|
||||
|
||||
internal sealed class ShaderDataTypeFull
|
||||
{
|
||||
public ShaderDataTypeFull(ShaderDataType type, ShaderPrecisionQualifier prec)
|
||||
public ShaderDataTypeFull(ShaderDataType type, ShaderPrecisionQualifier prec, int? count = null)
|
||||
{
|
||||
Type = type;
|
||||
Precision = prec;
|
||||
Count = count;
|
||||
}
|
||||
|
||||
public ShaderDataType Type { get; }
|
||||
public ShaderPrecisionQualifier Precision { get; }
|
||||
|
||||
public int? Count;
|
||||
|
||||
public bool IsArray => Count != null;
|
||||
|
||||
public string GetNativeType()
|
||||
{
|
||||
if (Precision == ShaderPrecisionQualifier.Low)
|
||||
string? precision = Precision switch
|
||||
{
|
||||
return "lowp " + Type.GetNativeType();
|
||||
}
|
||||
else if (Precision == ShaderPrecisionQualifier.Medium)
|
||||
{
|
||||
return "mediump " + Type.GetNativeType();
|
||||
}
|
||||
else if (Precision == ShaderPrecisionQualifier.High)
|
||||
{
|
||||
return "highp " + Type.GetNativeType();
|
||||
}
|
||||
return Type.GetNativeType();
|
||||
ShaderPrecisionQualifier.Low => "lowp ",
|
||||
ShaderPrecisionQualifier.Medium => "mediump ",
|
||||
ShaderPrecisionQualifier.High => "highp ",
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return IsArray ? $"{precision}{Type.GetNativeType()}[{Count}]" : $"{precision}{Type.GetNativeType()}";
|
||||
}
|
||||
|
||||
public bool TypePrecisionConsistent()
|
||||
{
|
||||
return Type.TypeHasPrecision() == (Precision != ShaderPrecisionQualifier.None);
|
||||
}
|
||||
|
||||
public bool TypeCountConsistent()
|
||||
{
|
||||
return Count == null || Type.TypeSupportsArrays();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ShaderEnumExt
|
||||
@@ -195,6 +201,7 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
return
|
||||
(type == ShaderDataType.Float) ||
|
||||
(type == ShaderDataType.Int) ||
|
||||
(type == ShaderDataType.Vec2) ||
|
||||
(type == ShaderDataType.Vec3) ||
|
||||
(type == ShaderDataType.Vec4) ||
|
||||
@@ -203,6 +210,14 @@ namespace Robust.Client.Graphics
|
||||
(type == ShaderDataType.Mat4);
|
||||
}
|
||||
|
||||
public static bool TypeSupportsArrays(this ShaderDataType type)
|
||||
{
|
||||
// TODO: add support for int, and vec3/4 arrays
|
||||
return
|
||||
(type == ShaderDataType.Float) ||
|
||||
(type == ShaderDataType.Vec2);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
private static readonly Dictionary<ShaderDataType, string> _nativeTypes = new()
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
@@ -119,6 +119,13 @@ namespace Robust.Client.Graphics
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, float[] value)
|
||||
{
|
||||
EnsureAlive();
|
||||
EnsureMutable();
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, Vector2 value)
|
||||
{
|
||||
EnsureAlive();
|
||||
@@ -126,6 +133,13 @@ namespace Robust.Client.Graphics
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, Vector2[] value)
|
||||
{
|
||||
EnsureAlive();
|
||||
EnsureMutable();
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, Vector3 value)
|
||||
{
|
||||
EnsureAlive();
|
||||
@@ -244,7 +258,9 @@ namespace Robust.Client.Graphics
|
||||
private protected abstract ShaderInstance DuplicateImpl();
|
||||
|
||||
private protected abstract void SetParameterImpl(string name, float value);
|
||||
private protected abstract void SetParameterImpl(string name, float[] value);
|
||||
private protected abstract void SetParameterImpl(string name, Vector2 value);
|
||||
private protected abstract void SetParameterImpl(string name, Vector2[] value);
|
||||
private protected abstract void SetParameterImpl(string name, Vector3 value);
|
||||
private protected abstract void SetParameterImpl(string name, Vector4 value);
|
||||
private protected abstract void SetParameterImpl(string name, Color value);
|
||||
|
||||
@@ -504,17 +504,33 @@ namespace Robust.Client.Graphics
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_shaderTypeMap.TryGetValue(wordType.Word, out var ret))
|
||||
if (!_shaderTypeMap.TryGetValue(wordType.Word, out var ret))
|
||||
throw new ShaderParseException("Expected type", wordType.Position);
|
||||
|
||||
var result = new ShaderDataTypeFull(ret, precision);
|
||||
if (!result.TypePrecisionConsistent())
|
||||
{
|
||||
var result = new ShaderDataTypeFull(ret, precision);
|
||||
if (!result.TypePrecisionConsistent())
|
||||
{
|
||||
throw new ShaderParseException($"Type {ret} cannot accept precision {precision}", wordType.Position);
|
||||
}
|
||||
return result;
|
||||
throw new ShaderParseException($"Type {ret} cannot accept precision {precision}", wordType.Position);
|
||||
}
|
||||
|
||||
throw new ShaderParseException("Expected type or precision", wordType.Position);
|
||||
// is this type meant to be an array?
|
||||
if (_peekToken() is TokenSymbol bracketOpen && bracketOpen.Symbol == Symbols.BracketOpen)
|
||||
{
|
||||
_takeToken();
|
||||
if (_takeToken() is not TokenNumber number || !int.TryParse(number.Number, out var count))
|
||||
throw new ShaderParseException($"Failed to parse array length", bracketOpen.Position);
|
||||
|
||||
if (_takeToken() is not TokenSymbol bracketClose || bracketClose.Symbol != Symbols.BracketClosed)
|
||||
throw new ShaderParseException($"Array length definition missing closing bracket", number.Position);
|
||||
|
||||
result.Count = count;
|
||||
|
||||
// are arrays supported by this type?
|
||||
if (!result.TypeCountConsistent())
|
||||
throw new ShaderParseException($"Type {ret} does not support arrays", wordType.Position);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.Graphics
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
|
||||
[ViewVariables]
|
||||
[DataField("id", required: true)]
|
||||
[IdDataFieldAttribute]
|
||||
public string ID { get; } = default!;
|
||||
|
||||
private ShaderKind Kind;
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
namespace Robust.Client
|
||||
{
|
||||
public interface IGameController
|
||||
{
|
||||
InitialLaunchState LaunchState { get; }
|
||||
namespace Robust.Client;
|
||||
|
||||
public interface IGameController
|
||||
{
|
||||
InitialLaunchState LaunchState { get; }
|
||||
|
||||
void Shutdown(string? reason=null);
|
||||
|
||||
/// <summary>
|
||||
/// Try to cause the launcher to either reconnect to the same server or connect to a new server.
|
||||
/// *The engine will shutdown on success.*
|
||||
/// Will throw an exception if contacting the launcher failed (success indicates it is now the launcher's responsibility).
|
||||
/// To redial the same server, retrieve the server's address from `LaunchState.Ss14Address`.
|
||||
/// </summary>
|
||||
/// <param name="address">The server address, such as "ss14://localhost:1212/".</param>
|
||||
/// <param name="text">Informational text on the cause of the reconnect. Empty or null gives a default reason.</param>
|
||||
void Redial(string address, string? text = null);
|
||||
}
|
||||
|
||||
void Shutdown(string? reason=null);
|
||||
}
|
||||
}
|
||||
@@ -497,7 +497,7 @@ namespace Robust.Client.Input
|
||||
|
||||
if (robustMapping.TryGet("binds", out var BaseKeyRegsNode))
|
||||
{
|
||||
var baseKeyRegs = serializationManager.ReadValueOrThrow<KeyBindingRegistration[]>(BaseKeyRegsNode);
|
||||
var baseKeyRegs = serializationManager.Read<KeyBindingRegistration[]>(BaseKeyRegsNode);
|
||||
|
||||
foreach (var reg in baseKeyRegs)
|
||||
{
|
||||
@@ -526,7 +526,7 @@ namespace Robust.Client.Input
|
||||
|
||||
if (userData && robustMapping.TryGet("leaveEmpty", out var node))
|
||||
{
|
||||
var leaveEmpty = serializationManager.ReadValueOrThrow<BoundKeyFunction[]>(node);
|
||||
var leaveEmpty = serializationManager.Read<BoundKeyFunction[]>(node);
|
||||
|
||||
if (leaveEmpty.Length > 0)
|
||||
{
|
||||
|
||||
134
Robust.Client/Physics/GridFixtureSystem.cs
Normal file
134
Robust.Client/Physics/GridFixtureSystem.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Physics
|
||||
{
|
||||
internal sealed class GridFixtureSystem : SharedGridFixtureSystem
|
||||
{
|
||||
public bool EnableDebug
|
||||
{
|
||||
get => _enableDebug;
|
||||
set
|
||||
{
|
||||
if (_enableDebug == value) return;
|
||||
|
||||
_enableDebug = value;
|
||||
var overlayManager = IoCManager.Resolve<IOverlayManager>();
|
||||
|
||||
if (_enableDebug)
|
||||
{
|
||||
var overlay = new GridSplitNodeOverlay(EntityManager, IoCManager.Resolve<IMapManager>(), this);
|
||||
overlayManager.AddOverlay(overlay);
|
||||
RaiseNetworkEvent(new RequestGridNodesMessage());
|
||||
}
|
||||
else
|
||||
{
|
||||
overlayManager.RemoveOverlay<GridSplitNodeOverlay>();
|
||||
RaiseNetworkEvent(new StopGridNodesMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableDebug = false;
|
||||
private readonly Dictionary<EntityUid, Dictionary<Vector2i, List<List<Vector2i>>>> _nodes = new();
|
||||
private readonly Dictionary<EntityUid, List<(Vector2, Vector2)>> _connections = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeNetworkEvent<ChunkSplitDebugMessage>(OnDebugMessage);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_nodes.Clear();
|
||||
_connections.Clear();
|
||||
}
|
||||
|
||||
private void OnDebugMessage(ChunkSplitDebugMessage ev)
|
||||
{
|
||||
if (!_enableDebug) return;
|
||||
|
||||
_nodes[ev.Grid] = ev.Nodes;
|
||||
_connections[ev.Grid] = ev.Connections;
|
||||
}
|
||||
|
||||
private sealed class GridSplitNodeOverlay : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
private IEntityManager _entManager;
|
||||
private IMapManager _mapManager;
|
||||
private GridFixtureSystem _system;
|
||||
|
||||
public GridSplitNodeOverlay(IEntityManager entManager, IMapManager mapManager, GridFixtureSystem system)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_mapManager = mapManager;
|
||||
_system = system;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
foreach (var iGrid in _mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
// May not have received nodes yet.
|
||||
if (!_system._nodes.TryGetValue(iGrid.GridEntityId, out var nodes)) continue;
|
||||
|
||||
var gridXform = xformQuery.GetComponent(iGrid.GridEntityId);
|
||||
worldHandle.SetTransform(gridXform.WorldMatrix);
|
||||
var grid = (MapGrid)iGrid;
|
||||
var chunkEnumerator = grid.GetMapChunks(args.WorldBounds);
|
||||
|
||||
while (chunkEnumerator.MoveNext(out var chunk))
|
||||
{
|
||||
if (!nodes.TryGetValue(chunk.Indices, out var chunkNodes)) continue;
|
||||
|
||||
for (var i = 0; i < chunkNodes.Count; i++)
|
||||
{
|
||||
var group = chunkNodes[i];
|
||||
var offset = chunk.Indices * chunk.ChunkSize;
|
||||
var color = GetColor(chunk, i);
|
||||
|
||||
foreach (var index in group)
|
||||
{
|
||||
worldHandle.DrawRect(new Box2(offset + index, offset + index + 1).Enlarged(-0.1f), color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var connections = _system._connections[iGrid.GridEntityId];
|
||||
|
||||
foreach (var (start, end) in connections)
|
||||
{
|
||||
worldHandle.DrawLine(start, end, Color.Aquamarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Color GetColor(MapChunk chunk, int index)
|
||||
{
|
||||
// Just want something that doesn't give similar indices at 0,0 but is also deterministic.
|
||||
// Add an offset to yIndex so we at least have some colour that isn't grey at 0,0
|
||||
var actualIndex = chunk.Indices.X * 20 + (chunk.Indices.Y + 20) * 35 + index * 50;
|
||||
|
||||
var red = (byte) (actualIndex % 255);
|
||||
var green = (byte) (actualIndex * 20 % 255);
|
||||
var blue = (byte) (actualIndex * 30 % 255);
|
||||
|
||||
return new Color(red, green, blue, 85);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Robust.Client/Physics/GridSplitVisualsCommand.cs
Normal file
18
Robust.Client/Physics/GridSplitVisualsCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Physics;
|
||||
|
||||
public sealed class GridSplitVisualCommand : IConsoleCommand
|
||||
{
|
||||
public string Command => SharedGridFixtureSystem.ShowGridNodesCommand;
|
||||
public string Description => "Shows the nodes for grid split purposes";
|
||||
public string Help => $"{Command}";
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var system = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<GridFixtureSystem>();
|
||||
system.EnableDebug ^= true;
|
||||
shell.WriteLine($"Toggled gridsplit node visuals");
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace Robust.Client.Physics
|
||||
// Also need to suss out having the client build the island anyway and just... not solving it?
|
||||
foreach (var body in AwakeBodies)
|
||||
{
|
||||
if (body.Island || body.LinearVelocity.Length > _linSleepTolerance / 2f || body.AngularVelocity > _angSleepTolerance / 2f) continue;
|
||||
if (body.LinearVelocity.Length > _linSleepTolerance / 2f || body.AngularVelocity > _angSleepTolerance / 2f) continue;
|
||||
body.SleepTime += frameTime;
|
||||
if (body.SleepTime > _timeToSleep)
|
||||
{
|
||||
|
||||
@@ -418,7 +418,7 @@ namespace Robust.Client.Placement
|
||||
if (!IsActive || !Eraser) return;
|
||||
if (Hijack != null && Hijack.HijackDeletion(entity)) return;
|
||||
|
||||
var msg = NetworkManager.CreateNetMessage<MsgPlacement>();
|
||||
var msg = new MsgPlacement();
|
||||
msg.PlaceType = PlacementManagerMessage.RequestEntRemove;
|
||||
msg.EntityUid = entity;
|
||||
NetworkManager.ClientSendMessage(msg);
|
||||
@@ -426,7 +426,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
public void HandleRectDeletion(EntityCoordinates start, Box2 rect)
|
||||
{
|
||||
var msg = NetworkManager.CreateNetMessage<MsgPlacement>();
|
||||
var msg = new MsgPlacement();
|
||||
msg.PlaceType = PlacementManagerMessage.RequestRectRemove;
|
||||
msg.EntityCoordinates = new EntityCoordinates(StartPoint.EntityId, rect.BottomLeft);
|
||||
msg.RectSize = rect.Size;
|
||||
@@ -748,7 +748,7 @@ namespace Robust.Client.Placement
|
||||
_pendingTileChanges.Add(tuple);
|
||||
}
|
||||
|
||||
var message = NetworkManager.CreateNetMessage<MsgPlacement>();
|
||||
var message = new MsgPlacement();
|
||||
message.PlaceType = PlacementManagerMessage.RequestPlacement;
|
||||
|
||||
message.Align = CurrentMode.ModeName;
|
||||
|
||||
@@ -170,10 +170,10 @@ namespace Robust.Client.Placement
|
||||
/// </summary>
|
||||
public TileRef GetTileRef(EntityCoordinates coordinates)
|
||||
{
|
||||
var mapCoords = coordinates.ToMap(pManager.EntityManager);
|
||||
var gridId = coordinates.GetGridId(pManager.EntityManager);
|
||||
var gridUid = coordinates.GetGridUid(pManager.EntityManager);
|
||||
return gridId.IsValid() ? pManager.MapManager.GetGrid(gridId).GetTileRef(MouseCoords)
|
||||
: new TileRef(mapCoords.MapId, gridId,
|
||||
: new TileRef(gridId, gridUid.GetValueOrDefault(),
|
||||
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager), Tile.Empty);
|
||||
}
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Robust.Client.Player
|
||||
{
|
||||
LocalPlayer = new LocalPlayer();
|
||||
|
||||
var msgList = _network.CreateNetMessage<MsgPlayerListReq>();
|
||||
var msgList = new MsgPlayerListReq();
|
||||
// message is empty
|
||||
_network.ClientSendMessage(msgList);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Robust.Client.Prototypes
|
||||
#if !FULL_RELEASE
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgReloadPrototypes>();
|
||||
var msg = new MsgReloadPrototypes();
|
||||
msg.Paths = _reloadQueue.ToArray();
|
||||
_netManager.ClientSendMessage(msg);
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Robust.Client.ResourceManagement
|
||||
{
|
||||
private static readonly float[] OneArray = {1};
|
||||
|
||||
public override ResourcePath? Fallback => new("/Textures/error.rsi");
|
||||
|
||||
private static readonly JsonSerializerOptions SerializerOptions =
|
||||
new JsonSerializerOptions(JsonSerializerDefaults.Web)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
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.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
@@ -16,26 +16,40 @@ namespace Robust.Client.Serialization
|
||||
[TypeSerializer]
|
||||
public sealed class AppearanceVisualizerSerializer : ITypeSerializer<AppearanceVisualizer, MappingDataNode>
|
||||
{
|
||||
public DeserializationResult Read(ISerializationManager serializationManager, MappingDataNode node,
|
||||
public AppearanceVisualizer Read(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
ISerializationContext? context = null, AppearanceVisualizer? value = null)
|
||||
{
|
||||
Type? type = null;
|
||||
if (!node.TryGet("type", out var typeNode))
|
||||
throw new InvalidMappingException("No type specified for AppearanceVisualizer!");
|
||||
{
|
||||
if (value == null)
|
||||
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!");
|
||||
type = value.GetType();
|
||||
}
|
||||
else
|
||||
{
|
||||
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!");
|
||||
type = IoCManager.Resolve<IReflectionManager>()
|
||||
.YamlTypeTagLookup(typeof(AppearanceVisualizer), typeValueDataNode.Value);
|
||||
if (type == null)
|
||||
throw new InvalidMappingException(
|
||||
$"Invalid type {typeValueDataNode.Value} specified for AppearanceVisualizer!");
|
||||
|
||||
if(value != null && !type.IsInstanceOfType(value))
|
||||
{
|
||||
throw new InvalidMappingException(
|
||||
$"Specified Type does not match type of provided Value for AppearanceVisualizer! (TypeOfValue: {value.GetType()}, ProvidedValue: {type})");
|
||||
}
|
||||
}
|
||||
|
||||
var newNode = node.Copy();
|
||||
newNode.Remove("type");
|
||||
return serializationManager.Read(type, newNode, context, skipHook);
|
||||
return (AppearanceVisualizer) serializationManager.Read(type, newNode, context, skipHook, value)!;
|
||||
}
|
||||
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
||||
|
||||
395
Robust.Client/UserInterface/Controls/ColorSelectorSliders.cs
Normal file
395
Robust.Client/UserInterface/Controls/ColorSelectorSliders.cs
Normal file
@@ -0,0 +1,395 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
// condensed version of the original ColorSlider set
|
||||
public sealed class ColorSelectorSliders : Control
|
||||
{
|
||||
public Color Color
|
||||
{
|
||||
get => _currentColor;
|
||||
set
|
||||
{
|
||||
_updating = true;
|
||||
_currentColor = value;
|
||||
|
||||
Update();
|
||||
_updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
public ColorSelectorType SelectorType
|
||||
{
|
||||
get => _currentType;
|
||||
set
|
||||
{
|
||||
_updating = true;
|
||||
_currentType = value;
|
||||
|
||||
UpdateType();
|
||||
Update();
|
||||
_updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAlphaVisible
|
||||
{
|
||||
get => _isAlphaVisible;
|
||||
set
|
||||
{
|
||||
_isAlphaVisible = value;
|
||||
|
||||
_alphaSliderBox.Visible = _isAlphaVisible;
|
||||
}
|
||||
}
|
||||
|
||||
public Action<Color>? OnColorChanged;
|
||||
|
||||
private bool _updating = false;
|
||||
private Color _currentColor = Color.White;
|
||||
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
|
||||
private bool _isAlphaVisible = false;
|
||||
|
||||
private ColorableSlider _topColorSlider;
|
||||
private ColorableSlider _middleColorSlider;
|
||||
private ColorableSlider _bottomColorSlider;
|
||||
private Slider _alphaSlider;
|
||||
|
||||
private BoxContainer _alphaSliderBox = new();
|
||||
|
||||
private FloatSpinBox _topInputBox;
|
||||
private FloatSpinBox _middleInputBox;
|
||||
private FloatSpinBox _bottomInputBox;
|
||||
private FloatSpinBox _alphaInputBox;
|
||||
|
||||
private Label _topSliderLabel = new();
|
||||
private Label _middleSliderLabel = new();
|
||||
private Label _bottomSliderLabel = new();
|
||||
private Label _alphaSliderLabel = new();
|
||||
|
||||
private OptionButton _typeSelector;
|
||||
private List<ColorSelectorType> _types = new();
|
||||
|
||||
public ColorSelectorSliders()
|
||||
{
|
||||
_topColorSlider = new ColorableSlider
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
MaxValue = 1.0f
|
||||
};
|
||||
|
||||
_middleColorSlider = new ColorableSlider
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
MaxValue = 1.0f
|
||||
};
|
||||
|
||||
_bottomColorSlider = new ColorableSlider
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
MaxValue = 1.0f
|
||||
};
|
||||
|
||||
_alphaSlider = new Slider
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
MaxValue = 1.0f,
|
||||
};
|
||||
|
||||
_topColorSlider.OnValueChanged += _ => { OnColorSet(); };
|
||||
_middleColorSlider.OnValueChanged += _ => { OnColorSet(); };
|
||||
_bottomColorSlider.OnValueChanged += _ => { OnColorSet(); };
|
||||
_alphaSlider.OnValueChanged += _ => { OnColorSet(); };
|
||||
|
||||
_topInputBox = new FloatSpinBox(1f, 2)
|
||||
{
|
||||
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Top)
|
||||
};
|
||||
|
||||
_middleInputBox = new FloatSpinBox(1f, 2)
|
||||
{
|
||||
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Middle)
|
||||
};
|
||||
|
||||
_bottomInputBox = new FloatSpinBox(1f, 2)
|
||||
{
|
||||
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Bottom)
|
||||
};
|
||||
|
||||
_alphaInputBox = new FloatSpinBox(1f, 2)
|
||||
{
|
||||
IsValid = value => IsSpinBoxValid(value, ColorSliderOrder.Alpha)
|
||||
};
|
||||
|
||||
_topInputBox.OnValueChanged += value =>
|
||||
{
|
||||
_topColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Top);
|
||||
};
|
||||
|
||||
_middleInputBox.OnValueChanged += value =>
|
||||
{
|
||||
_middleColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Middle);
|
||||
};
|
||||
|
||||
_bottomInputBox.OnValueChanged += value =>
|
||||
{
|
||||
_bottomColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Bottom);
|
||||
};
|
||||
|
||||
_alphaInputBox.OnValueChanged += value =>
|
||||
{
|
||||
_alphaSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Alpha);
|
||||
};
|
||||
|
||||
_alphaSliderLabel.Text = Loc.GetString("color-selector-sliders-alpha");
|
||||
|
||||
_typeSelector = new OptionButton();
|
||||
foreach (var ty in Enum.GetValues<ColorSelectorType>())
|
||||
{
|
||||
_typeSelector.AddItem(Loc.GetString($"color-selector-sliders-{ty.ToString().ToLower()}"));
|
||||
_types.Add(ty);
|
||||
}
|
||||
|
||||
_typeSelector.OnItemSelected += args =>
|
||||
{
|
||||
SelectorType = _types[args.Id];
|
||||
_typeSelector.Select(args.Id);
|
||||
};
|
||||
|
||||
// TODO: Maybe some engine widgets could be laid out in XAML?
|
||||
|
||||
var rootBox = new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical
|
||||
};
|
||||
AddChild(rootBox);
|
||||
|
||||
var headerBox = new BoxContainer();
|
||||
rootBox.AddChild(headerBox);
|
||||
|
||||
headerBox.AddChild(_typeSelector);
|
||||
|
||||
var bodyBox = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical
|
||||
};
|
||||
|
||||
// pita
|
||||
var topSliderBox = new BoxContainer();
|
||||
|
||||
topSliderBox.AddChild(_topSliderLabel);
|
||||
topSliderBox.AddChild(_topColorSlider);
|
||||
topSliderBox.AddChild(_topInputBox);
|
||||
|
||||
var middleSliderBox = new BoxContainer();
|
||||
|
||||
middleSliderBox.AddChild(_middleSliderLabel);
|
||||
middleSliderBox.AddChild(_middleColorSlider);
|
||||
middleSliderBox.AddChild(_middleInputBox);
|
||||
|
||||
var bottomSliderBox = new BoxContainer();
|
||||
|
||||
bottomSliderBox.AddChild(_bottomSliderLabel);
|
||||
bottomSliderBox.AddChild(_bottomColorSlider);
|
||||
bottomSliderBox.AddChild(_bottomInputBox);
|
||||
|
||||
_alphaSliderBox.Visible = IsAlphaVisible;
|
||||
_alphaSliderBox.AddChild(_alphaSliderLabel);
|
||||
_alphaSliderBox.AddChild(_alphaSlider);
|
||||
_alphaSliderBox.AddChild(_alphaInputBox);
|
||||
|
||||
bodyBox.AddChild(topSliderBox);
|
||||
bodyBox.AddChild(middleSliderBox);
|
||||
bodyBox.AddChild(bottomSliderBox);
|
||||
bodyBox.AddChild(_alphaSliderBox);
|
||||
|
||||
rootBox.AddChild(bodyBox);
|
||||
|
||||
_updating = true;
|
||||
UpdateType();
|
||||
Update();
|
||||
_updating = false;
|
||||
}
|
||||
|
||||
private void UpdateType()
|
||||
{
|
||||
(string topLabel, string middleLabel, string bottomLabel) labels = GetSliderLabels();
|
||||
|
||||
_topSliderLabel.Text = labels.topLabel;
|
||||
_middleSliderLabel.Text = labels.middleLabel;
|
||||
_bottomSliderLabel.Text = labels.bottomLabel;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_topColorSlider.SetColor(_currentColor);
|
||||
_middleColorSlider.SetColor(_currentColor);
|
||||
_bottomColorSlider.SetColor(_currentColor);
|
||||
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
_topColorSlider.Value = Color.R;
|
||||
_middleColorSlider.Value = Color.G;
|
||||
_bottomColorSlider.Value = Color.B;
|
||||
|
||||
_topInputBox.Value = Color.R * 255.0f;
|
||||
_middleInputBox.Value = Color.G * 255.0f;
|
||||
_bottomInputBox.Value = Color.B * 255.0f;
|
||||
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
Vector4 color = Color.ToHsv(Color);
|
||||
|
||||
// dumb workaround because the formula for
|
||||
// HSV calculation results in a negative
|
||||
// number in any value past 300 degrees
|
||||
if (color.X > 0)
|
||||
{
|
||||
_topColorSlider.Value = color.X;
|
||||
_topInputBox.Value = color.X * 360.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
_topInputBox.Value = _topColorSlider.Value * 360.0f;
|
||||
}
|
||||
|
||||
_middleColorSlider.Value = color.Y;
|
||||
_bottomColorSlider.Value = color.Z;
|
||||
|
||||
_middleInputBox.Value = color.Y * 100.0f;
|
||||
_bottomInputBox.Value = color.Z * 100.0f;
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
_alphaSlider.Value = Color.A;
|
||||
_alphaInputBox.Value = Color.A * 100.0f;
|
||||
}
|
||||
|
||||
private bool IsSpinBoxValid(float value, ColorSliderOrder ordering)
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ordering == ColorSliderOrder.Alpha)
|
||||
{
|
||||
return value <= 100.0f;
|
||||
}
|
||||
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
return value <= byte.MaxValue;
|
||||
case ColorSelectorType.Hsv:
|
||||
switch (ordering)
|
||||
{
|
||||
case ColorSliderOrder.Top:
|
||||
return value <= 360.0f;
|
||||
default:
|
||||
return value <= 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private (string, string, string) GetSliderLabels()
|
||||
{
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
return (
|
||||
Loc.GetString("color-selector-sliders-red"),
|
||||
Loc.GetString("color-selector-sliders-green"),
|
||||
Loc.GetString("color-selector-sliders-blue")
|
||||
);
|
||||
case ColorSelectorType.Hsv:
|
||||
return (
|
||||
Loc.GetString("color-selector-sliders-hue"),
|
||||
Loc.GetString("color-selector-sliders-saturation"),
|
||||
Loc.GetString("color-selector-sliders-value")
|
||||
);
|
||||
}
|
||||
|
||||
return ("ERR", "ERR", "ERR");
|
||||
}
|
||||
|
||||
private float GetColorValueDivisor(ColorSliderOrder order)
|
||||
{
|
||||
if (order == ColorSliderOrder.Alpha)
|
||||
{
|
||||
return 100.0f;
|
||||
}
|
||||
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
return 255.0f;
|
||||
case ColorSelectorType.Hsv:
|
||||
switch (order)
|
||||
{
|
||||
case ColorSliderOrder.Top:
|
||||
return 360.0f;
|
||||
default:
|
||||
return 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private void OnColorSet()
|
||||
{
|
||||
// stack overflow otherwise due to value sets
|
||||
if (_updating)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
Color rgbColor = new Color(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
|
||||
|
||||
_currentColor = rgbColor;
|
||||
Update();
|
||||
|
||||
OnColorChanged!(rgbColor);
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
Color hsvColor = Color.FromHsv(new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value));
|
||||
|
||||
_currentColor = hsvColor;
|
||||
Update();
|
||||
|
||||
OnColorChanged!(hsvColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private enum ColorSliderOrder
|
||||
{
|
||||
Top,
|
||||
Middle,
|
||||
Bottom,
|
||||
Alpha
|
||||
}
|
||||
|
||||
public enum ColorSelectorType
|
||||
{
|
||||
Rgb,
|
||||
Hsv,
|
||||
}
|
||||
}
|
||||
81
Robust.Client/UserInterface/Controls/ColorableSlider.cs
Normal file
81
Robust.Client/UserInterface/Controls/ColorableSlider.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
public sealed class ColorableSlider : Slider
|
||||
{
|
||||
public const string StylePropertyFillWhite = "fillWhite"; // needs to be filled with white
|
||||
public const string StylePropertyBackgroundWhite = "backgroundWhite"; // also needs to be filled with white
|
||||
|
||||
public Color Color { get; private set; } = Color.White;
|
||||
public PartSelector Part
|
||||
{
|
||||
get => _currentPartSelector;
|
||||
set
|
||||
{
|
||||
_currentPartSelector = value;
|
||||
|
||||
UpdateStyleBoxes();
|
||||
}
|
||||
}
|
||||
|
||||
private PartSelector _currentPartSelector = PartSelector.Background;
|
||||
|
||||
public void SetColor(Color color)
|
||||
{
|
||||
Color = color;
|
||||
switch (Part)
|
||||
{
|
||||
case PartSelector.Fill:
|
||||
_fillPanel.Modulate = Color;
|
||||
break;
|
||||
case PartSelector.Background:
|
||||
_backgroundPanel.Modulate = Color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateStyleBoxes()
|
||||
{
|
||||
StyleBox? GetStyleBox(string name)
|
||||
{
|
||||
if (TryGetStyleProperty<StyleBox>(name, out var box))
|
||||
{
|
||||
return box;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
string backBox = StylePropertyBackground;
|
||||
string fillBox = StylePropertyFill;
|
||||
|
||||
switch (Part)
|
||||
{
|
||||
case PartSelector.Fill:
|
||||
fillBox = StylePropertyFillWhite;
|
||||
_fillPanel.Modulate = Color;
|
||||
|
||||
break;
|
||||
case PartSelector.Background:
|
||||
backBox = StylePropertyBackgroundWhite;
|
||||
|
||||
_fillPanel.Modulate = Color.Transparent; // make this transparent
|
||||
_backgroundPanel.Modulate = Color;
|
||||
break;
|
||||
}
|
||||
|
||||
_backgroundPanel.PanelOverride = BackgroundStyleBoxOverride ?? GetStyleBox(backBox);
|
||||
_foregroundPanel.PanelOverride = ForegroundStyleBoxOverride ?? GetStyleBox(StylePropertyForeground);
|
||||
_fillPanel.PanelOverride = FillStyleBoxOverride ?? GetStyleBox(fillBox);
|
||||
_grabber.PanelOverride = GrabberStyleBoxOverride ?? GetStyleBox(StylePropertyGrabber);
|
||||
}
|
||||
|
||||
public enum PartSelector
|
||||
{
|
||||
Fill,
|
||||
Background
|
||||
}
|
||||
}
|
||||
@@ -65,18 +65,22 @@ namespace Robust.Client.UserInterface.Controls
|
||||
get => _text;
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
value = "";
|
||||
}
|
||||
// Save cursor position or -1 for end
|
||||
var cursorTarget = CursorPosition == _text.Length ? -1 : CursorPosition;
|
||||
|
||||
if (!SetText(value))
|
||||
if (!InternalSetText(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cursorPosition = 0;
|
||||
_selectionStart = 0;
|
||||
var clamped = MathHelper.Clamp(cursorTarget == -1 ? _text.Length : cursorTarget, 0, _text.Length);
|
||||
while (clamped < _text.Length && !Rune.TryGetRuneAt(_text, clamped, out _))
|
||||
{
|
||||
clamped++;
|
||||
}
|
||||
|
||||
_cursorPosition = clamped;
|
||||
_selectionStart = _cursorPosition;
|
||||
_updatePseudoClass();
|
||||
}
|
||||
}
|
||||
@@ -203,7 +207,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var lower = SelectionLower;
|
||||
var newContents = Text[..lower] + text + Text[SelectionUpper..];
|
||||
|
||||
if (!SetText(newContents))
|
||||
if (!InternalSetText(newContents))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -216,7 +220,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// <remarks>
|
||||
/// Does not fix cursor positions, those will have to be adjusted manually.
|
||||
/// </remarks>>
|
||||
protected bool SetText(string newText)
|
||||
private bool InternalSetText(string newText)
|
||||
{
|
||||
if (IsValid != null && !IsValid(newText))
|
||||
{
|
||||
|
||||
@@ -13,10 +13,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string StylePropertyFill = "fill";
|
||||
public const string StylePropertyGrabber = "grabber";
|
||||
|
||||
private readonly PanelContainer _foregroundPanel;
|
||||
private readonly PanelContainer _backgroundPanel;
|
||||
private readonly PanelContainer _fillPanel;
|
||||
private readonly PanelContainer _grabber;
|
||||
protected readonly PanelContainer _foregroundPanel;
|
||||
protected readonly PanelContainer _backgroundPanel;
|
||||
protected readonly PanelContainer _fillPanel;
|
||||
protected readonly PanelContainer _grabber;
|
||||
|
||||
private bool _grabbed;
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
UpdateStyleBoxes();
|
||||
}
|
||||
|
||||
private void UpdateStyleBoxes()
|
||||
protected virtual void UpdateStyleBoxes()
|
||||
{
|
||||
StyleBox? GetStyleBox(string name)
|
||||
{
|
||||
@@ -182,7 +182,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
_backgroundPanel.PanelOverride = BackgroundStyleBoxOverride ?? GetStyleBox(StylePropertyBackground);
|
||||
_foregroundPanel.PanelOverride = BackgroundStyleBoxOverride ?? GetStyleBox(StylePropertyForeground);
|
||||
_foregroundPanel.PanelOverride = ForegroundStyleBoxOverride ?? GetStyleBox(StylePropertyForeground);
|
||||
_fillPanel.PanelOverride = FillStyleBoxOverride ?? GetStyleBox(StylePropertyFill);
|
||||
_grabber.PanelOverride = GrabberStyleBoxOverride ?? GetStyleBox(StylePropertyGrabber);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
{
|
||||
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
|
||||
mouseWorldMap.Position);
|
||||
tile = new TileRef(mouseWorldMap.MapId, GridId.Invalid,
|
||||
tile = new TileRef(GridId.Invalid, EntityUid.Invalid,
|
||||
mouseGridPos.ToVector2i(_entityManager, _mapManager), Tile.Empty);
|
||||
}
|
||||
|
||||
|
||||
@@ -228,7 +228,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
foreach (var prototype in prototypeManager.EnumeratePrototypes<EntityPrototype>())
|
||||
{
|
||||
if (prototype.Abstract)
|
||||
if (prototype.NoSpawn || prototype.Abstract)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
Text = value switch
|
||||
{
|
||||
IPrototype prototype => prototype.ID,
|
||||
ViewVariablesBlobMembers.PrototypeReferenceToken token => token.ID,
|
||||
ViewVariablesBlobMembers.PrototypeReferenceToken token => token.ID ?? string.Empty,
|
||||
_ => string.Empty
|
||||
},
|
||||
Editable = !ReadOnly
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
var lineEdit = new LineEdit
|
||||
{
|
||||
Text = (string) value!,
|
||||
Text = (string) (value ?? ""),
|
||||
Editable = !ReadOnly,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
{
|
||||
ViewVariablesTraitMembers.CreateMemberGroupHeader(
|
||||
ref first,
|
||||
TypeAbbreviation.Abbreviate(group.Key),
|
||||
PrettyPrint.PrintUserFacingTypeShort(group.Key, 2),
|
||||
clientVBox);
|
||||
|
||||
foreach (var control in group)
|
||||
@@ -206,7 +206,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
|
||||
foreach (var component in componentList)
|
||||
{
|
||||
var button = new Button {Text = TypeAbbreviation.Abbreviate(component.GetType()), TextAlign = Label.AlignMode.Left};
|
||||
var button = new Button {Text = PrettyPrint.PrintUserFacingTypeShort(component.GetType(), 2), TextAlign = Label.AlignMode.Left};
|
||||
var removeButton = new TextureButton()
|
||||
{
|
||||
StyleClasses = { DefaultWindow.StyleClassWindowCloseButton },
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
{
|
||||
CreateMemberGroupHeader(
|
||||
ref first,
|
||||
TypeAbbreviation.Abbreviate(group.Key),
|
||||
PrettyPrint.PrintUserFacingTypeShort(group.Key, 2),
|
||||
_memberList);
|
||||
|
||||
foreach (var control in group)
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace Robust.Client.ViewVariables
|
||||
Editable = access == VVAccess.ReadWrite,
|
||||
Name = memberInfo.Name,
|
||||
Type = memberType.AssemblyQualifiedName,
|
||||
TypePretty = TypeAbbreviation.Abbreviate(memberType),
|
||||
TypePretty = PrettyPrint.PrintUserFacingTypeShort(memberType, 2),
|
||||
Value = value
|
||||
};
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ namespace Robust.Client.ViewVariables
|
||||
|
||||
public Task<ViewVariablesRemoteSession> RequestSession(ViewVariablesObjectSelector selector)
|
||||
{
|
||||
var msg = _netManager.CreateNetMessage<MsgViewVariablesReqSession>();
|
||||
var msg = new MsgViewVariablesReqSession();
|
||||
msg.Selector = selector;
|
||||
msg.RequestId = _nextReqId++;
|
||||
_netManager.ClientSendMessage(msg);
|
||||
@@ -293,7 +293,7 @@ namespace Robust.Client.ViewVariables
|
||||
throw new ArgumentException("Session is closed", nameof(session));
|
||||
}
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgViewVariablesReqData>();
|
||||
var msg = new MsgViewVariablesReqData();
|
||||
var reqId = msg.RequestId = _nextReqId++;
|
||||
msg.RequestMeta = meta;
|
||||
msg.SessionId = session.SessionId;
|
||||
@@ -315,7 +315,7 @@ namespace Robust.Client.ViewVariables
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var closeMsg = _netManager.CreateNetMessage<MsgViewVariablesCloseSession>();
|
||||
var closeMsg = new MsgViewVariablesCloseSession();
|
||||
closeMsg.SessionId = session.SessionId;
|
||||
_netManager.ClientSendMessage(closeMsg);
|
||||
}
|
||||
@@ -332,7 +332,7 @@ namespace Robust.Client.ViewVariables
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var msg = _netManager.CreateNetMessage<MsgViewVariablesModifyRemote>();
|
||||
var msg = new MsgViewVariablesModifyRemote();
|
||||
msg.SessionId = session.SessionId;
|
||||
msg.ReinterpretValue = reinterpretValue;
|
||||
msg.PropertyIndex = propertyIndex;
|
||||
|
||||
@@ -31,6 +31,7 @@ using Robust.Shared.Network;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Serilog.Debugging;
|
||||
@@ -87,6 +88,7 @@ namespace Robust.Server
|
||||
[Dependency] private readonly ILocalizationManagerInternal _loc = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _netCfgMan = default!;
|
||||
[Dependency] private readonly IServerConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IParallelManagerInternal _parallelMgr = default!;
|
||||
|
||||
private readonly Stopwatch _uptimeStopwatch = new();
|
||||
|
||||
@@ -184,6 +186,8 @@ namespace Robust.Server
|
||||
|
||||
ProfileOptSetup.Setup(_config);
|
||||
|
||||
_parallelMgr.Initialize();
|
||||
|
||||
//Sets up Logging
|
||||
_logHandlerFactory = logHandlerFactory;
|
||||
|
||||
@@ -338,7 +342,7 @@ namespace Robust.Server
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
prototypeManager.Initialize();
|
||||
prototypeManager.LoadDirectory(Options.PrototypeDirectory);
|
||||
prototypeManager.Resync();
|
||||
prototypeManager.ResolveResults();
|
||||
|
||||
_consoleHost.Initialize();
|
||||
_entityManager.Startup();
|
||||
|
||||
@@ -312,15 +312,20 @@ namespace Robust.Server.Bql
|
||||
{
|
||||
var radius = (float)(double)arguments[0];
|
||||
var entityLookup = EntitySystem.Get<EntityLookupSystem>();
|
||||
var xformQuery = IoCManager.Resolve<IEntityManager>().GetEntityQuery<TransformComponent>();
|
||||
var distinct = new HashSet<EntityUid>();
|
||||
|
||||
foreach (var uid in input)
|
||||
{
|
||||
foreach (var near in entityLookup.GetEntitiesInRange(xformQuery.GetComponent(uid).Coordinates,
|
||||
radius))
|
||||
{
|
||||
if (!distinct.Add(near)) continue;
|
||||
yield return near;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this a foreach and reduce LINQ chain because it'll allocate a LOT
|
||||
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
|
||||
return input.Where(entityManager.HasComponent<TransformComponent>)
|
||||
.SelectMany(e =>
|
||||
entityLookup.GetEntitiesInRange(entityManager.GetComponent<TransformComponent>(e).Coordinates,
|
||||
radius))
|
||||
.Select(x => x) // Sloth's fault.
|
||||
.Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@@ -114,17 +115,19 @@ namespace Robust.Server.Console.Commands
|
||||
{
|
||||
public string Command => "loadbp";
|
||||
public string Description => "Loads a blueprint from disk into the game.";
|
||||
public string Help => "loadbp <MapID> <Path> [storeUids]";
|
||||
public string Help => "loadbp <MapID> <Path> [storeUids] [x y] [rotation]";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 2)
|
||||
if (args.Length != 2 && args.Length != 3 && args.Length != 5 && args.Length != 6)
|
||||
{
|
||||
shell.WriteError("Must have either 2, 3, 5, or 6 arguments.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var intMapId))
|
||||
{
|
||||
shell.WriteError($"{args[0]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -147,7 +150,41 @@ namespace Robust.Server.Console.Commands
|
||||
var loadOptions = new MapLoadOptions();
|
||||
if (args.Length > 2)
|
||||
{
|
||||
loadOptions.StoreMapUids = bool.Parse(args[2]);
|
||||
if (!Boolean.TryParse(args[2], out var storeUids))
|
||||
{
|
||||
shell.WriteError($"{args[2]} is not a valid boolean..");
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.StoreMapUids = storeUids;
|
||||
}
|
||||
|
||||
if (args.Length >= 5)
|
||||
{
|
||||
if (!int.TryParse(args[3], out var x))
|
||||
{
|
||||
shell.WriteError($"{args[3]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[4], out var y))
|
||||
{
|
||||
shell.WriteError($"{args[4]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.Offset = new Vector2(x, y);
|
||||
}
|
||||
|
||||
if (args.Length == 6)
|
||||
{
|
||||
if (!float.TryParse(args[5], out var rotation))
|
||||
{
|
||||
shell.WriteError($"{args[5]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.Rotation = new Angle(rotation);
|
||||
}
|
||||
|
||||
var mapLoader = IoCManager.Resolve<IMapLoader>();
|
||||
@@ -191,15 +228,20 @@ namespace Robust.Server.Console.Commands
|
||||
{
|
||||
public string Command => "loadmap";
|
||||
public string Description => "Loads a map from disk into the game.";
|
||||
public string Help => "loadmap <MapID> <Path>";
|
||||
public string Help => "loadmap <MapID> <Path> [x y] [rotation]";
|
||||
|
||||
public void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
return;
|
||||
if (args.Length != 2 && args.Length != 4 && args.Length != 5)
|
||||
{
|
||||
shell.WriteError($"Must have either 2, 4, or 5 arguments.");
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[0], out var intMapId))
|
||||
{
|
||||
shell.WriteError($"{args[0]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(intMapId);
|
||||
|
||||
@@ -217,7 +259,36 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
IoCManager.Resolve<IMapLoader>().LoadMap(mapId, args[1]);
|
||||
var loadOptions = new MapLoadOptions();
|
||||
|
||||
if (args.Length >= 3)
|
||||
{
|
||||
if (!int.TryParse(args[2], out var x))
|
||||
{
|
||||
shell.WriteError($"{args[2]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!int.TryParse(args[3], out var y))
|
||||
{
|
||||
shell.WriteError($"{args[3]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
loadOptions.Offset = new Vector2(x, y);
|
||||
}
|
||||
|
||||
if (args.Length == 4)
|
||||
{
|
||||
if (!float.TryParse(args[4], out var rotation))
|
||||
{
|
||||
shell.WriteError($"{args[4]} is not a valid integer.");
|
||||
return;
|
||||
}
|
||||
|
||||
loadOptions.Rotation = new Angle(rotation);
|
||||
}
|
||||
|
||||
IoCManager.Resolve<IMapLoader>().LoadMap(mapId, args[1], loadOptions);
|
||||
|
||||
if (mapManager.MapExists(mapId))
|
||||
shell.WriteLine($"Map {mapId} has been loaded from {args[1]}.");
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
|
||||
@@ -45,9 +45,12 @@ public sealed class ScaleCommand : IConsoleCommand
|
||||
var entManager = IoCManager.Resolve<IEntityManager>();
|
||||
entManager.EventBus.RaiseLocalEvent(uid, ref @event);
|
||||
|
||||
if (entManager.TryGetComponent(uid, out SpriteComponent? spriteComponent))
|
||||
if (entManager.TryGetComponent(uid, out AppearanceComponent? appearanceComponent))
|
||||
{
|
||||
spriteComponent.Scale *= scale;
|
||||
if (!appearanceComponent.TryGetData<Vector2>(ScaleVisuals.Scale, out var oldScale))
|
||||
oldScale = Vector2.One;
|
||||
|
||||
appearanceComponent.SetData(ScaleVisuals.Scale, oldScale * scale);
|
||||
}
|
||||
|
||||
if (entManager.TryGetComponent(uid, out FixturesComponent? manager))
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Server.Console.Commands
|
||||
{
|
||||
if (args.Length != 2)
|
||||
{
|
||||
shell.WriteLine("Require 2 args for testbed!");
|
||||
shell.WriteError("Require 2 args for testbed!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,58 +61,67 @@ namespace Robust.Server.Console.Commands
|
||||
|
||||
if (!int.TryParse(args[0], out var mapInt))
|
||||
{
|
||||
shell.WriteLine($"Unable to parse map {args[0]}");
|
||||
shell.WriteError($"Unable to parse map {args[0]}");
|
||||
return;
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
if (!mapManager.MapExists(mapId))
|
||||
{
|
||||
shell.WriteLine("Unable to find map {mapId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (shell.Player == null)
|
||||
{
|
||||
shell.WriteLine("No player found");
|
||||
shell.WriteError("No player found");
|
||||
return;
|
||||
}
|
||||
|
||||
var player = (IPlayerSession) shell.Player;
|
||||
Action testbed;
|
||||
SetupPlayer(mapId, shell, player, mapManager);
|
||||
|
||||
switch (args[1])
|
||||
{
|
||||
case "boxstack":
|
||||
SetupPlayer(mapId, shell, player, mapManager);
|
||||
CreateBoxStack(mapId);
|
||||
testbed = () => CreateBoxStack(mapId);
|
||||
break;
|
||||
case "circlestack":
|
||||
SetupPlayer(mapId, shell, player, mapManager);
|
||||
CreateCircleStack(mapId);
|
||||
testbed = () => CreateCircleStack(mapId);
|
||||
break;
|
||||
case "pyramid":
|
||||
SetupPlayer(mapId, shell, player, mapManager);
|
||||
CreatePyramid(mapId);
|
||||
testbed = () => CreatePyramid(mapId);
|
||||
break;
|
||||
case "tumbler":
|
||||
SetupPlayer(mapId, shell, player, mapManager);
|
||||
CreateTumbler(mapId);
|
||||
testbed = () => CreateTumbler(mapId);
|
||||
break;
|
||||
default:
|
||||
shell.WriteLine($"testbed {args[0]} not found!");
|
||||
shell.WriteError($"testbed {args[0]} not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
Timer.Spawn(1000, () =>
|
||||
{
|
||||
if (!mapManager.MapExists(mapId)) return;
|
||||
testbed();
|
||||
});
|
||||
|
||||
shell.WriteLine($"Testbed on map {mapId}");
|
||||
}
|
||||
|
||||
private void SetupPlayer(MapId mapId, IConsoleShell shell, IPlayerSession? player, IMapManager mapManager)
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
if (!mapManager.MapExists(mapId))
|
||||
{
|
||||
mapManager.CreateMap(mapId);
|
||||
}
|
||||
|
||||
mapManager.SetMapPaused(mapId, false);
|
||||
var mapUid = mapManager.GetMapEntityIdOrThrow(mapId);
|
||||
IoCManager.Resolve<IEntityManager>().GetComponent<SharedPhysicsMapComponent>(mapUid).Gravity = new Vector2(0, -9.8f);
|
||||
|
||||
shell.ExecuteCommand("aghost");
|
||||
shell.ExecuteCommand($"tp 0 0 {mapId}");
|
||||
shell.RemoteExecuteCommand($"physics shapes");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Robust.Server.Console
|
||||
if (!NetManager.IsConnected || session is null)
|
||||
return;
|
||||
|
||||
var msg = NetManager.CreateNetMessage<MsgConCmd>();
|
||||
var msg = new MsgConCmd();
|
||||
msg.Text = command;
|
||||
NetManager.ServerSendMessage(msg, ((IPlayerSession)session).ConnectedClient);
|
||||
}
|
||||
@@ -121,7 +121,7 @@ namespace Robust.Server.Console
|
||||
private void HandleRegistrationRequest(INetChannel senderConnection)
|
||||
{
|
||||
var netMgr = IoCManager.Resolve<IServerNetManager>();
|
||||
var message = netMgr.CreateNetMessage<MsgConCmdReg>();
|
||||
var message = new MsgConCmdReg();
|
||||
|
||||
var counter = 0;
|
||||
message.Commands = new MsgConCmdReg.Command[RegisteredCommands.Count];
|
||||
@@ -154,7 +154,7 @@ namespace Robust.Server.Console
|
||||
{
|
||||
if (session != null)
|
||||
{
|
||||
var replyMsg = NetManager.CreateNetMessage<MsgConCmdAck>();
|
||||
var replyMsg = new MsgConCmdAck();
|
||||
replyMsg.Error = error;
|
||||
replyMsg.Text = text;
|
||||
NetManager.ServerSendMessage(replyMsg, session.ConnectedClient);
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedHttpListener;
|
||||
using SpaceWizards.HttpListener;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
|
||||
@@ -184,6 +184,7 @@ namespace Robust.Server.GameObjects
|
||||
}
|
||||
|
||||
_subscribedSessions.Add(session);
|
||||
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner.Owner, new BoundUIOpenedEvent(UiKey, Owner.Owner, session));
|
||||
SendMessage(new OpenBoundInterfaceMessage(), session);
|
||||
if (_lastState != null)
|
||||
{
|
||||
@@ -236,11 +237,11 @@ namespace Robust.Server.GameObjects
|
||||
public void CloseShared(IPlayerSession session)
|
||||
{
|
||||
var owner = Owner.Owner;
|
||||
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(owner, new BoundUIClosedEvent(UiKey, owner, session));
|
||||
OnClosed?.Invoke(session);
|
||||
_subscribedSessions.Remove(session);
|
||||
_playerStateOverrides.Remove(session);
|
||||
session.PlayerStatusChanged -= OnSessionOnPlayerStatusChanged;
|
||||
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(owner, new BoundUIClosedEvent(UiKey, owner, session));
|
||||
|
||||
if (_subscribedSessions.Count == 0)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
void FinishEntityLoad(EntityUid entity, IEntityLoadContext? context = null);
|
||||
|
||||
void FinishEntityInitialization(EntityUid entity);
|
||||
void FinishEntityInitialization(EntityUid entity, MetaDataComponent? meta = null);
|
||||
|
||||
void FinishEntityStartup(EntityUid entity);
|
||||
}
|
||||
|
||||
@@ -52,9 +52,9 @@ namespace Robust.Server.GameObjects
|
||||
LoadEntity(entity, context);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityInitialization(EntityUid entity)
|
||||
void IServerEntityManagerInternal.FinishEntityInitialization(EntityUid entity, MetaDataComponent? meta = null)
|
||||
{
|
||||
InitializeEntity(entity);
|
||||
InitializeEntity(entity, meta);
|
||||
}
|
||||
|
||||
void IServerEntityManagerInternal.FinishEntityStartup(EntityUid entity)
|
||||
@@ -206,7 +206,7 @@ namespace Robust.Server.GameObjects
|
||||
/// <inheritdoc />
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message)
|
||||
{
|
||||
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
var newMsg = new MsgEntity();
|
||||
newMsg.Type = EntityMessageType.SystemMessage;
|
||||
newMsg.SystemMessage = message;
|
||||
newMsg.SourceTick = _gameTiming.CurTick;
|
||||
@@ -217,7 +217,7 @@ namespace Robust.Server.GameObjects
|
||||
/// <inheritdoc />
|
||||
public void SendSystemNetworkMessage(EntityEventArgs message, INetChannel targetConnection)
|
||||
{
|
||||
var newMsg = _networkManager.CreateNetMessage<MsgEntity>();
|
||||
var newMsg = new MsgEntity();
|
||||
newMsg.Type = EntityMessageType.SystemMessage;
|
||||
newMsg.SystemMessage = message;
|
||||
newMsg.SourceTick = _gameTiming.CurTick;
|
||||
|
||||
41
Robust.Server/GameStates/PVSOverrideSystem.cs
Normal file
41
Robust.Server/GameStates/PVSOverrideSystem.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
/// <summary>
|
||||
/// Placeholder system to expose some parts of the internal <see cref="PVSSystem"/> that allows entities to ignore
|
||||
/// normal PVS rules, such that they are always sent to clients.
|
||||
/// </summary>
|
||||
public sealed partial class PVSOverrideSystem : EntitySystem
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly PVSSystem _pvs = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that an entity is always sent to every client. Overrides any client-specific overrides.
|
||||
/// </summary>
|
||||
public void AddGlobalOverride(EntityUid uid)
|
||||
{
|
||||
_pvs.EntityPVSCollection.UpdateIndex(uid, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to ensure that an entity is always sent to a specific client. Overrides any global or pre-existing
|
||||
/// client-specific overrides.
|
||||
/// </summary>
|
||||
public void AddSessionOverride(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
_pvs.EntityPVSCollection.UpdateIndex(uid, session, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes any global or client-specific overrides.
|
||||
/// </summary>
|
||||
public void ClearOverride(EntityUid uid, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
_pvs.EntityPVSCollection.UpdateIndex(uid, xform.Coordinates, true);
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,11 @@ using NetSerializer;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Players;
|
||||
@@ -29,6 +31,10 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
[Shared.IoC.Dependency] private readonly INetConfigurationManager _netConfigManager = default!;
|
||||
|
||||
public const float ChunkSize = 8;
|
||||
public const int TickBuffer = 10;
|
||||
|
||||
private static TransformComponentState _transformCullState =
|
||||
new(Vector2.Zero, Angle.Zero, EntityUid.Invalid, false, false);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of pooled objects
|
||||
@@ -54,11 +60,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw last iteration.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ICommonSession, Dictionary<EntityUid, PVSEntityVisiblity>> _playerVisibleSets = new();
|
||||
/// <summary>
|
||||
/// All <see cref="Robust.Shared.GameObjects.EntityUid"/>s a <see cref="ICommonSession"/> saw along its entire connection.
|
||||
/// </summary>
|
||||
private readonly Dictionary<ICommonSession, HashSet<EntityUid>> _playerSeenSets = new();
|
||||
private readonly Dictionary<ICommonSession, OverflowDictionary<GameTick, Dictionary<EntityUid, PVSEntityVisiblity>>> _playerVisibleSets = new();
|
||||
|
||||
private PVSCollection<EntityUid> _entityPvsCollection = default!;
|
||||
public PVSCollection<EntityUid> EntityPVSCollection => _entityPvsCollection;
|
||||
@@ -66,7 +68,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
private readonly ObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>> _visSetPool
|
||||
= new DefaultObjectPool<Dictionary<EntityUid, PVSEntityVisiblity>>(
|
||||
new DictPolicy<EntityUid, PVSEntityVisiblity>(), MaxVisPoolSize);
|
||||
new DictPolicy<EntityUid, PVSEntityVisiblity>(), MaxVisPoolSize*TickBuffer);
|
||||
|
||||
private readonly ObjectPool<HashSet<EntityUid>> _uidSetPool
|
||||
= new DefaultObjectPool<HashSet<EntityUid>>(new SetPolicy<EntityUid>(), MaxVisPoolSize);
|
||||
@@ -83,11 +85,11 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
private readonly ObjectPool<Dictionary<MapChunkLocation, int>> _mapChunkPool =
|
||||
new DefaultObjectPool<Dictionary<MapChunkLocation, int>>(
|
||||
new ChunkPoolPolicy<MapChunkLocation>(), 256);
|
||||
new ChunkPoolPolicy<MapChunkLocation>(), MaxVisPoolSize);
|
||||
|
||||
private readonly ObjectPool<Dictionary<GridChunkLocation, int>> _gridChunkPool =
|
||||
new DefaultObjectPool<Dictionary<GridChunkLocation, int>>(
|
||||
new ChunkPoolPolicy<GridChunkLocation>(), 256);
|
||||
new ChunkPoolPolicy<GridChunkLocation>(), MaxVisPoolSize);
|
||||
|
||||
private readonly Dictionary<uint, Dictionary<MapChunkLocation, int>> _mapIndices = new(4);
|
||||
private readonly Dictionary<uint, Dictionary<GridChunkLocation, int>> _gridIndices = new(4);
|
||||
@@ -114,7 +116,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
SubscribeLocalEvent<MoveEvent>(OnEntityMove);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<TransformComponent, ComponentStartup>(OnTransformStartup);
|
||||
SubscribeLocalEvent<TransformComponent, TransformStartupEvent>(OnTransformStartup);
|
||||
EntityManager.EntityDeleted += OnEntityDeleted;
|
||||
|
||||
_configManager.OnValueChanged(CVars.NetPVS, SetPvs, true);
|
||||
@@ -220,15 +222,15 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
|
||||
private void OnEntityMove(ref MoveEvent ev)
|
||||
{
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var coordinates = _transform.GetMoverCoordinates(ev.Component);
|
||||
UpdateEntityRecursive(ev.Sender, ev.Component, coordinates, xformQuery, false);
|
||||
}
|
||||
|
||||
private void OnTransformStartup(EntityUid uid, TransformComponent component, ComponentStartup args)
|
||||
private void OnTransformStartup(EntityUid uid, TransformComponent component, ref TransformStartupEvent args)
|
||||
{
|
||||
// use Startup because GridId is not set during the eventbus init yet!
|
||||
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var coordinates = _transform.GetMoverCoordinates(component);
|
||||
UpdateEntityRecursive(uid, component, coordinates, xformQuery, false);
|
||||
}
|
||||
@@ -257,8 +259,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.InGame)
|
||||
{
|
||||
_playerVisibleSets.Add(e.Session, _visSetPool.Get());
|
||||
_playerSeenSets.Add(e.Session, new HashSet<EntityUid>());
|
||||
_playerVisibleSets.Add(e.Session, new OverflowDictionary<GameTick, Dictionary<EntityUid, PVSEntityVisiblity>>(TickBuffer, _visSetPool.Return));
|
||||
foreach (var pvsCollection in _pvsCollections)
|
||||
{
|
||||
pvsCollection.AddPlayer(e.Session);
|
||||
@@ -266,10 +267,12 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
else if (e.NewStatus == SessionStatus.Disconnected)
|
||||
{
|
||||
var playerVisSet = _playerVisibleSets[e.Session];
|
||||
var overflowDict = _playerVisibleSets[e.Session];
|
||||
_playerVisibleSets.Remove(e.Session);
|
||||
_visSetPool.Return(playerVisSet);
|
||||
_playerSeenSets.Remove(e.Session);
|
||||
foreach (var (_, playerVisSet) in overflowDict)
|
||||
{
|
||||
_visSetPool.Return(playerVisSet);
|
||||
}
|
||||
foreach (var pvsCollection in _pvsCollections)
|
||||
{
|
||||
pvsCollection.RemovePlayer(e.Session);
|
||||
@@ -352,6 +355,8 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
{
|
||||
var (viewPos, range, mapId) = CalcViewBounds(in eyeEuid, transformQuery);
|
||||
|
||||
if(mapId == MapId.Nullspace) continue;
|
||||
|
||||
uint visMask = EyeComponent.DefaultVisibilityMask;
|
||||
if (eyeQuery.TryGetComponent(eyeEuid, out var eyeComp))
|
||||
visMask = eyeComp.VisibilityMask;
|
||||
@@ -544,7 +549,7 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
!AddToChunkSetRecursively(in parent, visMask, tree, set, transform, metadata)) //did we just fail to add the parent?
|
||||
return false; //we failed? suppose we dont get added either
|
||||
|
||||
//todo paul i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles, make this a simpl assignment at some point maybe idk
|
||||
//i want it to crash here if it gets added double bc that shouldnt happen and will add alot of unneeded cycles
|
||||
tree.Set(uid, parent);
|
||||
set.Add(uid, mComp);
|
||||
return true;
|
||||
@@ -557,13 +562,10 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
EntityUid[] viewerEntities)
|
||||
{
|
||||
DebugTools.Assert(session.Status == SessionStatus.InGame);
|
||||
var newEntityBudget = _netConfigManager.GetClientCVar(session.ConnectedClient, CVars.NetPVSNewEntityBudget);
|
||||
var enteredEntityBudget = _netConfigManager.GetClientCVar(session.ConnectedClient, CVars.NetPVSEntityBudget);
|
||||
var newEntitiesSent = 0;
|
||||
var entitiesSent = 0;
|
||||
var playerVisibleSet = _playerVisibleSets[session];
|
||||
_playerVisibleSets[session].TryGetValue(fromTick, out var playerVisibleSet);
|
||||
var visibleEnts = _visSetPool.Get();
|
||||
var seenSet = _playerSeenSets[session];
|
||||
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
|
||||
|
||||
foreach (var i in chunkIndices)
|
||||
@@ -572,8 +574,8 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
if(!cache.HasValue) continue;
|
||||
foreach (var rootNode in cache.Value.tree.RootNodes)
|
||||
{
|
||||
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, cache.Value.metadata, in enteredEntityBudget, in newEntityBudget);
|
||||
RecursivelyAddTreeNode(in rootNode, cache.Value.tree, playerVisibleSet, visibleEnts, fromTick,
|
||||
ref entitiesSent, cache.Value.metadata, in enteredEntityBudget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,8 +583,8 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
while (globalEnumerator.MoveNext())
|
||||
{
|
||||
var uid = globalEnumerator.Current;
|
||||
RecursivelyAddOverride(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
|
||||
RecursivelyAddOverride(in uid, playerVisibleSet, visibleEnts, fromTick,
|
||||
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget);
|
||||
}
|
||||
globalEnumerator.Dispose();
|
||||
|
||||
@@ -590,15 +592,23 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
while (localEnumerator.MoveNext())
|
||||
{
|
||||
var uid = localEnumerator.Current;
|
||||
RecursivelyAddOverride(in uid, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
|
||||
RecursivelyAddOverride(in uid, playerVisibleSet, visibleEnts, fromTick,
|
||||
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget);
|
||||
}
|
||||
localEnumerator.Dispose();
|
||||
|
||||
foreach (var viewerEntity in viewerEntities)
|
||||
{
|
||||
RecursivelyAddOverride(in viewerEntity, seenSet, playerVisibleSet, visibleEnts, fromTick, ref newEntitiesSent,
|
||||
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget, in newEntityBudget);
|
||||
RecursivelyAddOverride(in viewerEntity, playerVisibleSet, visibleEnts, fromTick,
|
||||
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
var expandEvent = new ExpandPvsEvent(session, new List<EntityUid>());
|
||||
RaiseLocalEvent(ref expandEvent);
|
||||
foreach (var entityUid in expandEvent.Entities)
|
||||
{
|
||||
RecursivelyAddOverride(in entityUid, playerVisibleSet, visibleEnts, fromTick,
|
||||
ref entitiesSent, mQuery, tQuery, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
var entityStates = new List<EntityState>();
|
||||
@@ -617,24 +627,26 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
entityStates.Add(state);
|
||||
}
|
||||
|
||||
foreach (var (entityUid, _) in playerVisibleSet)
|
||||
if(playerVisibleSet != null)
|
||||
{
|
||||
// it was deleted, so we dont need to exit pvs
|
||||
if(deletions.Contains(entityUid)) continue;
|
||||
|
||||
//TODO: HACK: somehow an entity left the view, transform does not exist (deleted?), but was not in the
|
||||
// deleted list. This seems to happen with the map entity on round restart.
|
||||
if (!EntityManager.EntityExists(entityUid))
|
||||
continue;
|
||||
|
||||
entityStates.Add(new EntityState(entityUid, new NetListAsArray<ComponentChange>(new []
|
||||
foreach (var (entityUid, _) in playerVisibleSet)
|
||||
{
|
||||
ComponentChange.Changed(_stateManager.TransformNetId, new TransformComponent.TransformComponentState(Vector2.Zero, Angle.Zero, EntityUid.Invalid, false, false)),
|
||||
}), true));
|
||||
// it was deleted, so we dont need to exit pvs
|
||||
if (deletions.Contains(entityUid)) continue;
|
||||
|
||||
//TODO: HACK: somehow an entity left the view, transform does not exist (deleted?), but was not in the
|
||||
// deleted list. This seems to happen with the map entity on round restart.
|
||||
if (!EntityManager.EntityExists(entityUid))
|
||||
continue;
|
||||
|
||||
entityStates.Add(new EntityState(entityUid, new NetListAsArray<ComponentChange>(new[]
|
||||
{
|
||||
ComponentChange.Changed(_stateManager.TransformNetId, _transformCullState),
|
||||
}), true));
|
||||
}
|
||||
}
|
||||
|
||||
_playerVisibleSets[session] = visibleEnts;
|
||||
_visSetPool.Return(playerVisibleSet);
|
||||
_playerVisibleSets[session].Add(toTick, visibleEnts);
|
||||
|
||||
if (deletions.Count == 0) deletions = default;
|
||||
if (entityStates.Count == 0) entityStates = default;
|
||||
@@ -642,18 +654,14 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private void RecursivelyAddTreeNode(
|
||||
in EntityUid nodeIndex,
|
||||
private void RecursivelyAddTreeNode(in EntityUid nodeIndex,
|
||||
RobustTree<EntityUid> tree,
|
||||
HashSet<EntityUid> seenSet,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity>? previousVisibleEnts,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> toSend,
|
||||
GameTick fromTick,
|
||||
ref int newEntitiesSent,
|
||||
ref int totalEnteredEntities,
|
||||
Dictionary<EntityUid, MetaDataComponent> metaDataCache,
|
||||
in int enteredEntityBudget,
|
||||
in int newEntityBudget)
|
||||
in int enteredEntityBudget)
|
||||
{
|
||||
//are we valid?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
@@ -664,8 +672,8 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
if (nodeIndex.IsValid() && !toSend.ContainsKey(nodeIndex))
|
||||
{
|
||||
//are we new?
|
||||
var (entered, budgetFail) = ProcessEntry(in nodeIndex, seenSet, previousVisibleEnts, ref newEntitiesSent,
|
||||
ref totalEnteredEntities, in enteredEntityBudget, in newEntityBudget);
|
||||
var (entered, budgetFail) = ProcessEntry(in nodeIndex, previousVisibleEnts,
|
||||
ref totalEnteredEntities, in enteredEntityBudget);
|
||||
|
||||
if (budgetFail) return;
|
||||
|
||||
@@ -678,24 +686,21 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
RecursivelyAddTreeNode(in child, tree, seenSet, previousVisibleEnts, toSend, fromTick, ref newEntitiesSent,
|
||||
ref totalEnteredEntities, metaDataCache, in enteredEntityBudget, in newEntityBudget);
|
||||
RecursivelyAddTreeNode(in child, tree, previousVisibleEnts, toSend, fromTick,
|
||||
ref totalEnteredEntities, metaDataCache, in enteredEntityBudget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool RecursivelyAddOverride(
|
||||
in EntityUid uid,
|
||||
HashSet<EntityUid> seenSet,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity>? previousVisibleEnts,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> toSend,
|
||||
GameTick fromTick,
|
||||
ref int newEntitiesSent,
|
||||
ref int totalEnteredEntities,
|
||||
EntityQuery<MetaDataComponent> metaQuery,
|
||||
EntityQuery<TransformComponent> transQuery,
|
||||
in int enteredEntityBudget,
|
||||
in int newEntityBudget)
|
||||
in int enteredEntityBudget)
|
||||
{
|
||||
//are we valid?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
@@ -705,24 +710,22 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
if (toSend.ContainsKey(uid)) return true;
|
||||
|
||||
var parent = transQuery.GetComponent(uid).ParentUid;
|
||||
if (parent.IsValid() && !RecursivelyAddOverride(in parent, seenSet, previousVisibleEnts, toSend, fromTick,
|
||||
ref newEntitiesSent, ref totalEnteredEntities, metaQuery, transQuery, in enteredEntityBudget, in newEntityBudget))
|
||||
if (parent.IsValid() && !RecursivelyAddOverride(in parent, previousVisibleEnts, toSend, fromTick,
|
||||
ref totalEnteredEntities, metaQuery, transQuery, in enteredEntityBudget))
|
||||
return false;
|
||||
|
||||
var (entered, _) = ProcessEntry(in uid, seenSet, previousVisibleEnts, ref newEntitiesSent,
|
||||
ref totalEnteredEntities, in enteredEntityBudget, in newEntityBudget);
|
||||
var (entered, _) = ProcessEntry(in uid, previousVisibleEnts,
|
||||
ref totalEnteredEntities, in enteredEntityBudget);
|
||||
|
||||
AddToSendSet(in uid, metaQuery.GetComponent(uid), toSend, fromTick, entered);
|
||||
return true;
|
||||
}
|
||||
|
||||
private (bool entered, bool budgetFail) ProcessEntry(in EntityUid uid, HashSet<EntityUid> seenSet,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity> previousVisibleEnts,
|
||||
ref int newEntitiesSent,
|
||||
ref int totalEnteredEntities, in int enteredEntityBudget, in int newEntityBudget)
|
||||
private (bool entered, bool budgetFail) ProcessEntry(in EntityUid uid,
|
||||
Dictionary<EntityUid, PVSEntityVisiblity>? previousVisibleEnts,
|
||||
ref int totalEnteredEntities, in int enteredEntityBudget)
|
||||
{
|
||||
var @new = !seenSet.Contains(uid);
|
||||
var entered = @new | !previousVisibleEnts.Remove(uid);
|
||||
var entered = previousVisibleEnts?.Remove(uid) == false;
|
||||
|
||||
if (entered)
|
||||
{
|
||||
@@ -732,16 +735,6 @@ internal sealed partial class PVSSystem : EntitySystem
|
||||
totalEnteredEntities++;
|
||||
}
|
||||
|
||||
if (@new)
|
||||
{
|
||||
//we just entered pvs, do we still have enough budget to send us?
|
||||
if(newEntitiesSent >= newEntityBudget)
|
||||
return (entered, true);
|
||||
|
||||
newEntitiesSent++;
|
||||
seenSet.Add(uid);
|
||||
}
|
||||
|
||||
return (entered, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
@@ -18,8 +17,11 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using SharpZstd.Interop;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Robust.Server.GameStates
|
||||
{
|
||||
@@ -40,9 +42,13 @@ namespace Robust.Server.GameStates
|
||||
[Dependency] private readonly INetworkedMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systemManager = default!;
|
||||
[Dependency] private readonly IServerEntityNetworkManager _entityNetworkManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IParallelManager _parallelMgr = default!;
|
||||
|
||||
private ISawmill _logger = default!;
|
||||
|
||||
private DefaultObjectPool<PvsThreadResources> _threadResourcesPool = default!;
|
||||
|
||||
public ushort TransformNetId { get; set; }
|
||||
|
||||
public void PostInject()
|
||||
@@ -60,6 +66,54 @@ namespace Robust.Server.GameStates
|
||||
_networkManager.Disconnect += HandleClientDisconnect;
|
||||
|
||||
_pvs = EntitySystem.Get<PVSSystem>();
|
||||
|
||||
_parallelMgr.AddAndInvokeParallelCountChanged(ResetParallelism);
|
||||
|
||||
_cfg.OnValueChanged(CVars.NetPVSCompressLevel, _ => ResetParallelism(), true);
|
||||
}
|
||||
|
||||
private void ResetParallelism()
|
||||
{
|
||||
var compressLevel = _cfg.GetCVar(CVars.NetPVSCompressLevel);
|
||||
// The * 2 is because trusting .NET won't take more is what got this code into this mess in the first place.
|
||||
_threadResourcesPool = new DefaultObjectPool<PvsThreadResources>(new PvsThreadResourcesObjectPolicy(compressLevel), _parallelMgr.ParallelProcessCount * 2);
|
||||
}
|
||||
|
||||
private sealed class PvsThreadResourcesObjectPolicy : IPooledObjectPolicy<PvsThreadResources>
|
||||
{
|
||||
public int CompressionLevel;
|
||||
|
||||
public PvsThreadResourcesObjectPolicy(int ce)
|
||||
{
|
||||
CompressionLevel = ce;
|
||||
}
|
||||
|
||||
PvsThreadResources IPooledObjectPolicy<PvsThreadResources>.Create()
|
||||
{
|
||||
var res = new PvsThreadResources();
|
||||
res.CompressionContext.SetParameter(ZSTD_cParameter.ZSTD_c_compressionLevel, CompressionLevel);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool IPooledObjectPolicy<PvsThreadResources>.Return(PvsThreadResources _)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PvsThreadResources
|
||||
{
|
||||
public ZStdCompressionContext CompressionContext;
|
||||
|
||||
public PvsThreadResources()
|
||||
{
|
||||
CompressionContext = new ZStdCompressionContext();
|
||||
}
|
||||
|
||||
~PvsThreadResources()
|
||||
{
|
||||
CompressionContext.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleClientConnected(object? sender, NetChannelArgs e)
|
||||
@@ -142,7 +196,7 @@ namespace Robust.Server.GameStates
|
||||
(chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
|
||||
const int ChunkBatchSize = 2;
|
||||
var chunksCount = chunks.Count;
|
||||
var chunkBatches = (int) MathF.Ceiling((float) chunksCount / ChunkBatchSize);
|
||||
var chunkBatches = (int)MathF.Ceiling((float)chunksCount / ChunkBatchSize);
|
||||
chunkCache =
|
||||
new (Dictionary<EntityUid, MetaDataComponent> metadata, RobustTree<EntityUid> tree)?[chunksCount];
|
||||
|
||||
@@ -159,7 +213,8 @@ namespace Robust.Server.GameStates
|
||||
for (var j = start; j < end; ++j)
|
||||
{
|
||||
var (visMask, chunkIndexLocation) = chunks[j];
|
||||
reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery, out var chunk);
|
||||
reuse[j] = _pvs.TryCalculateChunk(chunkIndexLocation, visMask, transformQuery, metadataQuery,
|
||||
out var chunk);
|
||||
chunkCache[j] = chunk;
|
||||
}
|
||||
});
|
||||
@@ -168,33 +223,31 @@ namespace Robust.Server.GameStates
|
||||
ArrayPool<bool>.Shared.Return(reuse);
|
||||
}
|
||||
|
||||
const int BatchSize = 2;
|
||||
var batches = (int) MathF.Ceiling((float) players.Length / BatchSize);
|
||||
|
||||
Parallel.For(0, batches, i =>
|
||||
{
|
||||
var start = i * BatchSize;
|
||||
var end = Math.Min(start + BatchSize, players.Length);
|
||||
|
||||
for (var j = start; j < end; ++j)
|
||||
Parallel.For(
|
||||
0, players.Length,
|
||||
new ParallelOptions { MaxDegreeOfParallelism = _parallelMgr.ParallelProcessCount },
|
||||
() => _threadResourcesPool.Get(),
|
||||
(i, loop, resource) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
SendStateUpdate(j);
|
||||
SendStateUpdate(i, resource);
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
_logger.Log(LogLevel.Error, e, "Caught exception while generating mail.");
|
||||
}
|
||||
}
|
||||
});
|
||||
return resource;
|
||||
},
|
||||
resource => _threadResourcesPool.Return(resource)
|
||||
);
|
||||
|
||||
void SendStateUpdate(int sessionIndex)
|
||||
void SendStateUpdate(int sessionIndex, PvsThreadResources resources)
|
||||
{
|
||||
var session = players[sessionIndex];
|
||||
|
||||
// KILL IT WITH FIRE
|
||||
if(mainThread != Thread.CurrentThread)
|
||||
if (mainThread != Thread.CurrentThread)
|
||||
IoCManager.InitThread(new DependencyCollection(parentDeps), true);
|
||||
|
||||
var channel = session.ConnectedClient;
|
||||
@@ -214,13 +267,15 @@ namespace Robust.Server.GameStates
|
||||
// lastAck varies with each client based on lag and such, we can't just make 1 global state and send it to everyone
|
||||
var lastInputCommand = inputSystem.GetLastInputCommand(session);
|
||||
var lastSystemMessage = _entityNetworkManager.GetLastMessageSequence(session);
|
||||
var state = new GameState(lastAck, _gameTiming.CurTick, Math.Max(lastInputCommand, lastSystemMessage), entStates, playerStates, deletions, mapData);
|
||||
var state = new GameState(lastAck, _gameTiming.CurTick, Math.Max(lastInputCommand, lastSystemMessage),
|
||||
entStates, playerStates, deletions, mapData);
|
||||
|
||||
InterlockedHelper.Min(ref oldestAckValue, lastAck.Value);
|
||||
|
||||
// actually send the state
|
||||
var stateUpdateMessage = _networkManager.CreateNetMessage<MsgState>();
|
||||
var stateUpdateMessage = new MsgState();
|
||||
stateUpdateMessage.State = state;
|
||||
stateUpdateMessage.CompressionContext = resources.CompressionContext;
|
||||
|
||||
// If the state is too big we let Lidgren send it reliably.
|
||||
// This is to avoid a situation where a state is so large that it consistently gets dropped
|
||||
@@ -238,7 +293,7 @@ namespace Robust.Server.GameStates
|
||||
_networkManager.ServerSendMessage(stateUpdateMessage, channel);
|
||||
}
|
||||
|
||||
if(_pvs.CullingEnabled)
|
||||
if (_pvs.CullingEnabled)
|
||||
_pvs.ReturnToPool(playerChunks);
|
||||
_pvs.Cleanup(_playerManager.ServerSessions);
|
||||
var oldestAck = new GameTick(oldestAckValue);
|
||||
|
||||
218
Robust.Server/Maps/GridSerializer.cs
Normal file
218
Robust.Server/Maps/GridSerializer.cs
Normal file
@@ -0,0 +1,218 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Server.Maps
|
||||
{
|
||||
[TypeSerializer]
|
||||
internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingDataNode>
|
||||
{
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public MapChunk Read(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, MapChunk? chunk = null)
|
||||
{
|
||||
var tileNode = (ValueDataNode)node["tiles"];
|
||||
var tileBytes = Convert.FromBase64String(tileNode.Value);
|
||||
|
||||
using var stream = new MemoryStream(tileBytes);
|
||||
using var reader = new BinaryReader(stream);
|
||||
|
||||
var mapManager = dependencies.Resolve<IMapManager>();
|
||||
mapManager.SuppressOnTileChanged = true;
|
||||
|
||||
if (chunk == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Someone tried deserializing a gridchunk without passing a value.");
|
||||
}
|
||||
|
||||
if (context is not MapLoader.MapContext mapContext)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Someone tried serializing a gridchunk without passing {nameof(MapLoader.MapContext)} as context.");
|
||||
}
|
||||
|
||||
var tileMap = mapContext.TileMap;
|
||||
|
||||
if (tileMap == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Someone tried deserializing a gridchunk before deserializing the tileMap.");
|
||||
}
|
||||
|
||||
chunk.SuppressCollisionRegeneration = true;
|
||||
|
||||
var tileDefinitionManager = dependencies.Resolve<ITileDefinitionManager>();
|
||||
|
||||
for (ushort y = 0; y < chunk.ChunkSize; y++)
|
||||
{
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var id = reader.ReadUInt16();
|
||||
var flags = (TileRenderFlag)reader.ReadByte();
|
||||
var variant = reader.ReadByte();
|
||||
|
||||
var defName = tileMap[id];
|
||||
id = tileDefinitionManager[defName].TileId;
|
||||
|
||||
var tile = new Tile(id, flags, variant);
|
||||
chunk.SetTile(x, y, tile);
|
||||
}
|
||||
}
|
||||
|
||||
chunk.SuppressCollisionRegeneration = false;
|
||||
mapManager.SuppressOnTileChanged = false;
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, MapChunk value, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
var root = new MappingDataNode();
|
||||
var ind = new ValueDataNode($"{value.X},{value.Y}");
|
||||
root.Add("ind", ind);
|
||||
|
||||
var gridNode = new ValueDataNode();
|
||||
root.Add("tiles", gridNode);
|
||||
|
||||
gridNode.Value = SerializeTiles(value);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static string SerializeTiles(MapChunk chunk)
|
||||
{
|
||||
// number of bytes written per tile, because sizeof(Tile) is useless.
|
||||
const int structSize = 4;
|
||||
|
||||
var nTiles = chunk.ChunkSize * chunk.ChunkSize * structSize;
|
||||
var barr = new byte[nTiles];
|
||||
|
||||
using (var stream = new MemoryStream(barr))
|
||||
using (var writer = new BinaryWriter(stream))
|
||||
{
|
||||
for (ushort y = 0; y < chunk.ChunkSize; y++)
|
||||
{
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
writer.Write(tile.TypeId);
|
||||
writer.Write((byte)tile.Flags);
|
||||
writer.Write(tile.Variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Convert.ToBase64String(barr);
|
||||
}
|
||||
|
||||
public MapChunk Copy(ISerializationManager serializationManager, MapChunk source, MapChunk target, bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
//todo paul make this be used
|
||||
[TypeSerializer]
|
||||
internal sealed class GridSerializer : ITypeSerializer<MapGrid, MappingDataNode>
|
||||
{
|
||||
|
||||
public ValidationNode Validate(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public MapGrid Read(ISerializationManager serializationManager, MappingDataNode node,
|
||||
IDependencyCollection dependencies, bool skipHook, ISerializationContext? context = null, MapGrid? grid = null)
|
||||
{
|
||||
var info = node.Get<MappingDataNode>("settings");
|
||||
var chunks = node.Get<SequenceDataNode>("chunks");
|
||||
ushort csz = 0;
|
||||
ushort tsz = 0;
|
||||
float sgsz = 0.0f;
|
||||
|
||||
foreach (var kvInfo in info.Cast<KeyValuePair<ValueDataNode, ValueDataNode>>())
|
||||
{
|
||||
var key = kvInfo.Key.Value;
|
||||
var val = kvInfo.Value.Value;
|
||||
if (key == "chunksize")
|
||||
csz = ushort.Parse(val);
|
||||
else if (key == "tilesize")
|
||||
tsz = ushort.Parse(val);
|
||||
else if (key == "snapsize")
|
||||
sgsz = float.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
//TODO: Pass in options
|
||||
if (context is not MapLoader.MapContext mapContext)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Someone tried serializing a mapgrid without passing {nameof(MapLoader.MapContext)} as context.");
|
||||
}
|
||||
|
||||
if (grid == null) throw new NotImplementedException();
|
||||
//todo paul grid ??= dependencies.Resolve<MapManager>().CreateUnboundGrid(mapContext.TargetMap);
|
||||
|
||||
foreach (var chunkNode in chunks.Cast<MappingDataNode>())
|
||||
{
|
||||
var (chunkOffsetX, chunkOffsetY) =
|
||||
serializationManager.Read<Vector2i>(chunkNode["ind"], context, skipHook);
|
||||
var chunk = grid.GetChunk(chunkOffsetX, chunkOffsetY);
|
||||
serializationManager.Read(typeof(MapChunkSerializer), chunkNode, context, skipHook, chunk);
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, MapGrid value, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
var gridn = new MappingDataNode();
|
||||
var info = new MappingDataNode();
|
||||
var chunkSeq = new SequenceDataNode();
|
||||
|
||||
gridn.Add("settings", info);
|
||||
gridn.Add("chunks", chunkSeq);
|
||||
|
||||
info.Add("chunksize", value.ChunkSize.ToString(CultureInfo.InvariantCulture));
|
||||
info.Add("tilesize", value.TileSize.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
var chunks = value.GetMapChunks();
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
var chunkNode = serializationManager.WriteValue(chunk.Value);
|
||||
chunkSeq.Add(chunkNode);
|
||||
}
|
||||
|
||||
return gridn;
|
||||
}
|
||||
|
||||
public MapGrid Copy(ISerializationManager serializationManager, MapGrid source, MapGrid target, bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
@@ -6,12 +8,12 @@ namespace Robust.Server.Maps
|
||||
{
|
||||
public interface IMapLoader
|
||||
{
|
||||
IMapGrid? LoadBlueprint(MapId mapId, string path);
|
||||
IMapGrid? LoadBlueprint(MapId mapId, string path, MapLoadOptions options);
|
||||
(IReadOnlyList<EntityUid> entities, GridId? gridId) LoadBlueprint(MapId mapId, string path);
|
||||
(IReadOnlyList<EntityUid> entities, GridId? gridId) LoadBlueprint(MapId mapId, string path, MapLoadOptions options);
|
||||
void SaveBlueprint(GridId gridId, string yamlPath);
|
||||
|
||||
void LoadMap(MapId mapId, string path);
|
||||
void LoadMap(MapId mapId, string path, MapLoadOptions options);
|
||||
(IReadOnlyList<EntityUid> entities, IReadOnlyList<GridId> gridIds) LoadMap(MapId mapId, string path);
|
||||
(IReadOnlyList<EntityUid> entities, IReadOnlyList<GridId> gridIds) LoadMap(MapId mapId, string path, MapLoadOptions options);
|
||||
void SaveMap(MapId mapId, string yamlPath);
|
||||
|
||||
event Action<YamlStream, string> LoadedMapData;
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
namespace Robust.Server.Maps
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Server.Maps
|
||||
{
|
||||
[PublicAPI]
|
||||
public sealed class MapLoadOptions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -7,5 +11,38 @@
|
||||
/// to maintain consistency upon subsequent savings.
|
||||
/// </summary>
|
||||
public bool StoreMapUids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Offset to apply to the loaded objects.
|
||||
/// </summary>
|
||||
public Vector2 Offset
|
||||
{
|
||||
get => _offset;
|
||||
set
|
||||
{
|
||||
TransformMatrix = Matrix3.CreateTransform(value, Rotation);
|
||||
_offset = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 _offset = Vector2.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation to apply to the loaded objects as a collective, around 0, 0.
|
||||
/// </summary>
|
||||
/// <remarks>Setting this overrides <</remarks>
|
||||
public Angle Rotation
|
||||
{
|
||||
get => _rotation;
|
||||
set
|
||||
{
|
||||
TransformMatrix = Matrix3.CreateTransform(Offset, value);
|
||||
_rotation = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Angle _rotation = Angle.Zero;
|
||||
|
||||
public Matrix3 TransformMatrix { get; set; } = Matrix3.Identity;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user