From c8654a652b76732d8cd3904566436cd91843a71e Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 02:38:45 -0500 Subject: [PATCH 01/28] Turn world manager into a BackgroundService --- Obsidian.ConsoleApp/Program.cs | 7 +++-- Obsidian/Hosting/DependencyInjection.cs | 2 ++ Obsidian/Server.cs | 5 --- Obsidian/WorldData/World.cs | 10 +++--- Obsidian/WorldData/WorldManager.cs | 41 ++++++++++++++++++++----- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/Obsidian.ConsoleApp/Program.cs b/Obsidian.ConsoleApp/Program.cs index ac4478047..0a4e551e6 100644 --- a/Obsidian.ConsoleApp/Program.cs +++ b/Obsidian.ConsoleApp/Program.cs @@ -39,8 +39,11 @@ private static async Task Main() services.AddObsidian(env); // Give the server some time to shut down after CTRL-C or SIGTERM. - services.Configure( - opts => opts.ShutdownTimeout = TimeSpan.FromSeconds(10)); + //TODO SERVICES SET STOP CONCURRENTLY + services.Configure(opts => + { + opts.ShutdownTimeout = TimeSpan.FromSeconds(10); + }); }) .Build(); diff --git a/Obsidian/Hosting/DependencyInjection.cs b/Obsidian/Hosting/DependencyInjection.cs index 092fb6050..1a56d8814 100644 --- a/Obsidian/Hosting/DependencyInjection.cs +++ b/Obsidian/Hosting/DependencyInjection.cs @@ -18,7 +18,9 @@ public static IServiceCollection AddObsidian(this IServiceCollection services, I services.AddSingleton(); services.AddSingleton(f => f.GetRequiredService()); + services.AddHostedService(); + services.AddHostedService(x => x.GetRequiredService()); return services; } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 3828f83ed..5a5c87493 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -282,11 +282,6 @@ public async Task RunAsync() await Task.WhenAll(Config.DownloadPlugins.Select(path => PluginManager.LoadPluginAsync(path))); - // TODO: This should defenitly accept a cancellation token. - // If Cancel is called, this method should stop within the configured timeout, otherwise code execution will simply stop here, - // and server shutdown will not be handled correctly. - await WorldManager.LoadWorldsAsync(); - if (!Config.OnlineMode) _logger.LogInformation($"Starting in offline mode..."); diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 7aa21fe1e..12281e0a7 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -384,11 +384,11 @@ public async Task DoWorldTickAsync() Server.BroadcastPacket(new UpdateTimePacket(LevelData.Time, LevelData.Time % 24000)); } - // Check for chunks to load every second - if (LevelData.Time % 20 == 0) - { - await ManageChunksAsync(); - } + //// Check for chunks to load every second + //if (LevelData.Time % 20 == 0) + //{ + // await ManageChunksAsync(); + //} } #region world loading/saving diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index ce0871a34..7e2c9336a 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -1,10 +1,13 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Obsidian.Registries; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Threading; namespace Obsidian.WorldData; -public sealed class WorldManager : IAsyncDisposable +public sealed class WorldManager : BackgroundService, IAsyncDisposable { private readonly ILogger logger; private readonly Server server; @@ -17,7 +20,7 @@ public sealed class WorldManager : IAsyncDisposable public int LoadedChunkCount => worlds.Sum(pair => pair.Value.Regions.Sum(x => x.Value.LoadedChunkCount)); - public World DefaultWorld { get; private set; } + public World? DefaultWorld { get; private set; } public WorldManager(Server server, ILogger logger, List serverWorlds) { @@ -26,22 +29,41 @@ public WorldManager(Server server, ILogger logger, List serverWorld this.serverWorlds = serverWorlds; } + protected async override Task ExecuteAsync(CancellationToken stoppingToken) + { + var timer = new BalancingTimer(1000, stoppingToken); + + // TODO: This should defenitly accept a cancellation token. + // If Cancel is called, this method should stop within the configured timeout, otherwise code execution will simply stop here, + // and server shutdown will not be handled correctly. + // Load worlds on startup. + await this.LoadWorldsAsync(); + + while (await timer.WaitForNextTickAsync()) + { + await Task.WhenAll(this.worlds.Values.Select(x => x.ManageChunksAsync())); + } + } + public async Task LoadWorldsAsync() { foreach (var serverWorld in this.serverWorlds) { if (!server.WorldGenerators.TryGetValue(serverWorld.Generator, out var value)) - logger.LogWarning($"Unknown generator type {serverWorld.Generator}"); + { + this.logger.LogError("Unknown generator type {generator} for world {worldName}", serverWorld.Generator, serverWorld.Name); + return; + } var world = new World(serverWorld.Name, this.server, serverWorld.Seed, value); this.worlds.Add(world.Name, world); if (!CodecRegistry.TryGetDimension(serverWorld.DefaultDimension, out var defaultCodec) || !CodecRegistry.TryGetDimension("minecraft:overworld", out defaultCodec)) - throw new InvalidOperationException("Failed to get default dimension codec."); + throw new UnreachableException("Failed to get default dimension codec."); if (!await world.LoadAsync(defaultCodec)) { - logger.LogInformation($"Creating new world: {serverWorld.Name}..."); + this.logger.LogInformation("Creating new world: {worldName}...", serverWorld.Name); world.Init(defaultCodec); @@ -50,7 +72,7 @@ public async Task LoadWorldsAsync() { if (!CodecRegistry.TryGetDimension(dimensionName, out var codec)) { - logger.LogWarning($"Failed to find dimension with the name {dimensionName}"); + this.logger.LogWarning("Failed to find dimension with the name {dimensionName}", dimensionName); continue; } @@ -80,12 +102,15 @@ public async Task LoadWorldsAsync() public Task TickWorldsAsync() => Task.WhenAll(this.worlds.Select(pair => pair.Value.DoWorldTickAsync())); public Task FlushLoadedWorldsAsync() => Task.WhenAll(this.worlds.Select(pair => pair.Value.FlushRegionsAsync())); - + + public async ValueTask DisposeAsync() { foreach (var world in worlds) { await world.Value.DisposeAsync(); } + + this.Dispose(); } } From 2e6da3639806b0b4fb0c03c4256d7b289ca3fbb0 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 03:35:47 -0500 Subject: [PATCH 02/28] Abstractions refactor pt.1 --- Obsidian.API/_Interfaces/IRegion.cs | 10 +++++ Obsidian.API/_Interfaces/IWorld.cs | 13 +++++- Obsidian.API/_Interfaces/IWorldManager.cs | 21 ++++++++++ Obsidian/Entities/Player.cs | 2 +- Obsidian/WorldData/World.cs | 6 ++- Obsidian/WorldData/WorldManager.cs | 51 ++++++++++++++--------- 6 files changed, 81 insertions(+), 22 deletions(-) create mode 100644 Obsidian.API/_Interfaces/IRegion.cs create mode 100644 Obsidian.API/_Interfaces/IWorldManager.cs diff --git a/Obsidian.API/_Interfaces/IRegion.cs b/Obsidian.API/_Interfaces/IRegion.cs new file mode 100644 index 000000000..175081a82 --- /dev/null +++ b/Obsidian.API/_Interfaces/IRegion.cs @@ -0,0 +1,10 @@ +namespace Obsidian.API; +public interface IRegion : IAsyncDisposable +{ + public int X { get; } + public int Z { get; } + + public int LoadedChunkCount { get; } + + public string RegionFolder { get; } +} diff --git a/Obsidian.API/_Interfaces/IWorld.cs b/Obsidian.API/_Interfaces/IWorld.cs index 6ce3d25d4..ebba49893 100644 --- a/Obsidian.API/_Interfaces/IWorld.cs +++ b/Obsidian.API/_Interfaces/IWorld.cs @@ -1,6 +1,8 @@ +using System.Collections.Concurrent; + namespace Obsidian.API; -public interface IWorld +public interface IWorld : IAsyncDisposable { public string Name { get; } @@ -14,6 +16,12 @@ public interface IWorld public Gamemode DefaultGamemode { get; } + public int RegionCount { get; } + public int LoadedChunkCount { get; } + public int ChunksToGenCount { get; } + + public int GetTotalLoadedEntities(); + public Task GetBlockAsync(Vector location); public Task GetBlockAsync(int x, int y, int z); public Task SetBlockAsync(Vector location, IBlock block); @@ -27,4 +35,7 @@ public interface IWorld public Task SpawnEntityAsync(VectorF position, EntityType type); public Task SpawnExperienceOrbs(VectorF position, short count); + + public Task DoWorldTickAsync(); + public Task FlushRegionsAsync(); } diff --git a/Obsidian.API/_Interfaces/IWorldManager.cs b/Obsidian.API/_Interfaces/IWorldManager.cs new file mode 100644 index 000000000..39e5bbdcf --- /dev/null +++ b/Obsidian.API/_Interfaces/IWorldManager.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Obsidian.API; +public interface IWorldManager : IAsyncDisposable +{ + public int GeneratingChunkCount { get; } + public int LoadedChunkCount { get; } + + public int RegionCount { get; } + + public IWorld DefaultWorld { get; } + + public IReadOnlyCollection GetAvailableWorlds(); + + public Task FlushLoadedWorldsAsync(); + + public Task TickWorldsAsync(); + + public bool TryGetWorld(string name, [NotNullWhen(true)] out IWorld? world); + public bool TryGetWorld(string name, [NotNullWhen(true)] out TWorld? world) where TWorld : IWorld; +} diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index c943d5932..b1bb6e9b3 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -663,7 +663,7 @@ public async Task LoadAsync(bool loadFromPersistentWorld = true) Logger.LogInformation($"persistent world: {worldName}"); - if (loadFromPersistentWorld && server.WorldManager.TryGetWorld(worldName, out var world)) + if (loadFromPersistentWorld && server.WorldManager.TryGetWorld(worldName, out var world)) { base.world = world; Logger.LogInformation($"Loading from persistent world: {worldName}"); diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 12281e0a7..32298632d 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -12,7 +12,7 @@ namespace Obsidian.WorldData; -public class World : IWorld, IAsyncDisposable +public class World : IWorld { private float rainLevel = 0f; private bool initialized = false; @@ -45,6 +45,10 @@ public class World : IWorld, IAsyncDisposable public long Time => LevelData.Time; + public int RegionCount => this.Regions.Count; + public int ChunksToGenCount => this.ChunksToGen.Count; + public int LoadedChunkCount => this.Regions.Values.Sum(x => x.LoadedChunkCount); + public Gamemode DefaultGamemode => LevelData.DefaultGamemode; public string DimensionName { get; private set; } diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 7e2c9336a..34f8432a4 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Obsidian.Hosting; using Obsidian.Registries; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -7,26 +8,28 @@ namespace Obsidian.WorldData; -public sealed class WorldManager : BackgroundService, IAsyncDisposable +public sealed class WorldManager : BackgroundService, IWorldManager { private readonly ILogger logger; - private readonly Server server; - private readonly Dictionary worlds = new(); + private readonly IServer server; + private readonly Dictionary worlds = new(); private readonly List serverWorlds; - public int GeneratingChunkCount => worlds.Sum(w => w.Value.ChunksToGen.Count); + public int GeneratingChunkCount => worlds.Values.Sum(w => w.ChunksToGenCount); - public int RegionCount => worlds.Sum(pair => pair.Value.Regions.Count); + public int RegionCount => worlds.Values.Sum(pair => pair.RegionCount); - public int LoadedChunkCount => worlds.Sum(pair => pair.Value.Regions.Sum(x => x.Value.LoadedChunkCount)); + public int LoadedChunkCount => worlds.Values.Sum(pair => pair.LoadedChunkCount); - public World? DefaultWorld { get; private set; } + public IWorld DefaultWorld { get; private set; } - public WorldManager(Server server, ILogger logger, List serverWorlds) + public WorldManager(IServer server, ILoggerFactory loggerFactory, IServerEnvironment serverEnvironment) { this.server = server; - this.logger = logger; - this.serverWorlds = serverWorlds; + this.logger = loggerFactory.CreateLogger(); + this.serverWorlds = serverEnvironment.ServerWorlds; + + this.logger.LogInformation("Instantiated."); } protected async override Task ExecuteAsync(CancellationToken stoppingToken) @@ -41,7 +44,7 @@ protected async override Task ExecuteAsync(CancellationToken stoppingToken) while (await timer.WaitForNextTickAsync()) { - await Task.WhenAll(this.worlds.Values.Select(x => x.ManageChunksAsync())); + await Task.WhenAll(this.worlds.Values.Cast().Select(x => x.ManageChunksAsync())); } } @@ -49,13 +52,14 @@ public async Task LoadWorldsAsync() { foreach (var serverWorld in this.serverWorlds) { + var server = (Server)this.server; if (!server.WorldGenerators.TryGetValue(serverWorld.Generator, out var value)) { this.logger.LogError("Unknown generator type {generator} for world {worldName}", serverWorld.Generator, serverWorld.Name); return; } - var world = new World(serverWorld.Name, this.server, serverWorld.Seed, value); + var world = new World(serverWorld.Name, server, serverWorld.Seed, value); this.worlds.Add(world.Name, world); if (!CodecRegistry.TryGetDimension(serverWorld.DefaultDimension, out var defaultCodec) || !CodecRegistry.TryGetDimension("minecraft:overworld", out defaultCodec)) @@ -92,23 +96,32 @@ public async Task LoadWorldsAsync() } //No default world was defined so choose the first one to come up - if (this.DefaultWorld == null) - this.DefaultWorld = this.worlds.FirstOrDefault().Value; + this.DefaultWorld ??= this.worlds.FirstOrDefault().Value; } - public IReadOnlyCollection GetAvailableWorlds() => this.worlds.Values.ToList().AsReadOnly(); + public IReadOnlyCollection GetAvailableWorlds() => this.worlds.Values.ToList().AsReadOnly(); - public bool TryGetWorld(string name, [NotNullWhen(true)] out World? world) => this.worlds.TryGetValue(name, out world); + public bool TryGetWorld(string name, [NotNullWhen(true)] out IWorld? world) => this.worlds.TryGetValue(name, out world); + public bool TryGetWorld(string name, [NotNullWhen(true)] out TWorld? world) where TWorld : IWorld + { + if (this.worlds.TryGetValue(name, out var value)) + { + world = (TWorld)value; + return true; + } + + world = default; + return false; + } public Task TickWorldsAsync() => Task.WhenAll(this.worlds.Select(pair => pair.Value.DoWorldTickAsync())); public Task FlushLoadedWorldsAsync() => Task.WhenAll(this.worlds.Select(pair => pair.Value.FlushRegionsAsync())); - public async ValueTask DisposeAsync() { - foreach (var world in worlds) + foreach (var world in this.worlds.Values) { - await world.Value.DisposeAsync(); + await world.DisposeAsync(); } this.Dispose(); From a67ea67981a5e523bd2ff9731cbe83be3869e3c1 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 05:29:06 -0500 Subject: [PATCH 03/28] Update Region.cs --- Obsidian/WorldData/Region.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Obsidian/WorldData/Region.cs b/Obsidian/WorldData/Region.cs index 748831ff0..c0d0818ec 100644 --- a/Obsidian/WorldData/Region.cs +++ b/Obsidian/WorldData/Region.cs @@ -12,7 +12,7 @@ namespace Obsidian.WorldData; -public class Region : IAsyncDisposable +public class Region : IRegion { public const int CubicRegionSizeShift = 5; public const int CubicRegionSize = 1 << CubicRegionSizeShift; From 7d3b61d8cc82b32c8ac4a390f2552972cc973001 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 05:57:27 -0500 Subject: [PATCH 04/28] Abstractions refactor pt.2 --- Obsidian/Hosting/DependencyInjection.cs | 14 +++-- Obsidian/Server.cs | 14 ++--- Obsidian/Services/PacketBroadcaster.cs | 71 +++++++++++++++++++++++++ Obsidian/WorldData/World.cs | 14 +++-- Obsidian/WorldData/WorldManager.cs | 26 +++++---- 5 files changed, 108 insertions(+), 31 deletions(-) create mode 100644 Obsidian/Services/PacketBroadcaster.cs diff --git a/Obsidian/Hosting/DependencyInjection.cs b/Obsidian/Hosting/DependencyInjection.cs index 1a56d8814..c4ceec7fe 100644 --- a/Obsidian/Hosting/DependencyInjection.cs +++ b/Obsidian/Hosting/DependencyInjection.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Obsidian.Commands.Framework; using Obsidian.Net.Rcon; +using Obsidian.Services; using Obsidian.WorldData; namespace Obsidian.Hosting; @@ -12,15 +13,18 @@ public static IServiceCollection AddObsidian(this IServiceCollection services, I services.AddSingleton(env.Configuration); services.AddSingleton(f => f.GetRequiredService()); - services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); - services.AddSingleton(f => f.GetRequiredService()); - + services.AddHostedService(sp => sp.GetRequiredService()); + services.AddHostedService(sp => sp.GetRequiredService()); services.AddHostedService(); - services.AddHostedService(x => x.GetRequiredService()); + + services.AddSingleton(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); return services; } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 5a5c87493..2e9a1539f 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -75,6 +75,7 @@ public static string VERSION public IOperatorList Operators { get; } public IScoreboardManager ScoreboardManager { get; private set; } + public IWorldManager WorldManager { get; } public ConcurrentDictionary OnlinePlayers { get; } = new(); public Dictionary WorldGenerators { get; } = new(); @@ -89,7 +90,6 @@ public static string VERSION public string PersistentDataPath { get; } public string Brand { get; } = "obsidian"; public int Port { get; } - public WorldManager WorldManager { get; private set; } public IWorld DefaultWorld => WorldManager.DefaultWorld; public IEnumerable Players => GetPlayers(); @@ -99,13 +99,13 @@ public static string VERSION public Server( IHostApplicationLifetime lifetime, IServerEnvironment environment, - ILogger logger, + ILoggerFactory loggerFactory, + IWorldManager worldManager, RconServer rconServer) { Config = environment.Configuration; - var loggerProvider = new LoggerProvider(Config.LogLevel); - _logger = loggerProvider.CreateLogger("Server"); - _logger.LogInformation($"SHA / Version: {VERSION}"); + _logger = loggerFactory.CreateLogger(); + _logger.LogInformation("SHA / Version: {VERSION}", VERSION); _cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(lifetime.ApplicationStopping); _cancelTokenSource.Token.Register(() => _logger.LogWarning("Obsidian is shutting down...")); _rconServer = rconServer; @@ -131,7 +131,7 @@ public Server( _logger.LogDebug("Registering command context type..."); _logger.LogDebug("Done registering commands."); - WorldManager = new WorldManager(this, _logger, environment.ServerWorlds); + this.WorldManager = worldManager; Events.PlayerLeave += OnPlayerLeave; Events.PlayerJoin += OnPlayerJoin; @@ -377,7 +377,7 @@ private async Task AcceptClientsAsync() } // TODO Entity ids need to be unique on the entire server, not per world - var client = new Client(connection, Config, Math.Max(0, _clients.Count + WorldManager.DefaultWorld.GetTotalLoadedEntities()), this); + var client = new Client(connection, Config, Math.Max(0, _clients.Count + this.DefaultWorld.GetTotalLoadedEntities()), this); _clients.Add(client); diff --git a/Obsidian/Services/PacketBroadcaster.cs b/Obsidian/Services/PacketBroadcaster.cs new file mode 100644 index 000000000..188ba9502 --- /dev/null +++ b/Obsidian/Services/PacketBroadcaster.cs @@ -0,0 +1,71 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Obsidian.Entities; +using Obsidian.Net.Packets; +using Obsidian.WorldData; +using System.Threading; + +namespace Obsidian.Services; +public sealed class PacketBroadcaster : BackgroundService, IPacketBroadcaster +{ + private readonly IServer server; + private readonly PriorityQueue priorityQueue = new(); + private readonly ILogger logger; + + public PacketBroadcaster(IServer server, ILoggerFactory loggerFactory) + { + this.server = server; + this.logger = loggerFactory.CreateLogger(); + } + + public void QueuePacket(IClientboundPacket packet, params int[] exluded) => + this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = exluded }, 1); + + public void QueuePacketToWorld(IWorld world, IClientboundPacket packet, params int[] exluded) => + this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = exluded }, 1); + + public void QueuePacketToWorld(IWorld world, int priority, IClientboundPacket packet, params int[] exluded) => + this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = exluded, ToWorld = world }, priority); + + public void QueuePacket(IClientboundPacket packet, int priority, params int[] exluded) => + this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = exluded }, priority); + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(50)); + + while (await timer.WaitForNextTickAsync(stoppingToken)) + { + if (!this.priorityQueue.TryDequeue(out var queuedPacket, out _)) + continue; + + if (queuedPacket.ToWorld is World toWorld) + { + foreach (var player in toWorld.Players.Values.Where(player => !queuedPacket.ExcludedIds.Contains(player.EntityId))) + await player.client.QueuePacketAsync(queuedPacket.Packet); + + continue; + } + + foreach (var player in this.server.Players.Cast().Where(player => !queuedPacket.ExcludedIds.Contains(player.EntityId))) + await player.client.QueuePacketAsync(queuedPacket.Packet); + } + } + + private readonly struct QueuedPacket + { + public required IClientboundPacket Packet { get; init; } + + public int[] ExcludedIds { get; init; } + + public IWorld? ToWorld { get; init; } + } +} + +public interface IPacketBroadcaster +{ + public void QueuePacketToWorld(IWorld world, IClientboundPacket packet, params int[] exluded); + public void QueuePacket(IClientboundPacket packet, params int[] exluded); + public void QueuePacketToWorld(IWorld world, int priority, IClientboundPacket packet, params int[] exluded); + public void QueuePacket(IClientboundPacket packet, int priority, params int[] exluded); +} diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 32298632d..c38c29bfe 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -8,6 +8,7 @@ using Obsidian.Nbt; using Obsidian.Net.Packets.Play.Clientbound; using Obsidian.Registries; +using Obsidian.Services; using System.IO; namespace Obsidian.WorldData; @@ -25,8 +26,6 @@ public class World : IWorld public IWorldGenerator Generator { get; internal set; } - public Server Server { get; } - public ConcurrentDictionary Regions { get; private set; } = new(); public ConcurrentQueue<(int X, int Z)> ChunksToGen { get; private set; } = new(); @@ -59,20 +58,18 @@ public class World : IWorld /// /// Used to log actions caused by the client. /// - protected ILogger Logger { get; private set; } + protected ILogger Logger { get; } - internal World(string name, Server server, string seed, Type generatorType) + internal World(string name, string seed, ILogger logger, IPacketBroadcaster packetBroadcaster, Type generatorType) { Name = name ?? throw new ArgumentNullException(nameof(name)); - Server = server; - Seed = seed ?? throw new ArgumentNullException(nameof(seed)); + Logger = logger; + this.packetBroadcaster = packetBroadcaster; Generator = Activator.CreateInstance(generatorType) as IWorldGenerator ?? throw new ArgumentException("Invalid generator type.", nameof(generatorType)); Generator.Init(this); worldLight = new(this); - var loggerProvider = new LoggerProvider(); - Logger = loggerProvider.CreateLogger("World"); } public int GetTotalLoadedEntities() => Regions.Values.Sum(e => e == null ? 0 : e.Entities.Count); @@ -549,6 +546,7 @@ public async Task ScheduleBlockUpdateAsync(BlockUpdate blockUpdate) } private ConcurrentDictionary<(int x, int z), (int x, int z)> moduloCache = new(); + private readonly IPacketBroadcaster packetBroadcaster; public async Task ManageChunksAsync() { diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 34f8432a4..43e45d93d 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Logging; using Obsidian.Hosting; using Obsidian.Registries; +using Obsidian.Services; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -11,9 +12,10 @@ namespace Obsidian.WorldData; public sealed class WorldManager : BackgroundService, IWorldManager { private readonly ILogger logger; - private readonly IServer server; private readonly Dictionary worlds = new(); private readonly List serverWorlds; + private readonly ILoggerFactory loggerFactory; + private readonly IPacketBroadcaster packetBroadcaster; public int GeneratingChunkCount => worlds.Values.Sum(w => w.ChunksToGenCount); @@ -23,13 +25,14 @@ public sealed class WorldManager : BackgroundService, IWorldManager public IWorld DefaultWorld { get; private set; } - public WorldManager(IServer server, ILoggerFactory loggerFactory, IServerEnvironment serverEnvironment) + public WorldManager(ILoggerFactory loggerFactory, IPacketBroadcaster packetBroadcaster, IServerEnvironment serverEnvironment) { - this.server = server; this.logger = loggerFactory.CreateLogger(); this.serverWorlds = serverEnvironment.ServerWorlds; this.logger.LogInformation("Instantiated."); + this.loggerFactory = loggerFactory; + this.packetBroadcaster = packetBroadcaster; } protected async override Task ExecuteAsync(CancellationToken stoppingToken) @@ -52,14 +55,15 @@ public async Task LoadWorldsAsync() { foreach (var serverWorld in this.serverWorlds) { - var server = (Server)this.server; - if (!server.WorldGenerators.TryGetValue(serverWorld.Generator, out var value)) - { - this.logger.LogError("Unknown generator type {generator} for world {worldName}", serverWorld.Generator, serverWorld.Name); - return; - } - - var world = new World(serverWorld.Name, server, serverWorld.Seed, value); + //var server = (Server)this.server; + //if (!server.WorldGenerators.TryGetValue(serverWorld.Generator, out var value)) + //{ + // this.logger.LogError("Unknown generator type {generator} for world {worldName}", serverWorld.Generator, serverWorld.Name); + // return; + //} + + //TODO fix + var world = new World(serverWorld.Name, serverWorld.Seed, this.loggerFactory.CreateLogger($"World [{serverWorld.Name}]"), this.packetBroadcaster, null); this.worlds.Add(world.Name, world); if (!CodecRegistry.TryGetDimension(serverWorld.DefaultDimension, out var defaultCodec) || !CodecRegistry.TryGetDimension("minecraft:overworld", out defaultCodec)) From 2393b34f4f678992d20f5ef9298773433fab5ac5 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 07:03:17 -0500 Subject: [PATCH 05/28] Move world generator registering to WorldManager --- Obsidian.API/_Interfaces/IWorldManager.cs | 2 + Obsidian/Server.cs | 29 ------------- Obsidian/WorldData/WorldManager.cs | 53 +++++++++++++++++++---- 3 files changed, 47 insertions(+), 37 deletions(-) diff --git a/Obsidian.API/_Interfaces/IWorldManager.cs b/Obsidian.API/_Interfaces/IWorldManager.cs index 39e5bbdcf..6fd7bc853 100644 --- a/Obsidian.API/_Interfaces/IWorldManager.cs +++ b/Obsidian.API/_Interfaces/IWorldManager.cs @@ -3,6 +3,8 @@ namespace Obsidian.API; public interface IWorldManager : IAsyncDisposable { + public Dictionary WorldGenerators { get; } + public int GeneratingChunkCount { get; } public int LoadedChunkCount { get; } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 2e9a1539f..4915396d5 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -78,7 +78,6 @@ public static string VERSION public IWorldManager WorldManager { get; } public ConcurrentDictionary OnlinePlayers { get; } = new(); - public Dictionary WorldGenerators { get; } = new(); public HashSet RegisteredChannels { get; } = new(); public CommandHandler CommandsHandler { get; } @@ -231,20 +230,6 @@ public void BroadcastMessage(string message) _logger.LogInformation(message); } - /// - /// Registers new world generator(s) to the server. - /// - /// A compatible list of entries. - public void RegisterWorldGenerator() where T : IWorldGenerator, new() - { - var gen = new T(); - if (string.IsNullOrWhiteSpace(gen.Id)) - throw new InvalidOperationException($"Failed to get id for generator: {gen.Id}"); - - if (this.WorldGenerators.TryAdd(gen.Id, typeof(T))) - this._logger.LogDebug($"Registered {gen.Id}..."); - } - /// /// Starts this server asynchronously. /// @@ -270,7 +255,6 @@ public async Task RunAsync() _logger.LogInformation($"Loading properties..."); await (Operators as OperatorList).InitializeAsync(); - RegisterDefaults(); ScoreboardManager = new ScoreboardManager(this); _logger.LogInformation("Loading plugins..."); @@ -641,7 +625,6 @@ public async Task StopAsync() await _tcpListener.UnbindAsync(); } - WorldGenerators.Clear(); foreach (var client in _clients) { client.Disconnect(); @@ -728,18 +711,6 @@ await player.SendSoundAsync(SoundEffectBuilder.Create(SoundId.EntitySheepAmbient await WorldManager.FlushLoadedWorldsAsync(); } - /// - /// Registers the "obsidian-vanilla" entities and objects. - /// - /// Might be used for more stuff later so I'll leave this here - tides - private void RegisterDefaults() - { - RegisterWorldGenerator(); - RegisterWorldGenerator(); - RegisterWorldGenerator(); - RegisterWorldGenerator(); - } - internal void UpdateStatusConsole() { var status = $" tps:{Tps} c:{WorldManager.GeneratingChunkCount}/{WorldManager.LoadedChunkCount} r:{WorldManager.RegionCount}"; diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 43e45d93d..c7817e422 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -3,6 +3,7 @@ using Obsidian.Hosting; using Obsidian.Registries; using Obsidian.Services; +using Obsidian.WorldData.Generators; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Threading; @@ -16,15 +17,16 @@ public sealed class WorldManager : BackgroundService, IWorldManager private readonly List serverWorlds; private readonly ILoggerFactory loggerFactory; private readonly IPacketBroadcaster packetBroadcaster; + private readonly IServerEnvironment serverEnvironment; public int GeneratingChunkCount => worlds.Values.Sum(w => w.ChunksToGenCount); - public int RegionCount => worlds.Values.Sum(pair => pair.RegionCount); - public int LoadedChunkCount => worlds.Values.Sum(pair => pair.LoadedChunkCount); public IWorld DefaultWorld { get; private set; } + public Dictionary WorldGenerators { get; } = new(); + public WorldManager(ILoggerFactory loggerFactory, IPacketBroadcaster packetBroadcaster, IServerEnvironment serverEnvironment) { this.logger = loggerFactory.CreateLogger(); @@ -33,12 +35,14 @@ public WorldManager(ILoggerFactory loggerFactory, IPacketBroadcaster packetBroad this.logger.LogInformation("Instantiated."); this.loggerFactory = loggerFactory; this.packetBroadcaster = packetBroadcaster; + this.serverEnvironment = serverEnvironment; } protected async override Task ExecuteAsync(CancellationToken stoppingToken) { var timer = new BalancingTimer(1000, stoppingToken); + this.RegisterDefaults(); // TODO: This should defenitly accept a cancellation token. // If Cancel is called, this method should stop within the configured timeout, otherwise code execution will simply stop here, // and server shutdown will not be handled correctly. @@ -51,19 +55,40 @@ protected async override Task ExecuteAsync(CancellationToken stoppingToken) } } + /// + /// Registers new world generator(s) to the server. + /// + /// A compatible list of entries. + public void RegisterGenerator() where T : IWorldGenerator, new() + { + var gen = new T(); + if (string.IsNullOrWhiteSpace(gen.Id)) + throw new InvalidOperationException($"Failed to get id for generator: {gen.Id}"); + + if (this.WorldGenerators.TryAdd(gen.Id, typeof(T))) + this.logger.LogDebug("Registered {generatorId}...", gen.Id); + } + public async Task LoadWorldsAsync() { foreach (var serverWorld in this.serverWorlds) { //var server = (Server)this.server; - //if (!server.WorldGenerators.TryGetValue(serverWorld.Generator, out var value)) - //{ - // this.logger.LogError("Unknown generator type {generator} for world {worldName}", serverWorld.Generator, serverWorld.Name); - // return; - //} + if (!this.WorldGenerators.TryGetValue(serverWorld.Generator, out var generatorType)) + { + this.logger.LogError("Unknown generator type {generator} for world {worldName}", serverWorld.Generator, serverWorld.Name); + return; + } //TODO fix - var world = new World(serverWorld.Name, serverWorld.Seed, this.loggerFactory.CreateLogger($"World [{serverWorld.Name}]"), this.packetBroadcaster, null); + var world = new World(this.loggerFactory.CreateLogger($"World [{serverWorld.Name}]"), generatorType, this) + { + Configuration = this.serverEnvironment.Configuration, + PacketBroadcaster = this.packetBroadcaster, + Name = serverWorld.Name, + Seed = serverWorld.Seed + }; + this.worlds.Add(world.Name, world); if (!CodecRegistry.TryGetDimension(serverWorld.DefaultDimension, out var defaultCodec) || !CodecRegistry.TryGetDimension("minecraft:overworld", out defaultCodec)) @@ -130,4 +155,16 @@ public async ValueTask DisposeAsync() this.Dispose(); } + + /// + /// Registers the "obsidian-vanilla" entities and objects. + /// + /// Might be used for more stuff later so I'll leave this here - tides + private void RegisterDefaults() + { + this.RegisterGenerator(); + this.RegisterGenerator(); + this.RegisterGenerator(); + this.RegisterGenerator(); + } } From 7307ac8387ffee26bac6f1930d18c6158239e6ef Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 07:15:36 -0500 Subject: [PATCH 06/28] abstractions pt.3 --- Obsidian.API/_Interfaces/IConfig.cs | 2 + Obsidian.API/_Interfaces/IEntity.cs | 2 - Obsidian.API/_Interfaces/IWorld.cs | 2 +- Obsidian/Entities/Entity.cs | 28 ++--- Obsidian/Server.cs | 154 ------------------------- Obsidian/Services/PacketBroadcaster.cs | 2 +- Obsidian/Utilities/Extensions.cs | 15 ++- Obsidian/WorldData/Region.cs | 44 +++---- Obsidian/WorldData/World.cs | 127 ++++++++++++-------- 9 files changed, 131 insertions(+), 245 deletions(-) diff --git a/Obsidian.API/_Interfaces/IConfig.cs b/Obsidian.API/_Interfaces/IConfig.cs index f7c356e20..0373c0b52 100644 --- a/Obsidian.API/_Interfaces/IConfig.cs +++ b/Obsidian.API/_Interfaces/IConfig.cs @@ -33,6 +33,8 @@ public interface IServerConfiguration /// Maximum amount of players that is allowed to be connected at the same time. /// public int MaxPlayers { get; set; } + public int PregenerateChunkRange { get; set; } + public int TimeTickSpeedMultiplier { get; set; } public bool AllowOperatorRequests { get; set; } diff --git a/Obsidian.API/_Interfaces/IEntity.cs b/Obsidian.API/_Interfaces/IEntity.cs index 15981424d..1ea2fbffe 100644 --- a/Obsidian.API/_Interfaces/IEntity.cs +++ b/Obsidian.API/_Interfaces/IEntity.cs @@ -4,8 +4,6 @@ namespace Obsidian.API; public interface IEntity { - public IServer Server { get; } - public IWorld World { get; } public INavigator? Navigator { get; set; } diff --git a/Obsidian.API/_Interfaces/IWorld.cs b/Obsidian.API/_Interfaces/IWorld.cs index ebba49893..2762ada58 100644 --- a/Obsidian.API/_Interfaces/IWorld.cs +++ b/Obsidian.API/_Interfaces/IWorld.cs @@ -34,7 +34,7 @@ public interface IWorld : IAsyncDisposable public Task GetWorldSurfaceHeightAsync(int x, int z); public Task SpawnEntityAsync(VectorF position, EntityType type); - public Task SpawnExperienceOrbs(VectorF position, short count); + public void SpawnExperienceOrbs(VectorF position, short count); public Task DoWorldTickAsync(); public Task FlushRegionsAsync(); diff --git a/Obsidian/Entities/Entity.cs b/Obsidian/Entities/Entity.cs index b3e928c0d..2ea853fca 100644 --- a/Obsidian/Entities/Entity.cs +++ b/Obsidian/Entities/Entity.cs @@ -1,6 +1,7 @@ using Obsidian.API.AI; using Obsidian.Net; using Obsidian.Net.Packets.Play.Clientbound; +using Obsidian.Services; using Obsidian.WorldData; using System.Diagnostics.CodeAnalysis; @@ -10,8 +11,7 @@ public class Entity : IEquatable, IEntity { protected virtual ConcurrentDictionary Attributes { get; } = new(); - public required IServer Server { get => server; init => server = (Server)value; } - protected Server server = null!; + public required IPacketBroadcaster PacketBroadcaster { get; init; } public required IWorld World { get => world; init => world = (World)value; } internal World world = null!; @@ -76,7 +76,7 @@ internal virtual async Task UpdateAsync(VectorF position, bool onGround) { var delta = (Vector)((position * 32 - Position * 32) * 128); - server.BroadcastPacket(new UpdateEntityPositionPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionPacket { EntityId = EntityId, @@ -100,7 +100,7 @@ internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch if (isNewRotation) { - server.BroadcastPacket(new UpdateEntityPositionAndRotationPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionAndRotationPacket { EntityId = EntityId, @@ -112,7 +112,7 @@ internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch OnGround = onGround }, EntityId); - server.BroadcastPacket(new SetHeadRotationPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new SetHeadRotationPacket { EntityId = EntityId, HeadYaw = yaw @@ -120,7 +120,7 @@ internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch } else { - server.BroadcastPacket(new UpdateEntityPositionPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionPacket { EntityId = EntityId, @@ -140,7 +140,7 @@ internal virtual Task UpdateAsync(Angle yaw, Angle pitch, bool onGround) if (isNewRotation) { - server.BroadcastPacket(new UpdateEntityRotationPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityRotationPacket { EntityId = EntityId, OnGround = onGround, @@ -148,7 +148,7 @@ internal virtual Task UpdateAsync(Angle yaw, Angle pitch, bool onGround) Pitch = pitch }, EntityId); - server.BroadcastPacket(new SetHeadRotationPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new SetHeadRotationPacket { EntityId = EntityId, HeadYaw = yaw @@ -210,7 +210,7 @@ public VectorF GetLookDirection() return new(-cosPitch * sinYaw, -sinPitch, cosPitch * cosYaw); } - public Task RemoveAsync() => world.DestroyEntityAsync(this); + public async Task RemoveAsync() => await this.world.DestroyEntityAsync(this); private EntityBitMask GenerateBitmask() { @@ -298,7 +298,7 @@ public async Task DamageAsync(IEntity source, float amount = 1.0f) if (this is ILiving living) { - await server.QueueBroadcastPacketAsync(new EntityAnimationPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new EntityAnimationPacket { EntityId = EntityId, Animation = EntityAnimationType.TakeDamage @@ -363,7 +363,7 @@ public async virtual Task TeleportAsync(IEntity to) if (VectorF.Distance(Position, to.Position) > 8) { - await server.QueueBroadcastPacketAsync(new TeleportEntityPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new TeleportEntityPacket { EntityId = EntityId, OnGround = OnGround, @@ -377,7 +377,7 @@ await server.QueueBroadcastPacketAsync(new TeleportEntityPacket var delta = (Vector)(to.Position * 32 - Position * 32) * 128; - await server.QueueBroadcastPacketAsync(new UpdateEntityPositionAndRotationPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionAndRotationPacket { EntityId = EntityId, Delta = delta, @@ -391,7 +391,7 @@ public async virtual Task TeleportAsync(VectorF pos) { if (VectorF.Distance(Position, pos) > 8) { - await server.QueueBroadcastPacketAsync(new TeleportEntityPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new TeleportEntityPacket { EntityId = EntityId, OnGround = OnGround, @@ -405,7 +405,7 @@ await server.QueueBroadcastPacketAsync(new TeleportEntityPacket var delta = (Vector)(pos * 32 - Position * 32) * 128; - await server.QueueBroadcastPacketAsync(new UpdateEntityPositionAndRotationPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionAndRotationPacket { EntityId = EntityId, Delta = delta, diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 4915396d5..150d3552e 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -451,27 +451,6 @@ internal async Task QueueBroadcastPacketAsync(IClientboundPacket packet, params await player.client.QueuePacketAsync(packet); } - internal void BroadcastPacket(IClientboundPacket packet) - { - foreach (Player player in Players) - { - player.client.SendPacket(packet); - } - } - - internal void BroadcastPacket(IClientboundPacket packet, params int[] excluded) - { - foreach (Player player in Players.Where(x => !excluded.Contains(x.EntityId))) - player.client.SendPacket(packet); - } - - internal async Task BroadcastNewCommandsAsync() - { - CommandsRegistry.Register(this); - foreach (Player player in Players) - await player.client.SendCommandsAsync(); - } - internal async Task DisconnectIfConnectedAsync(string username, ChatMessage? reason = null) { var player = Players.FirstOrDefault(x => x.Username == username); @@ -483,139 +462,6 @@ internal async Task DisconnectIfConnectedAsync(string username, ChatMessage? rea } } - private bool TryAddEntity(World world, Entity entity) => world.TryAddEntity(entity); - - private void DropItem(Player player, sbyte amountToRemove) - { - var droppedItem = player.GetHeldItem(); - - if (droppedItem is null or { Type: Material.Air }) - return; - - var loc = new VectorF(player.Position.X, (float)player.HeadY - 0.3f, player.Position.Z); - - var item = new ItemEntity - { - EntityId = player + player.world.GetTotalLoadedEntities() + 1, - Count = amountToRemove, - Id = droppedItem.AsItem().Id, - Glowing = true, - World = player.world, - Server = player.Server, - Position = loc - }; - - TryAddEntity(player.world, item); - - var lookDir = player.GetLookDirection(); - - var vel = Velocity.FromDirection(loc, lookDir);//TODO properly shoot the item towards the direction the players looking at - - BroadcastPacket(new SpawnEntityPacket - { - EntityId = item.EntityId, - Uuid = item.Uuid, - Type = EntityType.Item, - Position = item.Position, - Pitch = 0, - Yaw = 0, - Data = 1, - Velocity = vel - }); - BroadcastPacket(new SetEntityMetadataPacket - { - EntityId = item.EntityId, - Entity = item - }); - - player.Inventory.RemoveItem(player.inventorySlot, amountToRemove); - - player.client.SendPacket(new SetContainerSlotPacket - { - Slot = player.inventorySlot, - - WindowId = 0, - - SlotData = player.GetHeldItem(), - - StateId = player.Inventory.StateId++ - }); - - } - - internal void BroadcastPlayerAction(PlayerActionStore store, IBlock block) - { - var action = store.Packet; - - if (!OnlinePlayers.TryGetValue(store.Player, out var player))//This should NEVER return false but who knows :))) - return; - - switch (action.Status) - { - case PlayerActionStatus.DropItem: - { - DropItem(player, 1); - break; - } - case PlayerActionStatus.DropItemStack: - { - DropItem(player, 64); - break; - } - case PlayerActionStatus.StartedDigging: - case PlayerActionStatus.CancelledDigging: - break; - case PlayerActionStatus.FinishedDigging: - { - BroadcastPacket(new SetBlockDestroyStagePacket - { - EntityId = player, - Position = action.Position, - DestroyStage = -1 - }); - - var droppedItem = ItemsRegistry.Get(block.Material); - - if (droppedItem.Id == 0) { break; } - - var item = new ItemEntity - { - EntityId = player + player.world.GetTotalLoadedEntities() + 1, - Count = 1, - Id = droppedItem.Id, - Glowing = true, - World = player.world, - Position = action.Position, - Server = this - }; - - TryAddEntity(player.world, item); - - BroadcastPacket(new SpawnEntityPacket - { - EntityId = item.EntityId, - Uuid = item.Uuid, - Type = EntityType.Item, - Position = item.Position, - Pitch = 0, - Yaw = 0, - Data = 1, - Velocity = Velocity.FromVector(new VectorF( - Globals.Random.NextFloat() * 0.5f, - Globals.Random.NextFloat() * 0.5f, - Globals.Random.NextFloat() * 0.5f)) - }); - - BroadcastPacket(new SetEntityMetadataPacket - { - EntityId = item.EntityId, - Entity = item - }); - break; - } - } - } - public async Task StopAsync() { _cancelTokenSource.Cancel(); diff --git a/Obsidian/Services/PacketBroadcaster.cs b/Obsidian/Services/PacketBroadcaster.cs index 188ba9502..bc6182818 100644 --- a/Obsidian/Services/PacketBroadcaster.cs +++ b/Obsidian/Services/PacketBroadcaster.cs @@ -32,7 +32,7 @@ public void QueuePacket(IClientboundPacket packet, int priority, params int[] ex protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(50)); + using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(20)); while (await timer.WaitForNextTickAsync(stoppingToken)) { diff --git a/Obsidian/Utilities/Extensions.cs b/Obsidian/Utilities/Extensions.cs index 2595253a0..f5b67e6ab 100644 --- a/Obsidian/Utilities/Extensions.cs +++ b/Obsidian/Utilities/Extensions.cs @@ -1,17 +1,18 @@ using Microsoft.AspNetCore.Connections; +using Obsidian.API; using Obsidian.Entities; using Obsidian.Net; using Obsidian.Net.Packets; +using Obsidian.Net.Packets.Play.Clientbound; using Obsidian.Registries; +using Obsidian.Services; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq.Expressions; -using System.Numerics; using System.Security.Cryptography; using System.Text.Json; using System.Threading; - #nullable enable namespace Obsidian.Utilities; @@ -54,6 +55,14 @@ public static partial class Extensions EntityType.FishingBobber, EntityType.EyeOfEnder}; + public static void BroadcastBlockChange(this IPacketBroadcaster packetBroadcaster, IWorld world, IBlock block, Vector location) + { + var packet = new BlockUpdatePacket(location, block.GetHashCode()); + foreach (Player player in world.PlayersInRange(world, location)) + { + player.client.SendPacket(packet); + } + } public static void WritePacketId(this IPacket packet, MinecraftStream stream) { @@ -108,7 +117,7 @@ public static string MinecraftShaDigest(this IEnumerable data) // Reverse the bytes since BigInteger uses little endian Array.Reverse(hash); - var b = new BigInteger(hash); + var b = new System.Numerics.BigInteger(hash); // very annoyingly, BigInteger in C# tries to be smart and puts in // a leading 0 when formatting as a hex number to allow roundtripping // of negative numbers, thus we have to trim it off. diff --git a/Obsidian/WorldData/Region.cs b/Obsidian/WorldData/Region.cs index c0d0818ec..87bcce5eb 100644 --- a/Obsidian/WorldData/Region.cs +++ b/Obsidian/WorldData/Region.cs @@ -133,33 +133,35 @@ internal async Task SerializeChunkAsync(Chunk chunk) await regionFile.SetChunkAsync(x, z, strm.ToArray()); } - internal async Task BeginTickAsync(CancellationToken cts) + internal async Task BeginTickAsync(CancellationToken cts = default) { - var timer = new BalancingTimer(50, cts); - while (await timer.WaitForNextTickAsync()) - { - await Task.WhenAll(Entities.Select(entityEntry => entityEntry.Value.TickAsync())); + //var timer = new BalancingTimer(50, cts); + //while (await timer.WaitForNextTickAsync()) + //{ + + //} + + await Task.WhenAll(Entities.Select(entityEntry => entityEntry.Value.TickAsync())); - List neighborUpdates = new(); - List delayed = new(); + List neighborUpdates = new(); + List delayed = new(); - foreach (var pos in blockUpdates.Keys) + foreach (var pos in blockUpdates.Keys) + { + blockUpdates.Remove(pos, out var bu); + if (bu.delayCounter > 0) { - blockUpdates.Remove(pos, out var bu); - if (bu.delayCounter > 0) - { - bu.delayCounter--; - delayed.Add(bu); - } - else - { - bool updateNeighbor = await bu.world.HandleBlockUpdateAsync(bu); - if (updateNeighbor) { neighborUpdates.Add(bu); } - } + bu.delayCounter--; + delayed.Add(bu); + } + else + { + bool updateNeighbor = await bu.world.HandleBlockUpdateAsync(bu); + if (updateNeighbor) { neighborUpdates.Add(bu); } } - delayed.ForEach(i => AddBlockUpdate(i)); - neighborUpdates.ForEach(async u => await u.world.BlockUpdateNeighborsAsync(u)); } + delayed.ForEach(i => AddBlockUpdate(i)); + neighborUpdates.ForEach(async u => await u.world.BlockUpdateNeighborsAsync(u)); } #region NBT Ops diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index c38c29bfe..678cc2011 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -1,5 +1,4 @@ using Microsoft.Extensions.Logging; -using Obsidian.API.Logging; using Obsidian.API.Registry.Codecs.Dimensions; using Obsidian.API.Utilities; using Obsidian.Blocks; @@ -15,9 +14,14 @@ namespace Obsidian.WorldData; public class World : IWorld { + private readonly IWorldManager worldManager; + private float rainLevel = 0f; private bool initialized = false; + private ConcurrentDictionary<(int x, int z), (int x, int z)> moduloCache = new(); + + internal Dictionary dimensions = new(); public Level LevelData { get; internal set; } @@ -34,8 +38,8 @@ public class World : IWorld public ConcurrentHashSet<(int X, int Z)> LoadedChunks { get; private set; } = new(); - public string Name { get; } - public string Seed { get; } + public required string Name { get; init; } + public required string Seed { get; init; } public string FolderPath { get; private set; } public string PlayerDataPath { get; private set; } public string LevelDataFilePath { get; private set; } @@ -48,37 +52,39 @@ public class World : IWorld public int ChunksToGenCount => this.ChunksToGen.Count; public int LoadedChunkCount => this.Regions.Values.Sum(x => x.LoadedChunkCount); + public required IPacketBroadcaster PacketBroadcaster { get; init; } + public required IServerConfiguration Configuration { get; init; } + public Gamemode DefaultGamemode => LevelData.DefaultGamemode; public string DimensionName { get; private set; } public string? ParentWorldName { get; private set; } private WorldLight worldLight; + /// /// Used to log actions caused by the client. /// protected ILogger Logger { get; } - internal World(string name, string seed, ILogger logger, IPacketBroadcaster packetBroadcaster, Type generatorType) + internal World(ILogger logger, Type generatorType, IWorldManager worldManager) { - Name = name ?? throw new ArgumentNullException(nameof(name)); - Seed = seed ?? throw new ArgumentNullException(nameof(seed)); Logger = logger; - this.packetBroadcaster = packetBroadcaster; - Generator = Activator.CreateInstance(generatorType) as IWorldGenerator ?? throw new ArgumentException("Invalid generator type.", nameof(generatorType)); Generator.Init(this); worldLight = new(this); + + this.worldManager = worldManager; } public int GetTotalLoadedEntities() => Regions.Values.Sum(e => e == null ? 0 : e.Entities.Count); - public async Task DestroyEntityAsync(Entity entity) + public ValueTask DestroyEntityAsync(Entity entity) { var destroyed = new RemoveEntitiesPacket(entity); - await Server.QueueBroadcastPacketAsync(destroyed); + this.PacketBroadcaster.QueuePacketToWorld(this, destroyed); var (chunkX, chunkZ) = entity.Position.ToChunkCoord(); @@ -87,7 +93,7 @@ public async Task DestroyEntityAsync(Entity entity) if (region is null) throw new InvalidOperationException("Region is null this wasn't supposed to happen."); - return region.Entities.TryRemove(entity.EntityId, out _); + return ValueTask.FromResult(region.Entities.TryRemove(entity.EntityId, out _)); } public Region? GetRegionForLocation(VectorF location) @@ -206,7 +212,8 @@ public async Task SetBlockEntity(int x, int y, int z, NbtCompound tileEntityData public async Task SetBlockAsync(Vector location, IBlock block) { await SetBlockUntrackedAsync(location.X, location.Y, location.Z, block); - Server.BroadcastBlockChange(this, block, location); + + this.BroadcastBlockChange(block, location); } public Task SetBlockAsync(int x, int y, int z, IBlock block, bool doBlockUpdate) => SetBlockAsync(new Vector(x, y, z), block, doBlockUpdate); @@ -214,9 +221,22 @@ public async Task SetBlockAsync(Vector location, IBlock block) public async Task SetBlockAsync(Vector location, IBlock block, bool doBlockUpdate) { await SetBlockUntrackedAsync(location.X, location.Y, location.Z, block, doBlockUpdate); - Server.BroadcastBlockChange(this, block, location); + this.BroadcastBlockChange(block, location); + } + + //TODO ????? + private void BroadcastBlockChange(IBlock block, Vector location) + { + var packet = new BlockUpdatePacket(location, block.GetHashCode()); + foreach (Player player in this.PlayersInRange(location)) + { + player.client.SendPacket(packet); + } } + public IEnumerable PlayersInRange(Vector location) => + this.Players.Values.Where(player => player.client.LoadedChunks.Contains(location.ToChunkCoord())); + public Task SetBlockUntrackedAsync(Vector location, IBlock block, bool doBlockUpdate = false) => SetBlockUntrackedAsync(location.X, location.Y, location.Z, block, doBlockUpdate); public async Task SetBlockUntrackedAsync(int x, int y, int z, IBlock block, bool doBlockUpdate = false) @@ -340,8 +360,8 @@ public IEnumerable GetPlayersInRange(VectorF location, float distance) /// public async Task DoWorldTickAsync() { - LevelData.Time += Server.Config.TimeTickSpeedMultiplier; - LevelData.RainTime -= Server.Config.TimeTickSpeedMultiplier; + LevelData.Time += this.Configuration.TimeTickSpeedMultiplier; + LevelData.RainTime -= this.Configuration.TimeTickSpeedMultiplier; if (LevelData.RainTime < 1) { @@ -360,7 +380,7 @@ public async Task DoWorldTickAsync() } LevelData.RainTime = rainTime; - Logger.LogInformation($"Toggled rain: {LevelData.Raining} for {LevelData.RainTime} ticks."); + Logger.LogInformation("Toggled rain: {raining} for {rainTime} ticks.", LevelData.Raining, LevelData.RainTime); } // Gradually increase and decrease rain levels based on @@ -374,17 +394,20 @@ public async Task DoWorldTickAsync() if (oldLevel != rainLevel) { // send new level if updated - Server.BroadcastPacket(new GameEventPacket(ChangeGameStateReason.RainLevelChange, rainLevel)); + this.PacketBroadcaster.QueuePacketToWorld(this, new GameEventPacket(ChangeGameStateReason.RainLevelChange, rainLevel)); if (rainLevel < 0.3f && rainLevel > 0.1f) - Server.BroadcastPacket(new GameEventPacket(LevelData.Raining ? ChangeGameStateReason.BeginRaining : ChangeGameStateReason.EndRaining)); + this.PacketBroadcaster.QueuePacketToWorld(this, new GameEventPacket(LevelData.Raining ? ChangeGameStateReason.BeginRaining : ChangeGameStateReason.EndRaining)); } - if (LevelData.Time % (20 * Server.Config.TimeTickSpeedMultiplier) == 0) + if (LevelData.Time % (20 * this.Configuration.TimeTickSpeedMultiplier) == 0) { // Update client time every second / 20 ticks - Server.BroadcastPacket(new UpdateTimePacket(LevelData.Time, LevelData.Time % 24000)); + this.PacketBroadcaster.QueuePacketToWorld(this, new UpdateTimePacket(LevelData.Time, LevelData.Time % 24000)); } + //Tick regions within the world manager + await Task.WhenAll(this.Regions.Values.Select(r => r.BeginTickAsync())); + //// Check for chunks to load every second //if (LevelData.Time % 20 == 0) //{ @@ -400,7 +423,7 @@ public async Task LoadAsync(DimensionCodec codec) Init(codec); - var dataPath = Path.Join(Server.ServerFolderPath, "worlds", ParentWorldName ?? Name, "level.dat"); + var dataPath = Path.Combine("worlds", ParentWorldName ?? Name, "level.dat"); var fi = new FileInfo(dataPath); @@ -409,7 +432,7 @@ public async Task LoadAsync(DimensionCodec codec) var reader = new NbtReader(fi.OpenRead(), NbtCompression.GZip); - var levelCompound = reader.ReadNextTag() as NbtCompound; + var levelCompound = (reader.ReadNextTag() as NbtCompound)!; LevelData = new Level() { Hardcore = levelCompound.GetBool("hardcore"), @@ -447,9 +470,10 @@ public async Task LoadAsync(DimensionCodec codec) await Parallel.ForEachAsync(SpawnChunks, async (c, cts) => { await GetChunkAsync(c.X, c.Z); - // Update status occasionally so we're not destroying consoleio - if (c.X % 5 == 0) - Server.UpdateStatusConsole(); + //// Update status occasionally so we're not destroying consoleio + //// Removing this for now + //if (c.X % 5 == 0) + // Server.UpdateStatusConsole(); }); Loaded = true; @@ -524,7 +548,7 @@ public async Task UnloadPlayerAsync(Guid uuid) if (await region.InitAsync()) { Regions.TryAdd(value, region);//Add after its initialized - _ = Task.Run(() => region.BeginTickAsync(Server._cancelTokenSource.Token)); + //_ = Task.Run(() => region.BeginTickAsync(Server._cancelTokenSource.Token)); } return region; @@ -545,8 +569,6 @@ public async Task ScheduleBlockUpdateAsync(BlockUpdate blockUpdate) region?.AddBlockUpdate(blockUpdate); } - private ConcurrentDictionary<(int x, int z), (int x, int z)> moduloCache = new(); - private readonly IPacketBroadcaster packetBroadcaster; public async Task ManageChunksAsync() { @@ -615,11 +637,11 @@ public IEntity SpawnFallingBlock(VectorF position, Material mat) Type = EntityType.FallingBlock, EntityId = GetTotalLoadedEntities() + 1, World = this, - Server = Server, + PacketBroadcaster = this.PacketBroadcaster, Block = BlocksRegistry.Get(mat) }; - Server.BroadcastPacket(new SpawnEntityPacket + this.PacketBroadcaster.QueuePacketToWorld(this, new SpawnEntityPacket { EntityId = entity.EntityId, Uuid = entity.Uuid, @@ -657,7 +679,7 @@ public async Task SpawnEntityAsync(VectorF position, EntityType type) Position = position, EntityId = GetTotalLoadedEntities() + 1, World = this, - Server = Server + PacketBroadcaster = this.PacketBroadcaster }; if (type == EntityType.ExperienceOrb || type == EntityType.ExperienceBottle) @@ -666,7 +688,7 @@ public async Task SpawnEntityAsync(VectorF position, EntityType type) } else { - await Server.QueueBroadcastPacketAsync(new SpawnEntityPacket + this.PacketBroadcaster.QueuePacketToWorld(this, new SpawnEntityPacket { EntityId = entity.EntityId, Uuid = entity.Uuid, @@ -687,10 +709,10 @@ await Server.QueueBroadcastPacketAsync(new SpawnEntityPacket EntityId = GetTotalLoadedEntities() + 1, Type = type, World = this, - Server = Server + PacketBroadcaster = this.PacketBroadcaster }; - await Server.QueueBroadcastPacketAsync(new SpawnEntityPacket + this.PacketBroadcaster.QueuePacketToWorld(this, new SpawnEntityPacket { EntityId = entity.EntityId, Uuid = entity.Uuid, @@ -713,18 +735,24 @@ public void RegisterDimension(DimensionCodec codec, string? worldGeneratorId = n if (dimensions.ContainsKey(codec.Name)) throw new ArgumentException($"World already contains dimension with name: {codec.Name}"); - if (!Server.WorldGenerators.TryGetValue(worldGeneratorId ?? codec.Name.TrimResourceTag(true), out var generatorType)) + if (!this.worldManager.WorldGenerators.TryGetValue(worldGeneratorId ?? codec.Name.TrimResourceTag(true), out var generatorType)) throw new ArgumentException($"Failed to find generator with id: {worldGeneratorId}."); - var dimensionWorld = new World(codec.Name.TrimResourceTag(true), Server, Seed, generatorType); + //TODO CREATE NEW TYPE CALLED DIMENSION AND IDIMENSION + var dimensionWorld = new World(this.Logger, generatorType, this.worldManager) + { + PacketBroadcaster = this.PacketBroadcaster, + Configuration = this.Configuration, + Name = codec.Name.TrimResourceTag(true), + Seed = this.Seed + }; dimensionWorld.Init(codec, Name); dimensions.Add(codec.Name, dimensionWorld); } - public Task SpawnExperienceOrbs(VectorF position, short count = 1) => - Server.QueueBroadcastPacketAsync(new SpawnExperienceOrbPacket(count, position)); + public void SpawnExperienceOrbs(VectorF position, short count = 1) => this.PacketBroadcaster.QueuePacketToWorld(this, new SpawnExperienceOrbPacket(count, position)); /// /// @@ -773,15 +801,15 @@ internal void Init(DimensionCodec codec, string? parentWorldName = null) // Make sure we set the right paths if (string.IsNullOrWhiteSpace(parentWorldName)) { - FolderPath = Path.Combine(Server.ServerFolderPath, "worlds", Name); + FolderPath = Path.Combine("worlds", Name); LevelDataFilePath = Path.Combine(FolderPath, "level.dat"); PlayerDataPath = Path.Combine(FolderPath, "playerdata"); } else { - FolderPath = Path.Combine(Server.ServerFolderPath, "worlds", parentWorldName, Name); - LevelDataFilePath = Path.Combine(Server.ServerFolderPath, "worlds", parentWorldName, "level.dat"); - PlayerDataPath = Path.Combine(Server.ServerFolderPath, "worlds", parentWorldName, "playerdata"); + FolderPath = Path.Combine("worlds", parentWorldName, Name); + LevelDataFilePath = Path.Combine("worlds", parentWorldName, "level.dat"); + PlayerDataPath = Path.Combine("worlds", parentWorldName, "playerdata"); } ParentWorldName = parentWorldName; @@ -806,8 +834,8 @@ internal async Task GenerateWorldAsync(bool setWorldSpawn = false) if (!initialized) throw new InvalidOperationException("World hasn't been initialized please call World.Init() before trying to generate the world."); - Logger.LogInformation($"Generating world... (Config pregeneration size is {Server.Config.PregenerateChunkRange})"); - int pregenerationRange = Server.Config.PregenerateChunkRange; + Logger.LogInformation($"Generating world... (Config pregeneration size is {this.Configuration.PregenerateChunkRange})"); + int pregenerationRange = this.Configuration.PregenerateChunkRange; int regionPregenRange = (pregenerationRange >> Region.CubicRegionSizeShift) + 1; @@ -827,7 +855,7 @@ await Parallel.ForEachAsync(Enumerable.Range(-regionPregenRange, regionPregenRan while (!ChunksToGen.IsEmpty) { await ManageChunksAsync(); - Server.UpdateStatusConsole(); + //Server.UpdateStatusConsole(); } await FlushRegionsAsync(); @@ -848,6 +876,7 @@ internal async Task SetWorldSpawnAsync() { if (LevelData.SpawnPosition.Y != 0) { return; } + var pregenRange = this.Configuration.PregenerateChunkRange; foreach (var region in Regions.Values) { foreach (var chunk in region.GeneratedChunks()) @@ -874,13 +903,13 @@ internal async Task SetWorldSpawnAsync() var worldPos = new VectorF(bx + 0.5f + (chunk.X * 16), by + 1, bz + 0.5f + (chunk.Z * 16)); LevelData.SpawnPosition = worldPos; - Logger.LogInformation($"World Spawn set to {worldPos}"); + Logger.LogInformation("World Spawn set to {worldPos}", worldPos); // Should spawn be far from (0,0), queue up chunks in generation range. // Just feign a request for a chunk and if it doesn't exist, it'll get queued for gen. - for (int x = chunk.X - Server.Config.PregenerateChunkRange; x < chunk.X + Server.Config.PregenerateChunkRange; x++) + for (int x = chunk.X - pregenRange; x < chunk.X + pregenRange; x++) { - for (int z = chunk.Z - Server.Config.PregenerateChunkRange; z < chunk.Z + Server.Config.PregenerateChunkRange; z++) + for (int z = chunk.Z - pregenRange; z < chunk.Z + pregenRange; z++) { await GetChunkAsync(x, z); } @@ -891,7 +920,7 @@ internal async Task SetWorldSpawnAsync() } } } - Logger.LogWarning($"Failed to set World Spawn."); + Logger.LogWarning("Failed to set World Spawn."); } internal bool TryAddEntity(Entity entity) From e13bd36553664d5364979af7def73ecff2033936 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 07:26:19 -0500 Subject: [PATCH 07/28] Use packet broadcaster --- Obsidian/Entities/Player.cs | 32 ++-- .../Play/Serverbound/ClickContainerPacket.cs | 171 +++++++++--------- .../Play/Serverbound/PlayerActionPacket.cs | 138 +++++++++++++- .../Play/Serverbound/PlayerCommandPacket.cs | 2 +- .../Serverbound/SetCreativeModeSlotPacket.cs | 6 +- .../Net/Packets/Play/SetHeldItemPacket.cs | 9 +- Obsidian/Server.cs | 24 +-- Obsidian/Utilities/Extensions.cs | 9 - Obsidian/WorldData/World.cs | 8 +- 9 files changed, 251 insertions(+), 148 deletions(-) diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index b1bb6e9b3..99a724956 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -14,9 +14,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using System.Net; -using static Org.BouncyCastle.Math.EC.ECCurve; namespace Obsidian.Entities; @@ -41,7 +39,8 @@ public sealed partial class Player : Living, IPlayer internal int TeleportId { get; set; } - public bool IsOperator => Server.Operators.IsOperator(this); + //TODO + public bool IsOperator { get; } public string Username { get; } @@ -123,8 +122,6 @@ internal Player(Guid uuid, string username, Client client, World world) Username = username; this.client = client; EntityId = client.id; - var loggerProvider = new LoggerProvider(); - Logger = loggerProvider.CreateLogger("Player"); Inventory = new Container(9 * 5 + 1, InventoryType.Generic) { @@ -137,11 +134,10 @@ internal Player(Guid uuid, string username, Client client, World world) }; base.world = world; - Server = client.Server; Type = EntityType.Player; - PersistentDataFile = Path.Join(server.PersistentDataPath, $"{Uuid}.dat"); - PersistentDataBackupFile = Path.Join(server.PersistentDataPath, $"{Uuid}.dat.old"); + PersistentDataFile = Path.Combine(Server.PersistentDataPath, $"{Uuid}.dat"); + PersistentDataBackupFile = Path.Combine(Server.PersistentDataPath, $"{Uuid}.dat.old"); Health = 20f; } @@ -152,7 +148,7 @@ internal Player(Guid uuid, string username, Client client, World world) public async Task LoadPermsAsync() { // Load a JSON file that contains all permissions - var file = new FileInfo(Path.Combine(server.PermissionPath, $"{Uuid}.json")); + var file = new FileInfo(Path.Combine(Server.PermissionPath, $"{Uuid}.json")); if (file.Exists) { @@ -165,7 +161,7 @@ public async Task LoadPermsAsync() public async Task SavePermsAsync() { // Save permissions to JSON file - var file = new FileInfo(Path.Combine(server.PermissionPath, $"{Uuid}.json")); + var file = new FileInfo(Path.Combine(Server.PermissionPath, $"{Uuid}.json")); await using var fs = file.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite); @@ -446,18 +442,20 @@ public override void Write(MinecraftStream stream) public async Task SetGamemodeAsync(Gamemode gamemode) { - await client.Server.QueueBroadcastPacketAsync(new PlayerInfoUpdatePacket(CompilePlayerInfo(new UpdateGamemodeInfoAction(gamemode)))); + this.PacketBroadcaster.QueuePacketToWorld(this.World, new PlayerInfoUpdatePacket(CompilePlayerInfo(new UpdateGamemodeInfoAction(gamemode)))); await client.QueuePacketAsync(new GameEventPacket(gamemode)); Gamemode = gamemode; } - public async Task UpdateDisplayNameAsync(string newDisplayName) + public Task UpdateDisplayNameAsync(string newDisplayName) { - await client.Server.QueueBroadcastPacketAsync(new PlayerInfoUpdatePacket(CompilePlayerInfo(new UpdateDisplayNameInfoAction(newDisplayName)))); + this.PacketBroadcaster.QueuePacketToWorld(this.World, new PlayerInfoUpdatePacket(CompilePlayerInfo(new UpdateDisplayNameInfoAction(newDisplayName)))); CustomName = newDisplayName; + + return Task.CompletedTask; } public async Task SendTitleAsync(ChatMessage title, int fadeIn, int stay, int fadeOut) @@ -663,7 +661,7 @@ public async Task LoadAsync(bool loadFromPersistentWorld = true) Logger.LogInformation($"persistent world: {worldName}"); - if (loadFromPersistentWorld && server.WorldManager.TryGetWorld(worldName, out var world)) + if (loadFromPersistentWorld && this.world.WorldManager.TryGetWorld(worldName, out var world)) { base.world = world; Logger.LogInformation($"Loading from persistent world: {worldName}"); @@ -899,8 +897,8 @@ await client.QueuePacketAsync(new SpawnPlayerPacket } } - var removed = visiblePlayers.Where(x => Server.GetPlayer(x) == null || !world.Players.Any(p => p.Value == x)).ToArray(); - visiblePlayers.RemoveWhere(x => Server.GetPlayer(x) == null || !world.Players.Any(p => p.Value == x)); + var removed = visiblePlayers.Where(x => !world.Players.Any(p => p.Value == x)).ToArray(); + visiblePlayers.RemoveWhere(x => !world.Players.Any(p => p.Value == x)); if (removed.Length > 0) await client.QueuePacketAsync(new RemoveEntitiesPacket(removed)); @@ -913,7 +911,7 @@ private async Task PickupNearbyItemsAsync(float distance = 0.5f) if (entity is not ItemEntity item) continue; - server.BroadcastPacket(new PickupItemPacket + this.PacketBroadcaster.QueuePacketToWorld(this.World, new PickupItemPacket { CollectedEntityId = item.EntityId, CollectorEntityId = EntityId, diff --git a/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs index 661cb919f..40c1d3201 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using Obsidian.API.Events; +using Obsidian.API.Events; using Obsidian.Entities; using Obsidian.Nbt; using Obsidian.Net.Packets.Play.Clientbound; @@ -68,115 +67,115 @@ public async ValueTask HandleAsync(Server server, Player player) switch (Mode) { case InventoryOperationMode.MouseClick: - { - if (CarriedItem == null) - return; - - await HandleMouseClick(container, server, player, slot); - break; - } + { + if (CarriedItem == null) + return; + + await HandleMouseClick(container, server, player, slot); + break; + } case InventoryOperationMode.ShiftMouseClick: - { - if (CarriedItem == null) - return; + { + if (CarriedItem == null) + return; - //TODO implement shift click + //TODO implement shift click - break; - } + break; + } case InventoryOperationMode.NumberKeys: - { - var localSlot = Button + 36; + { + var localSlot = Button + 36; - var currentItem = player.Inventory.GetItem(localSlot); + var currentItem = player.Inventory.GetItem(localSlot); - if (currentItem.IsAir && CarriedItem != null) - { - container.RemoveItem(slot); - - player.Inventory.SetItem(localSlot, CarriedItem); - } - else if (!currentItem.IsAir && CarriedItem != null) - { - container.SetItem(slot, currentItem); + if (currentItem.IsAir && CarriedItem != null) + { + container.RemoveItem(slot); - player.Inventory.SetItem(localSlot, CarriedItem); - } - else - { - container.SetItem(slot, currentItem); + player.Inventory.SetItem(localSlot, CarriedItem); + } + else if (!currentItem.IsAir && CarriedItem != null) + { + container.SetItem(slot, currentItem); - player.Inventory.RemoveItem(localSlot); - } + player.Inventory.SetItem(localSlot, CarriedItem); + } + else + { + container.SetItem(slot, currentItem); - break; + player.Inventory.RemoveItem(localSlot); } + break; + } + case InventoryOperationMode.MiddleMouseClick: break; case InventoryOperationMode.Drop: + { + if (ClickedSlot != Outsideinventory) { - if (ClickedSlot != Outsideinventory) + ItemStack? removedItem; + if (Button == 0) + container.RemoveItem(slot, 1, out removedItem); + else + container.RemoveItem(slot, 64, out removedItem); + + if (removedItem == null) + return; + + var loc = new VectorF(player.Position.X, (float)player.HeadY - 0.3f, player.Position.Z); + + var item = new ItemEntity { - ItemStack? removedItem; - if (Button == 0) - container.RemoveItem(slot, 1, out removedItem); - else - container.RemoveItem(slot, 64, out removedItem); - - if (removedItem == null) - return; - - var loc = new VectorF(player.Position.X, (float)player.HeadY - 0.3f, player.Position.Z); - - var item = new ItemEntity - { - EntityId = player + player.world.GetTotalLoadedEntities() + 1, - Count = 1, - Id = removedItem.AsItem().Id, - Glowing = true, - World = player.world, - Server = player.Server, - Position = loc - }; - - var lookDir = player.GetLookDirection(); - var vel = Velocity.FromDirection(loc, lookDir); - - //TODO Get this shooting out from the player properly. - server.BroadcastPacket(new SpawnEntityPacket - { - EntityId = item.EntityId, - Uuid = item.Uuid, - Type = EntityType.Item, - Position = item.Position, - Pitch = 0, - Yaw = 0, - Data = 1, - Velocity = vel - }); - server.BroadcastPacket(new SetEntityMetadataPacket - { - EntityId = item.EntityId, - Entity = item - }); - } - break; + EntityId = player + player.world.GetTotalLoadedEntities() + 1, + Count = 1, + Id = removedItem.AsItem().Id, + Glowing = true, + World = player.world, + PacketBroadcaster = player.PacketBroadcaster, + Position = loc + }; + + var lookDir = player.GetLookDirection(); + var vel = Velocity.FromDirection(loc, lookDir); + + //TODO Get this shooting out from the player properly. + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SpawnEntityPacket + { + EntityId = item.EntityId, + Uuid = item.Uuid, + Type = EntityType.Item, + Position = item.Position, + Pitch = 0, + Yaw = 0, + Data = 1, + Velocity = vel + }); + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SetEntityMetadataPacket + { + EntityId = item.EntityId, + Entity = item + }); } + break; + } case InventoryOperationMode.MouseDrag: HandleDragClick(container, player, slot); break; case InventoryOperationMode.DoubleClick: - { - if (CarriedItem == null || CarriedItem.Count >= 64) - return; + { + if (CarriedItem == null || CarriedItem.Count >= 64) + return; - TakeFromContainer(container, player.Inventory); - break; - } + TakeFromContainer(container, player.Inventory); + break; + } } if (container is IBlockEntity tileEntityContainer) diff --git a/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs index 1598a5faf..ebff48cf0 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Logging; -using Obsidian.API.Events; +using Obsidian.API.Events; using Obsidian.Entities; using Obsidian.Net.Packets.Play.Clientbound; using Obsidian.Registries; @@ -25,8 +24,7 @@ public partial class PlayerActionPacket : IServerboundPacket public async ValueTask HandleAsync(Server server, Player player) { - IBlock? b = await player.world.GetBlockAsync(Position); - if (b is not IBlock) + if (await player.world.GetBlockAsync(Position) is not IBlock block) return; if (Status == PlayerActionStatus.FinishedDigging || (Status == PlayerActionStatus.StartedDigging && player.Gamemode == Gamemode.Creative)) @@ -37,16 +35,138 @@ public async ValueTask HandleAsync(Server server, Player player) SequenceID = Sequence }); - var blockBreakEvent = await server.Events.BlockBreak.InvokeAsync(new BlockBreakEventArgs(server, player, b, Position)); + var blockBreakEvent = await server.Events.BlockBreak.InvokeAsync(new BlockBreakEventArgs(server, player, block, Position)); if (blockBreakEvent.Handled) return; } - server.BroadcastPlayerAction(new PlayerActionStore + this.BroadcastPlayerAction(player, block); + } + + private void BroadcastPlayerAction(Player player, IBlock block) + { + switch (this.Status) + { + case PlayerActionStatus.DropItem: + { + DropItem(player, 1); + break; + } + case PlayerActionStatus.DropItemStack: + { + DropItem(player, 64); + break; + } + case PlayerActionStatus.StartedDigging: + case PlayerActionStatus.CancelledDigging: + break; + case PlayerActionStatus.FinishedDigging: + { + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SetBlockDestroyStagePacket + { + EntityId = player, + Position = this.Position, + DestroyStage = -1 + }); + + var droppedItem = ItemsRegistry.Get(block.Material); + + if (droppedItem.Id == 0) { break; } + + var item = new ItemEntity + { + EntityId = player + player.world.GetTotalLoadedEntities() + 1, + Count = 1, + Id = droppedItem.Id, + Glowing = true, + World = player.world, + Position = this.Position, + PacketBroadcaster = player.PacketBroadcaster, + }; + + player.world.TryAddEntity(item); + + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SpawnEntityPacket + { + EntityId = item.EntityId, + Uuid = item.Uuid, + Type = EntityType.Item, + Position = item.Position, + Pitch = 0, + Yaw = 0, + Data = 1, + Velocity = Velocity.FromVector(new VectorF( + Globals.Random.NextFloat() * 0.5f, + Globals.Random.NextFloat() * 0.5f, + Globals.Random.NextFloat() * 0.5f)) + }); + + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SetEntityMetadataPacket + { + EntityId = item.EntityId, + Entity = item + }); + break; + } + } + } + + private void DropItem(Player player, sbyte amountToRemove) + { + var droppedItem = player.GetHeldItem(); + + if (droppedItem is null or { Type: Material.Air }) + return; + + var loc = new VectorF(player.Position.X, (float)player.HeadY - 0.3f, player.Position.Z); + + var item = new ItemEntity + { + EntityId = player + player.world.GetTotalLoadedEntities() + 1, + Count = amountToRemove, + Id = droppedItem.AsItem().Id, + Glowing = true, + World = player.world, + PacketBroadcaster = player.PacketBroadcaster, + Position = loc + }; + + player.world.TryAddEntity(item); + + var lookDir = player.GetLookDirection(); + + var vel = Velocity.FromDirection(loc, lookDir);//TODO properly shoot the item towards the direction the players looking at + + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SpawnEntityPacket { - Player = player.Uuid, - Packet = this - }, b); + EntityId = item.EntityId, + Uuid = item.Uuid, + Type = EntityType.Item, + Position = item.Position, + Pitch = 0, + Yaw = 0, + Data = 1, + Velocity = vel + }); + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SetEntityMetadataPacket + { + EntityId = item.EntityId, + Entity = item + }); + + player.Inventory.RemoveItem(player.inventorySlot, amountToRemove); + + player.client.SendPacket(new SetContainerSlotPacket + { + Slot = player.inventorySlot, + + WindowId = 0, + + SlotData = player.GetHeldItem(), + + StateId = player.Inventory.StateId++ + }); + } } diff --git a/Obsidian/Net/Packets/Play/Serverbound/PlayerCommandPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/PlayerCommandPacket.cs index 7cff13b82..3037d70e5 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/PlayerCommandPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/PlayerCommandPacket.cs @@ -57,7 +57,7 @@ public async ValueTask HandleAsync(Server server, Player player) break; } - await server.QueueBroadcastPacketAsync(new SetEntityMetadataPacket + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SetEntityMetadataPacket { EntityId = player.EntityId, Entity = player diff --git a/Obsidian/Net/Packets/Play/Serverbound/SetCreativeModeSlotPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/SetCreativeModeSlotPacket.cs index e537ce7d7..13a3482e9 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/SetCreativeModeSlotPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/SetCreativeModeSlotPacket.cs @@ -14,7 +14,7 @@ public partial class SetCreativeModeSlotPacket : IServerboundPacket public int Id => 0x2E; - public async ValueTask HandleAsync(Server server, Player player) + public ValueTask HandleAsync(Server server, Player player) { var inventory = player.OpenedContainer ?? player.Inventory; @@ -31,7 +31,7 @@ public async ValueTask HandleAsync(Server server, Player player) { var heldItem = player.GetHeldItem(); - await server.QueueBroadcastPacketAsync(new SetEquipmentPacket + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SetEquipmentPacket { EntityId = player.EntityId, Equipment = new() @@ -44,5 +44,7 @@ await server.QueueBroadcastPacketAsync(new SetEquipmentPacket } }, player); } + + return ValueTask.CompletedTask; } } diff --git a/Obsidian/Net/Packets/Play/SetHeldItemPacket.cs b/Obsidian/Net/Packets/Play/SetHeldItemPacket.cs index 5bef89eaa..28c1ae5df 100644 --- a/Obsidian/Net/Packets/Play/SetHeldItemPacket.cs +++ b/Obsidian/Net/Packets/Play/SetHeldItemPacket.cs @@ -38,13 +38,13 @@ public static SetHeldItemPacket Deserialize(MinecraftStream stream) return packet; } - public async ValueTask HandleAsync(Server server, Player player) + public ValueTask HandleAsync(Server server, Player player) { player.CurrentSlot = Slot; var heldItem = player.GetHeldItem(); - await server.QueueBroadcastPacketAsync(new SetEquipmentPacket + player.PacketBroadcaster.QueuePacketToWorld(player.World, new SetEquipmentPacket { EntityId = player.EntityId, Equipment = new() @@ -55,7 +55,8 @@ await server.QueueBroadcastPacketAsync(new SetEquipmentPacket Item = heldItem } } - }, - excluded: player); + }, player.EntityId); + + return ValueTask.CompletedTask; } } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 150d3552e..7e9c350dd 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -5,7 +5,6 @@ using Obsidian.API.Builders; using Obsidian.API.Crafting; using Obsidian.API.Events; -using Obsidian.API.Logging; using Obsidian.API.Utilities; using Obsidian.Commands; using Obsidian.Commands.Framework; @@ -23,7 +22,6 @@ using Obsidian.Plugins; using Obsidian.Registries; using Obsidian.WorldData; -using Obsidian.WorldData.Generators; using System.Diagnostics; using System.IO; using System.Net; @@ -53,10 +51,12 @@ public static string VERSION #endif public const ProtocolVersion DefaultProtocol = ProtocolVersion.v1_20_2; + public static string PersistentDataPath { get; } = Path.Combine("persistentdata"); + public static string PermissionPath { get; } = Path.Combine("permissions"); + internal static readonly ConcurrentDictionary throttler = new(); internal readonly CancellationTokenSource _cancelTokenSource; - internal string PermissionPath => Path.Combine(ServerFolderPath, "permissions"); private readonly ConcurrentQueue _chatMessagesQueue = new(); private readonly ConcurrentHashSet _clients = new(); @@ -85,8 +85,9 @@ public static string VERSION public ServerConfiguration Config { get; } public IServerConfiguration Configuration => Config; public string Version => VERSION; - public string ServerFolderPath { get; } - public string PersistentDataPath { get; } + + + public string Brand { get; } = "obsidian"; public int Port { get; } public IWorld DefaultWorld => WorldManager.DefaultWorld; @@ -110,7 +111,6 @@ public Server( _rconServer = rconServer; Port = Config.Port; - ServerFolderPath = Directory.GetCurrentDirectory(); Operators = new OperatorList(this); @@ -138,8 +138,6 @@ public Server( Events.PlayerInteract += OnPlayerInteract; Events.ContainerClosed += OnContainerClosed; - PersistentDataPath = Path.Combine(ServerFolderPath, "persistentdata"); - Directory.CreateDirectory(PermissionPath); Directory.CreateDirectory(PersistentDataPath); @@ -259,10 +257,10 @@ public async Task RunAsync() ScoreboardManager = new ScoreboardManager(this); _logger.LogInformation("Loading plugins..."); - Directory.CreateDirectory(Path.Join(ServerFolderPath, "plugins")); + Directory.CreateDirectory("plugins"); PluginManager.DirectoryWatcher.Filters = new[] { ".cs", ".dll" }; - PluginManager.DirectoryWatcher.Watch(Path.Join(ServerFolderPath, "plugins")); + PluginManager.DirectoryWatcher.Watch("plugins"); await Task.WhenAll(Config.DownloadPlugins.Select(path => PluginManager.LoadPluginAsync(path))); @@ -445,12 +443,6 @@ internal async Task QueueBroadcastPacketAsync(IClientboundPacket packet) await player.client.QueuePacketAsync(packet); } - internal async Task QueueBroadcastPacketAsync(IClientboundPacket packet, params int[] excluded) - { - foreach (Player player in Players.Where(x => !excluded.Contains(x.EntityId))) - await player.client.QueuePacketAsync(packet); - } - internal async Task DisconnectIfConnectedAsync(string username, ChatMessage? reason = null) { var player = Players.FirstOrDefault(x => x.Username == username); diff --git a/Obsidian/Utilities/Extensions.cs b/Obsidian/Utilities/Extensions.cs index f5b67e6ab..0eca64c0c 100644 --- a/Obsidian/Utilities/Extensions.cs +++ b/Obsidian/Utilities/Extensions.cs @@ -55,15 +55,6 @@ public static partial class Extensions EntityType.FishingBobber, EntityType.EyeOfEnder}; - public static void BroadcastBlockChange(this IPacketBroadcaster packetBroadcaster, IWorld world, IBlock block, Vector location) - { - var packet = new BlockUpdatePacket(location, block.GetHashCode()); - foreach (Player player in world.PlayersInRange(world, location)) - { - player.client.SendPacket(packet); - } - } - public static void WritePacketId(this IPacket packet, MinecraftStream stream) { stream.Lock.Wait(); diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 678cc2011..13a402673 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -14,7 +14,7 @@ namespace Obsidian.WorldData; public class World : IWorld { - private readonly IWorldManager worldManager; + public IWorldManager WorldManager { get; } private float rainLevel = 0f; private bool initialized = false; @@ -75,7 +75,7 @@ internal World(ILogger logger, Type generatorType, IWorldManager worldManager) Generator.Init(this); worldLight = new(this); - this.worldManager = worldManager; + this.WorldManager = worldManager; } public int GetTotalLoadedEntities() => Regions.Values.Sum(e => e == null ? 0 : e.Entities.Count); @@ -735,11 +735,11 @@ public void RegisterDimension(DimensionCodec codec, string? worldGeneratorId = n if (dimensions.ContainsKey(codec.Name)) throw new ArgumentException($"World already contains dimension with name: {codec.Name}"); - if (!this.worldManager.WorldGenerators.TryGetValue(worldGeneratorId ?? codec.Name.TrimResourceTag(true), out var generatorType)) + if (!this.WorldManager.WorldGenerators.TryGetValue(worldGeneratorId ?? codec.Name.TrimResourceTag(true), out var generatorType)) throw new ArgumentException($"Failed to find generator with id: {worldGeneratorId}."); //TODO CREATE NEW TYPE CALLED DIMENSION AND IDIMENSION - var dimensionWorld = new World(this.Logger, generatorType, this.worldManager) + var dimensionWorld = new World(this.Logger, generatorType, this.WorldManager) { PacketBroadcaster = this.PacketBroadcaster, Configuration = this.Configuration, From da4ad734dff639481293d3768892a35987c33a00 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 07:34:32 -0500 Subject: [PATCH 08/28] Fix event parameters --- Obsidian.API/Events/ContainerClickEventArgs.cs | 2 +- Obsidian.API/Events/ContainerClosedEventArgs.cs | 2 +- Obsidian.API/Events/ContainerEventArgs.cs | 6 ++---- Obsidian.API/Events/IncomingChatMessageEventArgs.cs | 3 ++- Obsidian.API/Events/PacketReceivedEventArgs.cs | 3 ++- Obsidian.API/Events/PermissionGrantedEventArgs.cs | 2 +- Obsidian.API/Events/PermissionRevokedEventArgs.cs | 2 +- Obsidian.API/Events/PlayerEventArgs.cs | 2 +- Obsidian.API/Events/PlayerInteractEventArgs.cs | 2 +- Obsidian.API/Events/PlayerJoinEventArgs.cs | 2 +- Obsidian.API/Events/PlayerLeaveEventArgs.cs | 2 +- Obsidian.API/Events/PlayerTeleportEventArgs.cs | 2 +- Obsidian.API/_Interfaces/ILiving.cs | 2 +- Obsidian/Client.cs | 8 ++++---- Obsidian/Entities/Player.cs | 6 +++--- Obsidian/Net/Packets/Play/CloseContainerPacket.cs | 2 +- .../Net/Packets/Play/Serverbound/ClickContainerPacket.cs | 2 +- Obsidian/Net/Packets/Play/Serverbound/UseItemOnPacket.cs | 2 +- Obsidian/Net/Packets/Play/Serverbound/UseItemPacket.cs | 2 +- Obsidian/Server.cs | 2 +- Obsidian/Utilities/OperatorList.cs | 2 +- 21 files changed, 29 insertions(+), 29 deletions(-) diff --git a/Obsidian.API/Events/ContainerClickEventArgs.cs b/Obsidian.API/Events/ContainerClickEventArgs.cs index b801a3df2..5f017463b 100644 --- a/Obsidian.API/Events/ContainerClickEventArgs.cs +++ b/Obsidian.API/Events/ContainerClickEventArgs.cs @@ -18,7 +18,7 @@ public sealed class ContainerClickEventArgs : ContainerEventArgs, ICancellable public bool IsCancelled { get; private set; } [SetsRequiredMembers] - internal ContainerClickEventArgs(IPlayer player, BaseContainer container, ItemStack item) : base(player) + internal ContainerClickEventArgs(IPlayer player, IServer server, BaseContainer container, ItemStack item) : base(player, server) { this.Container = container; this.Item = item; diff --git a/Obsidian.API/Events/ContainerClosedEventArgs.cs b/Obsidian.API/Events/ContainerClosedEventArgs.cs index 464018723..75924b67d 100644 --- a/Obsidian.API/Events/ContainerClosedEventArgs.cs +++ b/Obsidian.API/Events/ContainerClosedEventArgs.cs @@ -3,7 +3,7 @@ public sealed class ContainerClosedEventArgs : ContainerEventArgs, ICancellable { public bool IsCancelled { get; private set; } - internal ContainerClosedEventArgs(IPlayer player) : base(player) + internal ContainerClosedEventArgs(IPlayer player, IServer server) : base(player, server) { } diff --git a/Obsidian.API/Events/ContainerEventArgs.cs b/Obsidian.API/Events/ContainerEventArgs.cs index c39e4a65c..4babfc1ec 100644 --- a/Obsidian.API/Events/ContainerEventArgs.cs +++ b/Obsidian.API/Events/ContainerEventArgs.cs @@ -1,12 +1,10 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Obsidian.API.Events; +namespace Obsidian.API.Events; public class ContainerEventArgs : PlayerEventArgs { public required BaseContainer Container { get; init; } - protected ContainerEventArgs(IPlayer player) : base(player) + protected ContainerEventArgs(IPlayer player, IServer server) : base(player, server) { } } diff --git a/Obsidian.API/Events/IncomingChatMessageEventArgs.cs b/Obsidian.API/Events/IncomingChatMessageEventArgs.cs index f1c1af41b..662acd75d 100644 --- a/Obsidian.API/Events/IncomingChatMessageEventArgs.cs +++ b/Obsidian.API/Events/IncomingChatMessageEventArgs.cs @@ -21,7 +21,8 @@ public class IncomingChatMessageEventArgs : PlayerEventArgs, ICancellable /// The player which sent the message. /// The message which was sent. /// Any formatting appied to the message. - public IncomingChatMessageEventArgs(IPlayer player, string message, string format) : base(player) + /// The server this took place in. + public IncomingChatMessageEventArgs(IPlayer player, IServer server, string message, string format) : base(player, server) { this.Message = message; this.Format = format; diff --git a/Obsidian.API/Events/PacketReceivedEventArgs.cs b/Obsidian.API/Events/PacketReceivedEventArgs.cs index eef0fedda..a92f43d54 100644 --- a/Obsidian.API/Events/PacketReceivedEventArgs.cs +++ b/Obsidian.API/Events/PacketReceivedEventArgs.cs @@ -25,7 +25,8 @@ public sealed class PacketReceivedEventArgs : PlayerEventArgs, ICancellable /// The player involved in this event. /// Id of the received packet. /// Packet data, excluding packet id and packet length. - public PacketReceivedEventArgs(IPlayer player, int id, ReadOnlyMemory data) : base(player) + /// The server this took place in. + public PacketReceivedEventArgs(IPlayer player, IServer server, int id, ReadOnlyMemory data) : base(player, server) { Id = id; Data = data; diff --git a/Obsidian.API/Events/PermissionGrantedEventArgs.cs b/Obsidian.API/Events/PermissionGrantedEventArgs.cs index 393f95026..7c9f1bf5b 100644 --- a/Obsidian.API/Events/PermissionGrantedEventArgs.cs +++ b/Obsidian.API/Events/PermissionGrantedEventArgs.cs @@ -4,7 +4,7 @@ public class PermissionGrantedEventArgs : PlayerEventArgs { public string Permission { get; } - public PermissionGrantedEventArgs(IPlayer player, string permission) : base(player) + public PermissionGrantedEventArgs(IPlayer player, IServer server, string permission) : base(player, server) { Permission = permission; } diff --git a/Obsidian.API/Events/PermissionRevokedEventArgs.cs b/Obsidian.API/Events/PermissionRevokedEventArgs.cs index 7a97cad2c..65b00a48e 100644 --- a/Obsidian.API/Events/PermissionRevokedEventArgs.cs +++ b/Obsidian.API/Events/PermissionRevokedEventArgs.cs @@ -4,7 +4,7 @@ public class PermissionRevokedEventArgs : PlayerEventArgs { public string Permission { get; } - public PermissionRevokedEventArgs(IPlayer player, string permission) : base(player) + public PermissionRevokedEventArgs(IPlayer player, IServer server, string permission) : base(player, server) { Permission = permission; } diff --git a/Obsidian.API/Events/PlayerEventArgs.cs b/Obsidian.API/Events/PlayerEventArgs.cs index 133f8c554..9769748f6 100644 --- a/Obsidian.API/Events/PlayerEventArgs.cs +++ b/Obsidian.API/Events/PlayerEventArgs.cs @@ -7,7 +7,7 @@ public class PlayerEventArgs : BaseMinecraftEventArgs /// public IPlayer Player { get; } - protected PlayerEventArgs(IPlayer player) : base(player.Server) + protected PlayerEventArgs(IPlayer player, IServer server) : base(server) { Player = player; } diff --git a/Obsidian.API/Events/PlayerInteractEventArgs.cs b/Obsidian.API/Events/PlayerInteractEventArgs.cs index 61fe453d4..9f30911dd 100644 --- a/Obsidian.API/Events/PlayerInteractEventArgs.cs +++ b/Obsidian.API/Events/PlayerInteractEventArgs.cs @@ -27,7 +27,7 @@ public sealed class PlayerInteractEventArgs : PlayerEventArgs, ICancellable /// public bool IsCancelled { get; private set; } - public PlayerInteractEventArgs(IPlayer player) : base(player) { } + public PlayerInteractEventArgs(IPlayer player, IServer server) : base(player, server) { } /// public void Cancel() diff --git a/Obsidian.API/Events/PlayerJoinEventArgs.cs b/Obsidian.API/Events/PlayerJoinEventArgs.cs index 7e15946b4..49a8b8c81 100644 --- a/Obsidian.API/Events/PlayerJoinEventArgs.cs +++ b/Obsidian.API/Events/PlayerJoinEventArgs.cs @@ -7,7 +7,7 @@ public class PlayerJoinEventArgs : PlayerEventArgs /// public DateTimeOffset JoinDate { get; } - public PlayerJoinEventArgs(IPlayer player, DateTimeOffset join) : base(player) + public PlayerJoinEventArgs(IPlayer player, IServer server, DateTimeOffset join) : base(player, server) { this.JoinDate = join; } diff --git a/Obsidian.API/Events/PlayerLeaveEventArgs.cs b/Obsidian.API/Events/PlayerLeaveEventArgs.cs index d8965b165..ddcf71c91 100644 --- a/Obsidian.API/Events/PlayerLeaveEventArgs.cs +++ b/Obsidian.API/Events/PlayerLeaveEventArgs.cs @@ -7,7 +7,7 @@ public class PlayerLeaveEventArgs : PlayerEventArgs /// public DateTimeOffset LeaveDate { get; } - public PlayerLeaveEventArgs(IPlayer player, DateTimeOffset leave) : base(player) + public PlayerLeaveEventArgs(IPlayer player, IServer server, DateTimeOffset leave) : base(player, server) { this.LeaveDate = leave; } diff --git a/Obsidian.API/Events/PlayerTeleportEventArgs.cs b/Obsidian.API/Events/PlayerTeleportEventArgs.cs index af369469a..2331e3113 100644 --- a/Obsidian.API/Events/PlayerTeleportEventArgs.cs +++ b/Obsidian.API/Events/PlayerTeleportEventArgs.cs @@ -5,7 +5,7 @@ public class PlayerTeleportEventArgs : PlayerEventArgs public VectorF OldPosition { get; } public VectorF NewPosition { get; } - public PlayerTeleportEventArgs(IPlayer player, VectorF oldPosition, VectorF newPosition) : base(player) + public PlayerTeleportEventArgs(IPlayer player, IServer server, VectorF oldPosition, VectorF newPosition) : base(player, server) { OldPosition = oldPosition; NewPosition = newPosition; diff --git a/Obsidian.API/_Interfaces/ILiving.cs b/Obsidian.API/_Interfaces/ILiving.cs index 3781f73a6..bc37d55cd 100644 --- a/Obsidian.API/_Interfaces/ILiving.cs +++ b/Obsidian.API/_Interfaces/ILiving.cs @@ -43,7 +43,7 @@ public interface ILiving : IEntity /// Whether to show the particles or not. /// Whether to show the icon on the client or not. /// Whether the potion is emitted by ambient source e.g. the beacon. The icon has a blue border in the HUD if its ambient. - public Task AddPotionEffectAsync(PotionEffect potion, int duration, byte amplifier = 0, bool showParticles = true, + public void AddPotionEffect(PotionEffect potion, int duration, byte amplifier = 0, bool showParticles = true, bool showIcon = true, bool isAmbient = false); /// diff --git a/Obsidian/Client.cs b/Obsidian/Client.cs index 1bbfccfd8..78e58eb66 100644 --- a/Obsidian/Client.cs +++ b/Obsidian/Client.cs @@ -317,7 +317,7 @@ public async Task StartConnectionAsync() case ClientState.Configuration: Debug.Assert(Player is not null); - var configurationPacketReceived = new PacketReceivedEventArgs(Player, id, data); + var configurationPacketReceived = new PacketReceivedEventArgs(Player, this.Server, id, data); await Server.Events.PacketReceived.InvokeAsync(configurationPacketReceived); @@ -328,7 +328,7 @@ public async Task StartConnectionAsync() case ClientState.Play: Debug.Assert(Player is not null); - var playPacketReceived = new PacketReceivedEventArgs(Player, id, data); + var playPacketReceived = new PacketReceivedEventArgs(Player, this.Server, id, data); await Server.Events.PacketReceived.InvokeAsync(playPacketReceived); @@ -347,7 +347,7 @@ public async Task StartConnectionAsync() if (State == ClientState.Play) { Debug.Assert(Player is not null); - await Server.Events.PlayerLeave.InvokeAsync(new PlayerLeaveEventArgs(Player, DateTimeOffset.Now)); + await Server.Events.PlayerLeave.InvokeAsync(new PlayerLeaveEventArgs(Player, this.Server, DateTimeOffset.Now)); } Disconnected?.Invoke(this); @@ -559,7 +559,7 @@ await QueuePacketAsync(new UpdateRecipeBookPacket await SendPlayerInfoAsync(); await Player.UpdateChunksAsync(distance: 7); await SendInfoAsync(); - await Server.Events.PlayerJoin.InvokeAsync(new PlayerJoinEventArgs(Player, DateTimeOffset.Now)); + await Server.Events.PlayerJoin.InvokeAsync(new PlayerJoinEventArgs(Player, this.Server, DateTimeOffset.Now)); } #region Packet sending diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index 99a724956..fdc2676ad 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -2,7 +2,6 @@ // https://wiki.vg/Map_Format using Microsoft.Extensions.Logging; using Obsidian.API.Events; -using Obsidian.API.Logging; using Obsidian.API.Utilities; using Obsidian.Nbt; using Obsidian.Net; @@ -227,6 +226,7 @@ await client.Server.Events.PlayerTeleported.InvokeAsync( new PlayerTeleportEventArgs ( this, + this.client.Server, Position, pos )); @@ -783,7 +783,7 @@ public async Task GrantPermissionAsync(string permissionNode) await SavePermsAsync(); if (result) - await this.client.Server.Events.PermissionGranted.InvokeAsync(new PermissionGrantedEventArgs(this, permissionNode)); + await this.client.Server.Events.PermissionGranted.InvokeAsync(new PermissionGrantedEventArgs(this, this.client.Server, permissionNode)); return result; } @@ -805,7 +805,7 @@ public async Task RevokePermissionAsync(string permissionNode) parent.Children.Remove(childToRemove); await this.SavePermsAsync(); - await this.client.Server.Events.PermissionRevoked.InvokeAsync(new PermissionRevokedEventArgs(this, permissionNode)); + await this.client.Server.Events.PermissionRevoked.InvokeAsync(new PermissionRevokedEventArgs(this, this.client.Server, permissionNode)); return true; } diff --git a/Obsidian/Net/Packets/Play/CloseContainerPacket.cs b/Obsidian/Net/Packets/Play/CloseContainerPacket.cs index 88282f79f..fe8447e02 100644 --- a/Obsidian/Net/Packets/Play/CloseContainerPacket.cs +++ b/Obsidian/Net/Packets/Play/CloseContainerPacket.cs @@ -16,6 +16,6 @@ public async ValueTask HandleAsync(Server server, Player player) if (WindowId == 0) return; - await server.Events.ContainerClosed.InvokeAsync(new ContainerClosedEventArgs(player) { Container = player.OpenedContainer! }); + await server.Events.ContainerClosed.InvokeAsync(new ContainerClosedEventArgs(player, server) { Container = player.OpenedContainer! }); } } diff --git a/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs index 40c1d3201..6a3e7cdcb 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs @@ -275,7 +275,7 @@ private async Task HandleMouseClick(BaseContainer container, Server server, Play { if (!CarriedItem.IsAir) { - var @event = await server.Events.ContainerClick.InvokeAsync(new ContainerClickEventArgs(player, container, CarriedItem) + var @event = await server.Events.ContainerClick.InvokeAsync(new ContainerClickEventArgs(player, server, container, CarriedItem) { Slot = slot }); diff --git a/Obsidian/Net/Packets/Play/Serverbound/UseItemOnPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/UseItemOnPacket.cs index 1902e1730..42eaeb7c9 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/UseItemOnPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/UseItemOnPacket.cs @@ -42,7 +42,7 @@ public async ValueTask HandleAsync(Server server, Player player) if (TagsRegistry.Blocks.PlayersCanInteract.Entries.Contains(b.RegistryId) && !player.Sneaking) { - await server.Events.PlayerInteract.InvokeAsync(new PlayerInteractEventArgs(player) + await server.Events.PlayerInteract.InvokeAsync(new PlayerInteractEventArgs(player, server) { Item = currentItem, Block = b, diff --git a/Obsidian/Net/Packets/Play/Serverbound/UseItemPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/UseItemPacket.cs index b05a4f922..1cf306587 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/UseItemPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/UseItemPacket.cs @@ -16,7 +16,7 @@ public partial class UseItemPacket : IServerboundPacket public async ValueTask HandleAsync(Server server, Player player) { - await server.Events.PlayerInteract.InvokeAsync(new PlayerInteractEventArgs(player) + await server.Events.PlayerInteract.InvokeAsync(new PlayerInteractEventArgs(player, server) { Item = this.Hand == Hand.MainHand ? player.GetHeldItem() : player.GetOffHandItem(), Hand = this.Hand, diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 7e9c350dd..8884255e9 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -428,7 +428,7 @@ internal async Task HandleIncomingMessageAsync(ChatMessagePacket packet, Client var format = "<{0}> {1}"; var message = packet.Message; - var chat = await Events.IncomingChatMessage.InvokeAsync(new IncomingChatMessageEventArgs(source.Player, message, format)); + var chat = await Events.IncomingChatMessage.InvokeAsync(new IncomingChatMessageEventArgs(source.Player, this, message, format)); if (chat.IsCancelled) return; diff --git a/Obsidian/Utilities/OperatorList.cs b/Obsidian/Utilities/OperatorList.cs index 54cfa3e95..8b1d15952 100644 --- a/Obsidian/Utilities/OperatorList.cs +++ b/Obsidian/Utilities/OperatorList.cs @@ -11,7 +11,7 @@ public class OperatorList : IOperatorList private readonly List reqs; private readonly Server server; private readonly ILogger _logger; - private string Path => System.IO.Path.Combine(System.IO.Path.Combine(this.server.ServerFolderPath, "config"), "ops.json"); + private string Path => System.IO.Path.Combine(System.IO.Path.Combine("config"), "ops.json"); public OperatorList(Server server) { From 5aaa36acf1d8071a459461076012874c104476f7 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 07:44:34 -0500 Subject: [PATCH 09/28] Simplify ConsoleApp.Program.cs --- Obsidian.ConsoleApp/Program.cs | 90 ++++++++++------------ Obsidian/Hosting/ObsidianHostingService.cs | 4 +- 2 files changed, 41 insertions(+), 53 deletions(-) diff --git a/Obsidian.ConsoleApp/Program.cs b/Obsidian.ConsoleApp/Program.cs index 0a4e551e6..3949feffa 100644 --- a/Obsidian.ConsoleApp/Program.cs +++ b/Obsidian.ConsoleApp/Program.cs @@ -1,64 +1,52 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Obsidian; using Obsidian.API.Logging; -using Obsidian.API.Utilities; using Obsidian.Hosting; -namespace Obsidian.ConsoleApp; +// Cool startup console logo because that's cool +// 10/10 -IGN +const string asciilogo = + "\n" + + " ▄▄▄▄ ▄▄ ▄▄▄▄ ▀ ▄▄▄ ▐ ▄ \n" + + " ▐█ ▀█ ▐█ ▀ ██ ██ ██ ██ ▐█ ▀█ █▌▐█\n" + + " ▄█▀▄ ▐█▀▀█▄▄▀▀▀█▄▐█ ▐█ ▐█▌▐█ ▄█▀▀█ ▐█▐▐▌\n" + + "▐█▌ ▐▌██▄ ▐█▐█▄ ▐█▐█▌██ ██ ▐█▌▐█ ▐▌██▐█▌\n" + + " ▀█▄▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀ ▀ ▀ ▀▀ █ \n\n"; -public static class Program -{ - private static async Task Main() - { - Console.Title = $"Obsidian for {Server.DefaultProtocol} ({Server.VERSION})"; - Console.BackgroundColor = ConsoleColor.White; - Console.ForegroundColor = ConsoleColor.Black; - Console.CursorVisible = false; - Console.WriteLine(asciilogo); - Console.ResetColor(); - - var env = await IServerEnvironment.CreateDefaultAsync(); - - var loggerProvider = new LoggerProvider(env.Configuration.LogLevel); - var startupLogger = loggerProvider.CreateLogger("Startup"); +Console.Title = $"Obsidian for {Server.DefaultProtocol} ({Server.VERSION})"; +Console.BackgroundColor = ConsoleColor.White; +Console.ForegroundColor = ConsoleColor.Black; +Console.CursorVisible = false; +Console.WriteLine(asciilogo); +Console.ResetColor(); - startupLogger.LogInformation("A C# implementation of the Minecraft server protocol. Targeting: {description}", Server.DefaultProtocol.GetDescription()); +var env = await IServerEnvironment.CreateDefaultAsync(); - var host = Host.CreateDefaultBuilder() - .ConfigureLogging(options => - { - options.ClearProviders(); - options.AddProvider(loggerProvider); - options.SetMinimumLevel(env.Configuration.LogLevel); - // Shhh... Only let Microsoft log when stuff crashes. - //options.AddFilter("Microsoft", LogLevel.Warning); - }) - .ConfigureServices(services => - { - services.AddObsidian(env); +var loggerProvider = new LoggerProvider(env.Configuration.LogLevel); +var startupLogger = loggerProvider.CreateLogger("Startup"); - // Give the server some time to shut down after CTRL-C or SIGTERM. - //TODO SERVICES SET STOP CONCURRENTLY - services.Configure(opts => - { - opts.ShutdownTimeout = TimeSpan.FromSeconds(10); - }); - }) - .Build(); +var builder = Host.CreateApplicationBuilder(); - await host.RunAsync(); - } - - // Cool startup console logo because that's cool - // 10/10 -IGN - private const string asciilogo = - "\n" + - " ▄▄▄▄ ▄▄ ▄▄▄▄ ▀ ▄▄▄ ▐ ▄ \n" + - " ▐█ ▀█ ▐█ ▀ ██ ██ ██ ██ ▐█ ▀█ █▌▐█\n" + - " ▄█▀▄ ▐█▀▀█▄▄▀▀▀█▄▐█ ▐█ ▐█▌▐█ ▄█▀▀█ ▐█▐▐▌\n" + - "▐█▌ ▐▌██▄ ▐█▐█▄ ▐█▐█▌██ ██ ▐█▌▐█ ▐▌██▐█▌\n" + - " ▀█▄▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀ ▀ ▀ ▀▀ █ \n\n"; +builder.Services.AddLogging(loggingBuilder => +{ + loggingBuilder.ClearProviders(); + loggingBuilder.AddProvider(loggerProvider); + loggingBuilder.SetMinimumLevel(env.Configuration.LogLevel); + // Shhh... Only let Microsoft log when stuff crashes. + //options.AddFilter("Microsoft", LogLevel.Warning); +}); + +builder.Services.AddObsidian(env); + +// Give the server some time to shut down after CTRL-C or SIGTERM. +//TODO SERVICES SET STOP CONCURRENTLY +builder.Services.Configure(opts => +{ + opts.ShutdownTimeout = TimeSpan.FromSeconds(10); +}); -} +var app = builder.Build(); +await app.RunAsync(); diff --git a/Obsidian/Hosting/ObsidianHostingService.cs b/Obsidian/Hosting/ObsidianHostingService.cs index a1224002b..c6e8b3a33 100644 --- a/Obsidian/Hosting/ObsidianHostingService.cs +++ b/Obsidian/Hosting/ObsidianHostingService.cs @@ -7,12 +7,12 @@ internal sealed class ObsidianHostingService : BackgroundService { private readonly IHostApplicationLifetime _lifetime; private readonly IServerEnvironment _environment; - private readonly Server _server; + private readonly IServer _server; private readonly ILogger _logger; public ObsidianHostingService( IHostApplicationLifetime lifetime, - Server server, + IServer server, IServerEnvironment env, ILogger logger) { From 922f002fddfe90eb377ad5b5dce25e33b7fabb16 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 07:57:52 -0500 Subject: [PATCH 10/28] Refactor Living.cs --- Obsidian.API/_Interfaces/ILiving.cs | 4 ++-- Obsidian/Entities/Living.cs | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Obsidian.API/_Interfaces/ILiving.cs b/Obsidian.API/_Interfaces/ILiving.cs index bc37d55cd..a3cbdcb25 100644 --- a/Obsidian.API/_Interfaces/ILiving.cs +++ b/Obsidian.API/_Interfaces/ILiving.cs @@ -32,7 +32,7 @@ public interface ILiving : IEntity /// /// Clears all potion effects of the entity. /// - public Task ClearPotionEffects(); + public void ClearPotionEffects(); /// /// Adds the given to the entity. @@ -50,5 +50,5 @@ public void AddPotionEffect(PotionEffect potion, int duration, byte amplifier = /// Removes the given from the entity. /// /// The potion effect to be removed. - public Task RemovePotionEffectAsync(PotionEffect potion); + public void RemovePotionEffect(PotionEffect potion); } diff --git a/Obsidian/Entities/Living.cs b/Obsidian/Entities/Living.cs index b71d096c9..0c741fd5c 100644 --- a/Obsidian/Entities/Living.cs +++ b/Obsidian/Entities/Living.cs @@ -31,7 +31,7 @@ public Living() activePotionEffects = new ConcurrentDictionary(); } - public async override Task TickAsync() + public override Task TickAsync() { foreach (var (potion, data) in activePotionEffects) { @@ -39,9 +39,11 @@ public async override Task TickAsync() if (data.CurrentDuration <= 0) { - await RemovePotionEffectAsync(potion); + RemovePotionEffect(potion); } } + + return Task.CompletedTask; } public bool HasPotionEffect(PotionEffect potion) @@ -49,15 +51,15 @@ public bool HasPotionEffect(PotionEffect potion) return activePotionEffects.ContainsKey(potion); } - public async Task ClearPotionEffects() + public void ClearPotionEffects() { foreach (var (potion, _) in activePotionEffects) { - await RemovePotionEffectAsync(potion); + RemovePotionEffect(potion); } } - public async Task AddPotionEffectAsync(PotionEffect potion, int duration, byte amplifier = 0, bool showParticles = true, + public void AddPotionEffect(PotionEffect potion, int duration, byte amplifier = 0, bool showParticles = true, bool showIcon = true, bool isAmbient = false) { byte flags = 0; @@ -68,7 +70,7 @@ public async Task AddPotionEffectAsync(PotionEffect potion, int duration, byte a if (showIcon) flags |= 0x04; - await this.server.QueueBroadcastPacketAsync(new EntityEffectPacket(EntityId, (int)potion, duration) + this.PacketBroadcaster.QueuePacketToWorld(this.World, new EntityEffectPacket(EntityId, (int)potion, duration) { Amplifier = amplifier, Flags = flags @@ -78,12 +80,12 @@ await this.server.QueueBroadcastPacketAsync(new EntityEffectPacket(EntityId, (in activePotionEffects.AddOrUpdate(potion, _ => data, (_, _) => data); } - public async Task RemovePotionEffectAsync(PotionEffect potion) + public void RemovePotionEffect(PotionEffect potion) { - await this.server.QueueBroadcastPacketAsync(new RemoveEntityEffectPacket(EntityId, (int)potion)); + this.PacketBroadcaster.QueuePacketToWorld(this.World, new RemoveEntityEffectPacket(EntityId, (int)potion)); activePotionEffects.TryRemove(potion, out _); } - + public override async Task WriteAsync(MinecraftStream stream) { From cafb7a6f65f8a5c41c634b10f7ede786247c2ce1 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 08:01:06 -0500 Subject: [PATCH 11/28] A little cleanup --- Obsidian.API/_Interfaces/IServer.cs | 2 ++ Obsidian.ConsoleApp/Program.cs | 7 +------ Obsidian/Hosting/DependencyInjection.cs | 2 +- Obsidian/Server.cs | 2 +- Obsidian/WorldData/WorldManager.cs | 4 +--- 5 files changed, 6 insertions(+), 11 deletions(-) diff --git a/Obsidian.API/_Interfaces/IServer.cs b/Obsidian.API/_Interfaces/IServer.cs index 976ba78e5..fb5aeaaaa 100644 --- a/Obsidian.API/_Interfaces/IServer.cs +++ b/Obsidian.API/_Interfaces/IServer.cs @@ -17,6 +17,8 @@ public interface IServer public IScoreboardManager ScoreboardManager { get; } + public Task RunAsync(); + public bool IsPlayerOnline(string username); public bool IsPlayerOnline(Guid uuid); public void BroadcastMessage(string message); diff --git a/Obsidian.ConsoleApp/Program.cs b/Obsidian.ConsoleApp/Program.cs index 3949feffa..c73fa285f 100644 --- a/Obsidian.ConsoleApp/Program.cs +++ b/Obsidian.ConsoleApp/Program.cs @@ -24,18 +24,13 @@ var env = await IServerEnvironment.CreateDefaultAsync(); -var loggerProvider = new LoggerProvider(env.Configuration.LogLevel); -var startupLogger = loggerProvider.CreateLogger("Startup"); - var builder = Host.CreateApplicationBuilder(); builder.Services.AddLogging(loggingBuilder => { loggingBuilder.ClearProviders(); - loggingBuilder.AddProvider(loggerProvider); + loggingBuilder.AddProvider(new LoggerProvider(env.Configuration.LogLevel)); loggingBuilder.SetMinimumLevel(env.Configuration.LogLevel); - // Shhh... Only let Microsoft log when stuff crashes. - //options.AddFilter("Microsoft", LogLevel.Warning); }); builder.Services.AddObsidian(env); diff --git a/Obsidian/Hosting/DependencyInjection.cs b/Obsidian/Hosting/DependencyInjection.cs index c4ceec7fe..8159bfc62 100644 --- a/Obsidian/Hosting/DependencyInjection.cs +++ b/Obsidian/Hosting/DependencyInjection.cs @@ -19,9 +19,9 @@ public static IServiceCollection AddObsidian(this IServiceCollection services, I services.AddSingleton(); services.AddSingleton(); + services.AddHostedService(); services.AddHostedService(sp => sp.GetRequiredService()); services.AddHostedService(sp => sp.GetRequiredService()); - services.AddHostedService(); services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 8884255e9..87aa8fe40 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -235,7 +235,7 @@ public async Task RunAsync() { StartTime = DateTimeOffset.Now; - _logger.LogInformation($"Launching Obsidian Server v{Version}"); + _logger.LogInformation("Launching Obsidian Server v{Version}", this.Version); var loadTimeStopwatch = Stopwatch.StartNew(); // Check if MPDM and OM are enabled, if so, we can't handle connections diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index c7817e422..83c71134b 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -23,7 +23,7 @@ public sealed class WorldManager : BackgroundService, IWorldManager public int RegionCount => worlds.Values.Sum(pair => pair.RegionCount); public int LoadedChunkCount => worlds.Values.Sum(pair => pair.LoadedChunkCount); - public IWorld DefaultWorld { get; private set; } + public IWorld DefaultWorld { get; private set; } = default!; public Dictionary WorldGenerators { get; } = new(); @@ -31,8 +31,6 @@ public WorldManager(ILoggerFactory loggerFactory, IPacketBroadcaster packetBroad { this.logger = loggerFactory.CreateLogger(); this.serverWorlds = serverEnvironment.ServerWorlds; - - this.logger.LogInformation("Instantiated."); this.loggerFactory = loggerFactory; this.packetBroadcaster = packetBroadcaster; this.serverEnvironment = serverEnvironment; From 9c699fd42e79e9d4f8a03882432b3a87a421e41c Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 08:16:32 -0500 Subject: [PATCH 12/28] WE START WOOOOO --- Obsidian/Hosting/DependencyInjection.cs | 4 ++-- Obsidian/WorldData/World.cs | 3 ++- Obsidian/WorldData/WorldManager.cs | 15 ++++++++++----- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Obsidian/Hosting/DependencyInjection.cs b/Obsidian/Hosting/DependencyInjection.cs index 8159bfc62..c9fa6a7df 100644 --- a/Obsidian/Hosting/DependencyInjection.cs +++ b/Obsidian/Hosting/DependencyInjection.cs @@ -19,10 +19,10 @@ public static IServiceCollection AddObsidian(this IServiceCollection services, I services.AddSingleton(); services.AddSingleton(); - services.AddHostedService(); services.AddHostedService(sp => sp.GetRequiredService()); services.AddHostedService(sp => sp.GetRequiredService()); - + services.AddHostedService(); + services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 13a402673..e06de8558 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -72,7 +72,6 @@ internal World(ILogger logger, Type generatorType, IWorldManager worldManager) { Logger = logger; Generator = Activator.CreateInstance(generatorType) as IWorldGenerator ?? throw new ArgumentException("Invalid generator type.", nameof(generatorType)); - Generator.Init(this); worldLight = new(this); this.WorldManager = worldManager; @@ -80,6 +79,8 @@ internal World(ILogger logger, Type generatorType, IWorldManager worldManager) public int GetTotalLoadedEntities() => Regions.Values.Sum(e => e == null ? 0 : e.Entities.Count); + public void InitGenerator() => this.Generator.Init(this); + public ValueTask DestroyEntityAsync(Entity entity) { var destroyed = new RemoveEntitiesPacket(entity); diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 83c71134b..07722a34e 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Obsidian.Hosting; using Obsidian.Registries; @@ -16,8 +17,8 @@ public sealed class WorldManager : BackgroundService, IWorldManager private readonly Dictionary worlds = new(); private readonly List serverWorlds; private readonly ILoggerFactory loggerFactory; - private readonly IPacketBroadcaster packetBroadcaster; private readonly IServerEnvironment serverEnvironment; + private readonly IServiceScope serviceScope; public int GeneratingChunkCount => worlds.Values.Sum(w => w.ChunksToGenCount); public int RegionCount => worlds.Values.Sum(pair => pair.RegionCount); @@ -27,13 +28,13 @@ public sealed class WorldManager : BackgroundService, IWorldManager public Dictionary WorldGenerators { get; } = new(); - public WorldManager(ILoggerFactory loggerFactory, IPacketBroadcaster packetBroadcaster, IServerEnvironment serverEnvironment) + public WorldManager(ILoggerFactory loggerFactory, IServiceProvider serviceProvider, IServerEnvironment serverEnvironment) { this.logger = loggerFactory.CreateLogger(); this.serverWorlds = serverEnvironment.ServerWorlds; this.loggerFactory = loggerFactory; - this.packetBroadcaster = packetBroadcaster; this.serverEnvironment = serverEnvironment; + this.serviceScope = serviceProvider.CreateScope(); } protected async override Task ExecuteAsync(CancellationToken stoppingToken) @@ -82,11 +83,13 @@ public async Task LoadWorldsAsync() var world = new World(this.loggerFactory.CreateLogger($"World [{serverWorld.Name}]"), generatorType, this) { Configuration = this.serverEnvironment.Configuration, - PacketBroadcaster = this.packetBroadcaster, + PacketBroadcaster = this.serviceScope.ServiceProvider.GetRequiredService(), Name = serverWorld.Name, Seed = serverWorld.Seed }; + world.InitGenerator(); + this.worlds.Add(world.Name, world); if (!CodecRegistry.TryGetDimension(serverWorld.DefaultDimension, out var defaultCodec) || !CodecRegistry.TryGetDimension("minecraft:overworld", out defaultCodec)) @@ -151,6 +154,8 @@ public async ValueTask DisposeAsync() await world.DisposeAsync(); } + this.serviceScope.Dispose(); + this.Dispose(); } From 6fcfd3d2b7e7d0e9e31024f04c8f9e5db2a411e3 Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 08:20:44 -0500 Subject: [PATCH 13/28] Update Player.cs --- Obsidian/Entities/Player.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index fdc2676ad..da16d960a 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -139,6 +139,7 @@ internal Player(Guid uuid, string username, Client client, World world) PersistentDataBackupFile = Path.Combine(Server.PersistentDataPath, $"{Uuid}.dat.old"); Health = 20f; + this.PacketBroadcaster = world.PacketBroadcaster; } public ItemStack? GetHeldItem() => Inventory.GetItem(inventorySlot); From 737f4cd5b043e19a27d753ca58c587771bbeab8f Mon Sep 17 00:00:00 2001 From: Tides Date: Thu, 16 Nov 2023 08:30:52 -0500 Subject: [PATCH 14/28] oop --- Obsidian/Hosting/DependencyInjection.cs | 4 ++-- Obsidian/Server.cs | 4 ++-- Obsidian/WorldData/WorldManager.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Obsidian/Hosting/DependencyInjection.cs b/Obsidian/Hosting/DependencyInjection.cs index c9fa6a7df..570f9382b 100644 --- a/Obsidian/Hosting/DependencyInjection.cs +++ b/Obsidian/Hosting/DependencyInjection.cs @@ -19,10 +19,10 @@ public static IServiceCollection AddObsidian(this IServiceCollection services, I services.AddSingleton(); services.AddSingleton(); - services.AddHostedService(sp => sp.GetRequiredService()); services.AddHostedService(sp => sp.GetRequiredService()); services.AddHostedService(); - + services.AddHostedService(sp => sp.GetRequiredService()); + services.AddSingleton(sp => sp.GetRequiredService()); services.AddSingleton(sp => sp.GetRequiredService()); diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 87aa8fe40..00b200701 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -51,8 +51,8 @@ public static string VERSION #endif public const ProtocolVersion DefaultProtocol = ProtocolVersion.v1_20_2; - public static string PersistentDataPath { get; } = Path.Combine("persistentdata"); - public static string PermissionPath { get; } = Path.Combine("permissions"); + public static string PersistentDataPath { get; } = "persistentdata"; + public static string PermissionPath { get; } = "permissions"; internal static readonly ConcurrentDictionary throttler = new(); diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index 07722a34e..b930f42c3 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -39,7 +39,7 @@ public WorldManager(ILoggerFactory loggerFactory, IServiceProvider serviceProvid protected async override Task ExecuteAsync(CancellationToken stoppingToken) { - var timer = new BalancingTimer(1000, stoppingToken); + var timer = new BalancingTimer(20, stoppingToken); this.RegisterDefaults(); // TODO: This should defenitly accept a cancellation token. From aa6ede6e2b3002f2eb7784b9990b116a0105390f Mon Sep 17 00:00:00 2001 From: Tides Date: Fri, 17 Nov 2023 07:35:46 -0500 Subject: [PATCH 15/28] Fix up some NREs and wait for worlds to be ready --- Obsidian.API/_Interfaces/IWorldManager.cs | 2 ++ Obsidian/Commands/MainCommandModule.cs | 6 +++--- Obsidian/Entities/Player.cs | 4 ++-- Obsidian/Server.cs | 5 +++++ Obsidian/Services/PacketBroadcaster.cs | 6 +++--- Obsidian/WorldData/WorldManager.cs | 5 ++++- 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Obsidian.API/_Interfaces/IWorldManager.cs b/Obsidian.API/_Interfaces/IWorldManager.cs index 6fd7bc853..38e3a89a3 100644 --- a/Obsidian.API/_Interfaces/IWorldManager.cs +++ b/Obsidian.API/_Interfaces/IWorldManager.cs @@ -3,6 +3,8 @@ namespace Obsidian.API; public interface IWorldManager : IAsyncDisposable { + public bool ReadyToJoin { get; } + public Dictionary WorldGenerators { get; } public int GeneratingChunkCount { get; } diff --git a/Obsidian/Commands/MainCommandModule.cs b/Obsidian/Commands/MainCommandModule.cs index d3f08980a..a2ff380cd 100644 --- a/Obsidian/Commands/MainCommandModule.cs +++ b/Obsidian/Commands/MainCommandModule.cs @@ -343,7 +343,7 @@ public async Task DerpAsync(CommandContext ctx, string entityType) } var frogge = await player.World.SpawnEntityAsync(player.Position, type); - var server = player.Server as Server; + var server = (ctx.Server as Server)!; _ = Task.Run(async () => { @@ -363,8 +363,8 @@ public async Task DerpAsync(CommandContext ctx, string entityType) OnGround = false }; - server.BroadcastPacket(rotato); - server.BroadcastPacket(rotato2); + //server.BroadcastPacket(rotato); + //server.BroadcastPacket(rotato2); await Task.Delay(15); } }); diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index 7f07392e2..a1b409ee8 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -660,12 +660,12 @@ public async Task LoadAsync(bool loadFromPersistentWorld = true) { var worldName = persistentDataCompound.GetString("worldName"); - Logger.LogInformation($"persistent world: {worldName}"); + Logger?.LogInformation("persistent world: {worldName}", worldName); if (loadFromPersistentWorld && this.world.WorldManager.TryGetWorld(worldName, out var world)) { base.world = world; - Logger.LogInformation($"Loading from persistent world: {worldName}"); + Logger?.LogInformation("Loading from persistent world: {worldName}", worldName); } } } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 76f492f99..505dc2432 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -281,6 +281,11 @@ public async Task RunAsync() loadTimeStopwatch.Stop(); _logger.LogInformation("Server loaded in {time}", loadTimeStopwatch.Elapsed); + + //Wait for worlds to load + while (!this.WorldManager.ReadyToJoin && !this._cancelTokenSource.IsCancellationRequested) + continue; + _logger.LogInformation("Listening for new clients..."); try diff --git a/Obsidian/Services/PacketBroadcaster.cs b/Obsidian/Services/PacketBroadcaster.cs index bc6182818..043bf6fc9 100644 --- a/Obsidian/Services/PacketBroadcaster.cs +++ b/Obsidian/Services/PacketBroadcaster.cs @@ -41,13 +41,13 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) if (queuedPacket.ToWorld is World toWorld) { - foreach (var player in toWorld.Players.Values.Where(player => !queuedPacket.ExcludedIds.Contains(player.EntityId))) + foreach (var player in toWorld.Players.Values.Where(player => queuedPacket.ExcludedIds != null && !queuedPacket.ExcludedIds.Contains(player.EntityId))) await player.client.QueuePacketAsync(queuedPacket.Packet); continue; } - foreach (var player in this.server.Players.Cast().Where(player => !queuedPacket.ExcludedIds.Contains(player.EntityId))) + foreach (var player in this.server.Players.Cast().Where(player => queuedPacket.ExcludedIds != null && !queuedPacket.ExcludedIds.Contains(player.EntityId))) await player.client.QueuePacketAsync(queuedPacket.Packet); } } @@ -56,7 +56,7 @@ private readonly struct QueuedPacket { public required IClientboundPacket Packet { get; init; } - public int[] ExcludedIds { get; init; } + public int[]? ExcludedIds { get; init; } public IWorld? ToWorld { get; init; } } diff --git a/Obsidian/WorldData/WorldManager.cs b/Obsidian/WorldData/WorldManager.cs index b930f42c3..bb13b01b9 100644 --- a/Obsidian/WorldData/WorldManager.cs +++ b/Obsidian/WorldData/WorldManager.cs @@ -20,6 +20,8 @@ public sealed class WorldManager : BackgroundService, IWorldManager private readonly IServerEnvironment serverEnvironment; private readonly IServiceScope serviceScope; + public bool ReadyToJoin { get; private set; } + public int GeneratingChunkCount => worlds.Values.Sum(w => w.ChunksToGenCount); public int RegionCount => worlds.Values.Sum(pair => pair.RegionCount); public int LoadedChunkCount => worlds.Values.Sum(pair => pair.LoadedChunkCount); @@ -76,7 +78,7 @@ public async Task LoadWorldsAsync() if (!this.WorldGenerators.TryGetValue(serverWorld.Generator, out var generatorType)) { this.logger.LogError("Unknown generator type {generator} for world {worldName}", serverWorld.Generator, serverWorld.Name); - return; + continue; } //TODO fix @@ -127,6 +129,7 @@ public async Task LoadWorldsAsync() //No default world was defined so choose the first one to come up this.DefaultWorld ??= this.worlds.FirstOrDefault().Value; + this.ReadyToJoin = true; } public IReadOnlyCollection GetAvailableWorlds() => this.worlds.Values.ToList().AsReadOnly(); From 0663c3a605971d2140f92def004c68fa23df57ab Mon Sep 17 00:00:00 2001 From: Tides Date: Fri, 17 Nov 2023 08:04:02 -0500 Subject: [PATCH 16/28] Region loading does not need to be blocked --- Obsidian/WorldData/World.cs | 48 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index e06de8558..7301ab31f 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -9,6 +9,7 @@ using Obsidian.Registries; using Obsidian.Services; using System.IO; +using System.Threading; namespace Obsidian.WorldData; @@ -61,7 +62,7 @@ public class World : IWorld public string? ParentWorldName { get; private set; } private WorldLight worldLight; - + /// /// Used to log actions caused by the client. @@ -124,17 +125,10 @@ public ValueTask DestroyEntityAsync(Entity entity) /// Null if the region or chunk doesn't exist yet. Otherwise the full chunk or a partial chunk. public async Task GetChunkAsync(int chunkX, int chunkZ, bool scheduleGeneration = true) { - Region? region = GetRegionForChunk(chunkX, chunkZ); - - if (region is null) - { - region = await LoadRegionAsync(chunkX >> Region.CubicRegionSizeShift, chunkZ >> Region.CubicRegionSizeShift); - } + Region? region = GetRegionForChunk(chunkX, chunkZ) ?? LoadRegion(chunkX >> Region.CubicRegionSizeShift, chunkZ >> Region.CubicRegionSizeShift); if (region is null) - { return null; - } var (x, z) = (NumericsHelper.Modulo(chunkX, Region.CubicRegionSize), NumericsHelper.Modulo(chunkZ, Region.CubicRegionSize)); @@ -361,6 +355,9 @@ public IEnumerable GetPlayersInRange(VectorF location, float distance) /// public async Task DoWorldTickAsync() { + if (LevelData is null) + return; + LevelData.Time += this.Configuration.TimeTickSpeedMultiplier; LevelData.RainTime -= this.Configuration.TimeTickSpeedMultiplier; @@ -459,7 +456,7 @@ public async Task LoadAsync(DimensionCodec codec) Logger.LogInformation($"Loading spawn chunks into memory..."); for (int rx = -1; rx < 1; rx++) for (int rz = -1; rz < 1; rz++) - _ = await LoadRegionAsync(rx, rz); + LoadRegion(rx, rz); // spawn chunks are radius 12 from spawn, var radius = 12; @@ -531,26 +528,30 @@ public async Task UnloadPlayerAsync(Guid uuid) } #endregion - public async Task LoadRegionByChunkAsync(int chunkX, int chunkZ) + public Region LoadRegionByChunk(int chunkX, int chunkZ) { int regionX = chunkX >> Region.CubicRegionSizeShift, regionZ = chunkZ >> Region.CubicRegionSizeShift; - return await LoadRegionAsync(regionX, regionZ); + return LoadRegion(regionX, regionZ); } - public async Task LoadRegionAsync(int regionX, int regionZ) + private SemaphoreSlim semaphore = new(1, 1); + + public Region LoadRegion(int regionX, int regionZ) { long value = NumericsHelper.IntsToLong(regionX, regionZ); if (Regions.TryGetValue(value, out var region)) return region; + this.Logger.LogDebug("Trying to add {x}:{z}", regionX, regionZ); + region = new Region(regionX, regionZ, FolderPath); - if (await region.InitAsync()) - { - Regions.TryAdd(value, region);//Add after its initialized - //_ = Task.Run(() => region.BeginTickAsync(Server._cancelTokenSource.Token)); - } + if (Regions.TryAdd(value, region)) + this.Logger.LogDebug("Added region {x}:{z}", regionX, regionZ); + + //DOesn't need to be blocking + _ = region.InitAsync(); return region; } @@ -604,7 +605,7 @@ public async Task ManageChunksAsync() await Parallel.ForEachAsync(jobs, async (job, _) => { - Region region = GetRegionForChunk(job.x, job.z) ?? await LoadRegionByChunkAsync(job.x, job.z); + Region region = GetRegionForChunk(job.x, job.z) ?? LoadRegionByChunk(job.x, job.z); var (x, z) = (NumericsHelper.Modulo(job.x, Region.CubicRegionSize), NumericsHelper.Modulo(job.z, Region.CubicRegionSize)); @@ -835,14 +836,17 @@ internal async Task GenerateWorldAsync(bool setWorldSpawn = false) if (!initialized) throw new InvalidOperationException("World hasn't been initialized please call World.Init() before trying to generate the world."); - Logger.LogInformation($"Generating world... (Config pregeneration size is {this.Configuration.PregenerateChunkRange})"); + Logger.LogInformation("Generating world... (Config pregeneration size is {pregenRange})", this.Configuration.PregenerateChunkRange); int pregenerationRange = this.Configuration.PregenerateChunkRange; int regionPregenRange = (pregenerationRange >> Region.CubicRegionSizeShift) + 1; - await Parallel.ForEachAsync(Enumerable.Range(-regionPregenRange, regionPregenRange * 2 + 1), async (x, _) => + await Parallel.ForEachAsync(Enumerable.Range(-regionPregenRange, regionPregenRange * 2 + 1), (x, _) => { - for (int z = -regionPregenRange; z < regionPregenRange; z++) await LoadRegionAsync(x, z); + for (int z = -regionPregenRange; z < regionPregenRange; z++) + LoadRegion(x, z); + + return ValueTask.CompletedTask; }); for (int x = -pregenerationRange; x < pregenerationRange; x++) From 93dde3df7a0232457bab6a4a79c6c2bd99c8c43b Mon Sep 17 00:00:00 2001 From: Tides Date: Fri, 17 Nov 2023 08:17:15 -0500 Subject: [PATCH 17/28] Add a direct send method that won't get processed through the queue --- Obsidian/Services/PacketBroadcaster.cs | 84 ++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/Obsidian/Services/PacketBroadcaster.cs b/Obsidian/Services/PacketBroadcaster.cs index 043bf6fc9..b58541ef6 100644 --- a/Obsidian/Services/PacketBroadcaster.cs +++ b/Obsidian/Services/PacketBroadcaster.cs @@ -18,17 +18,32 @@ public PacketBroadcaster(IServer server, ILoggerFactory loggerFactory) this.logger = loggerFactory.CreateLogger(); } - public void QueuePacket(IClientboundPacket packet, params int[] exluded) => - this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = exluded }, 1); + public void QueuePacket(IClientboundPacket packet, params int[] excludedIds) => + this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = excludedIds }, 1); - public void QueuePacketToWorld(IWorld world, IClientboundPacket packet, params int[] exluded) => - this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = exluded }, 1); + public void QueuePacketToWorld(IWorld world, IClientboundPacket packet, params int[] excludedIds) => + this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = excludedIds }, 1); - public void QueuePacketToWorld(IWorld world, int priority, IClientboundPacket packet, params int[] exluded) => - this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = exluded, ToWorld = world }, priority); + public void QueuePacketToWorld(IWorld world, int priority, IClientboundPacket packet, params int[] excludedIds) => + this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = excludedIds, ToWorld = world }, priority); - public void QueuePacket(IClientboundPacket packet, int priority, params int[] exluded) => - this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = exluded }, priority); + public void QueuePacket(IClientboundPacket packet, int priority, params int[] excludedIds) => + this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = excludedIds }, priority); + + public void Broadcast(IClientboundPacket packet, params int[] excludedIds) + { + foreach (var player in this.server.Players.Cast().Where(player => excludedIds.Contains(player.EntityId))) + player.client.SendPacket(packet); + } + + public void BroadcastToWorld(IWorld toWorld, IClientboundPacket packet, params int[] excludedIds) + { + if (toWorld is not World world) + return; + + foreach (var player in world.Players.Values.Where(player => excludedIds.Contains(player.EntityId))) + player.client.SendPacket(packet); + } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { @@ -64,8 +79,53 @@ private readonly struct QueuedPacket public interface IPacketBroadcaster { - public void QueuePacketToWorld(IWorld world, IClientboundPacket packet, params int[] exluded); - public void QueuePacket(IClientboundPacket packet, params int[] exluded); - public void QueuePacketToWorld(IWorld world, int priority, IClientboundPacket packet, params int[] exluded); - public void QueuePacket(IClientboundPacket packet, int priority, params int[] exluded); + /// + /// Sends the packets directly to connected clients without processing in a queue. + /// + /// The packet to send. + /// The list of entity ids to exlude from the broadcast. + public void Broadcast(IClientboundPacket packet, params int[] excludedIds); + + + /// + /// Sends the packets directly to connected clients without processing in a queue. + /// + /// The world to broadcast this packet to. + /// The packet to send. + /// The list of entity ids to exlude from the broadcast. + public void BroadcastToWorld(IWorld toWorld, IClientboundPacket packet, params int[] excludedIds); + + /// + /// Puts the packet in a priority queue for processing then broadcasting when dequeued. + /// + /// The world to broadcast this packet to. + /// The packet to send. + /// The list of entity ids to exlude from the broadcast. + /// /// Packets queued without a priority set will be queued up with a priority of 1. + public void QueuePacketToWorld(IWorld toWorld, IClientboundPacket packet, params int[] excludedIds); + + /// + /// Puts the packet in a priority queue for processing then broadcasting when dequeued. + /// + /// The packet to send. + /// The list of entity ids to exlude from the broadcast. + /// Packets queued without a priority set will be queued up with a priority of 1. + public void QueuePacket(IClientboundPacket packet, params int[] excludedIds); + + /// + /// Puts the packet in a priority queue for processing then broadcasting when dequeued. + /// + /// The world to broadcast this packet to. + /// The priority to set the packet in the queue. Higher priority = better + /// The packet to send. + /// The list of entity ids to exlude from the broadcast. + public void QueuePacketToWorld(IWorld toWorld, int priority, IClientboundPacket packet, params int[] excludedIds); + + /// + /// Puts the packet in a priority queue for processing then broadcasting when dequeued. + /// + /// The priority to set the packet in the queue. Higher priority = better + /// The packet to send. + /// The list of entity ids to exlude from the broadcast. + public void QueuePacket(IClientboundPacket packet, int priority, params int[] excludedIds); } From 6dae353f3a3b2aaf49eba746eeab8fa39abbf930 Mon Sep 17 00:00:00 2001 From: Tides Date: Sat, 18 Nov 2023 06:28:51 -0500 Subject: [PATCH 18/28] Just use JsonNamingPolicy.SnakeCaseLower :)) --- .../Extensions.StringManipulation.cs | 30 +++++++++---------- Obsidian/Globals.cs | 10 +------ 2 files changed, 15 insertions(+), 25 deletions(-) diff --git a/Obsidian.API/Utilities/Extensions.StringManipulation.cs b/Obsidian.API/Utilities/Extensions.StringManipulation.cs index ef56a1d1e..c5dcafbe6 100644 --- a/Obsidian.API/Utilities/Extensions.StringManipulation.cs +++ b/Obsidian.API/Utilities/Extensions.StringManipulation.cs @@ -2,14 +2,13 @@ using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; +using System.Text.Json; using System.Text.RegularExpressions; namespace Obsidian.API.Utilities; public static partial class Extensions { - public static readonly Regex pattern = new(@"[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+"); - /// /// This method is not and shouldn't be used in performance-critical sections. /// Source: https://stackoverflow.com/a/1415187 @@ -38,7 +37,6 @@ public static string ToPascalCase(this string snakeCase) // Alternative implementation: // var textInfo = System.Globalization.CultureInfo.CurrentCulture.TextInfo; // return string.Join("", snakeCase.Split('_').Select(s => textInfo.ToTitleCase(s))); - int spaceCount = 0; for (int i = 0; i < snakeCase.Length; i++) { @@ -94,7 +92,7 @@ public static string Capitalize(this string value) public static bool EqualsIgnoreCase(this string a, string b) => a.Equals(b, StringComparison.OrdinalIgnoreCase); - public static string ToSnakeCase(this string str) => string.Join("_", pattern.Matches(str)).ToLower(); + public static string ToSnakeCase(this string str) => JsonNamingPolicy.SnakeCaseLower.ConvertName(str); /// /// Trims resource tag from the start and removes '_' characters. @@ -132,7 +130,7 @@ public static string TrimResourceTag(this string value, bool keepUnderscores = f }); } - [Obsolete("Do not use. Kept as a masterpiece.")] + //[Obsolete("Do not use. Kept as a masterpiece.")] // This method is an absolute masterpiece. Its author must've entered // the highest plane of existance when writing it. The purpose of this // method is to make a string camelCase, but at all places where it was @@ -153,15 +151,15 @@ public static string TrimResourceTag(this string value, bool keepUnderscores = f // The text appears 8 times in memory, in different forms (including the // returned string). That means that every call produces around 7 * str.Length // * sizeof(char) bytes of garbage. - public static string ToCamelCase(this string str) - { - return new string( - new CultureInfo("en-US", false) - .TextInfo - .ToTitleCase(string.Join(" ", pattern.Matches(str)).ToLower()) - .Replace(@" ", "") - .Select((x, i) => i == 0 ? char.ToLower(x) : x) - .ToArray() - ); - } + //public static string ToCamelCase(this string str) + //{ + // return new string( + // new CultureInfo("en-US", false) + // .TextInfo + // .ToTitleCase(string.Join(" ", pattern.Matches(str)).ToLower()) + // .Replace(@" ", "") + // .Select((x, i) => i == 0 ? char.ToLower(x) : x) + // .ToArray() + // ); + //} } diff --git a/Obsidian/Globals.cs b/Obsidian/Globals.cs index bc7f0acd6..02bdf8933 100644 --- a/Obsidian/Globals.cs +++ b/Obsidian/Globals.cs @@ -1,5 +1,4 @@ -using Obsidian.API.Utilities; -using Obsidian.Utilities.Converters; +using Obsidian.Utilities.Converters; using System.Net.Http; using System.Text.Encodings.Web; using System.Text.Json; @@ -35,13 +34,6 @@ public static class Globals }; } -public sealed class SnakeCaseNamingPolicy : JsonNamingPolicy -{ - public static SnakeCaseNamingPolicy Instance { get; } = new SnakeCaseNamingPolicy(); - - public override string ConvertName(string name) => name.ToSnakeCase(); -} - file class GuidJsonConverter : JsonConverter { public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => From cd68c5e3ad6429ee44de684b7503470f4021b542 Mon Sep 17 00:00:00 2001 From: Jon Pro Date: Sat, 18 Nov 2023 11:41:19 -0600 Subject: [PATCH 19/28] world gen status --- Obsidian/WorldData/World.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 7301ab31f..51b455459 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -8,6 +8,7 @@ using Obsidian.Net.Packets.Play.Clientbound; using Obsidian.Registries; using Obsidian.Services; +using System.Diagnostics; using System.IO; using System.Threading; @@ -857,12 +858,20 @@ await Parallel.ForEachAsync(Enumerable.Range(-regionPregenRange, regionPregenRan } } + float startChunks = ChunksToGenCount; + var stopwatch = new Stopwatch(); + stopwatch.Start(); + Logger.LogInformation("{startChunks} chunks to generate...", startChunks); while (!ChunksToGen.IsEmpty) { await ManageChunksAsync(); - //Server.UpdateStatusConsole(); + var pctComplete = (int)((1.0 - ChunksToGenCount / startChunks) * 100); + var completedChunks = startChunks - ChunksToGenCount; + var cps = completedChunks / (stopwatch.ElapsedMilliseconds / 1000.0); + int remain = ChunksToGenCount / (int)cps; + Console.Write("\r{0} chunks/second - {1}% complete - {2} seconds remaining ", cps.ToString("###.00"), pctComplete, remain); } - + Console.WriteLine(); await FlushRegionsAsync(); if (setWorldSpawn) From 2908dc6e531b8592b2073676cef324c2ebe277d2 Mon Sep 17 00:00:00 2001 From: Jon Pro Date: Sat, 18 Nov 2023 17:11:48 -0600 Subject: [PATCH 20/28] Fix strange chunk unloading bug --- Obsidian/Entities/Player.cs | 10 ++++++++-- .../Serverbound/SetPlayerPositionAndRotationPacket.cs | 8 ++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index a1b409ee8..b9a6e1a5e 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -973,12 +973,18 @@ internal async Task UpdateChunksAsync(bool unloadAll = false, int distance Math.Abs(playerChunkZ - chunk2.Z) ? -1 : 1; }); + clientUnneededChunks.ForEach(c => client.LoadedChunks.TryRemove(c)); await Parallel.ForEachAsync(clientUnneededChunks, async (chunkLoc, _) => { - await client.UnloadChunkAsync(chunkLoc.X, chunkLoc.Z); - client.LoadedChunks.TryRemove(chunkLoc); + //await client.UnloadChunkAsync(chunkLoc.X, chunkLoc.Z); + if (!client.LoadedChunks.TryRemove(chunkLoc)) + { + Logger.LogWarning("Failed to unload client chunk {0}", chunkLoc); + } }); + client.SendPacket(new SetCenterChunkPacket(playerChunkX, playerChunkZ)); + await Parallel.ForEachAsync(clientNeededChunks, async (chunkLoc, _) => { var chunk = await world.GetChunkAsync(chunkLoc.X, chunkLoc.Z); diff --git a/Obsidian/Net/Packets/Play/Serverbound/SetPlayerPositionAndRotationPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/SetPlayerPositionAndRotationPacket.cs index 58d416546..b8a6329e6 100644 --- a/Obsidian/Net/Packets/Play/Serverbound/SetPlayerPositionAndRotationPacket.cs +++ b/Obsidian/Net/Packets/Play/Serverbound/SetPlayerPositionAndRotationPacket.cs @@ -1,4 +1,5 @@ using Obsidian.Entities; +using Obsidian.Net.Packets.Play.Clientbound; using Obsidian.Serialization.Attributes; namespace Obsidian.Net.Packets.Play.Serverbound; @@ -22,5 +23,12 @@ public partial class SetPlayerPositionAndRotationPacket : IServerboundPacket public async ValueTask HandleAsync(Server server, Player player) { await player.UpdateAsync(Position, Yaw, Pitch, OnGround); + if (player.Position.ToChunkCoord() != player.LastPosition.ToChunkCoord()) + { + (int cx, int cz) = player.Position.ToChunkCoord(); + player.client.SendPacket(new SetCenterChunkPacket(cx, cz)); + } + + player.LastPosition = player.Position; } } From a8695d6fc170d22915687c90dcd394192a58a482 Mon Sep 17 00:00:00 2001 From: Jon Pro Date: Sat, 18 Nov 2023 17:15:51 -0600 Subject: [PATCH 21/28] Forgot to save O_O --- Obsidian/Entities/Player.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index b9a6e1a5e..bf020c3ed 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -974,16 +974,6 @@ internal async Task UpdateChunksAsync(bool unloadAll = false, int distance }); clientUnneededChunks.ForEach(c => client.LoadedChunks.TryRemove(c)); - await Parallel.ForEachAsync(clientUnneededChunks, async (chunkLoc, _) => - { - //await client.UnloadChunkAsync(chunkLoc.X, chunkLoc.Z); - if (!client.LoadedChunks.TryRemove(chunkLoc)) - { - Logger.LogWarning("Failed to unload client chunk {0}", chunkLoc); - } - }); - - client.SendPacket(new SetCenterChunkPacket(playerChunkX, playerChunkZ)); await Parallel.ForEachAsync(clientNeededChunks, async (chunkLoc, _) => { From c7c7013783249cf81a61d4a3c449116e6c0ac940 Mon Sep 17 00:00:00 2001 From: Tides Date: Mon, 20 Nov 2023 03:01:26 -0500 Subject: [PATCH 22/28] Update OperatorList & IOperatorList --- Obsidian.API/_Interfaces/IOperatorList.cs | 6 +- Obsidian/Server.cs | 6 +- Obsidian/Utilities/OperatorList.cs | 106 ++++++++++------------ 3 files changed, 53 insertions(+), 65 deletions(-) diff --git a/Obsidian.API/_Interfaces/IOperatorList.cs b/Obsidian.API/_Interfaces/IOperatorList.cs index 049907779..816860490 100644 --- a/Obsidian.API/_Interfaces/IOperatorList.cs +++ b/Obsidian.API/_Interfaces/IOperatorList.cs @@ -4,12 +4,10 @@ namespace Obsidian.API; public interface IOperatorList { - public void AddOperator(IPlayer player); + public void AddOperator(IPlayer player, int level = 4, bool bypassesPlayerLimit = false); public bool CreateRequest(IPlayer player); - public bool ProcessRequest(IPlayer player, string? code); - public void AddOperator(string username); + public bool ProcessRequest(IPlayer player, string code); public void RemoveOperator(IPlayer player); - public void RemoveOperator(string username); public bool IsOperator(IPlayer p); public ImmutableList GetOnlineOperators(); } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 505dc2432..9519ebe3c 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -51,8 +51,8 @@ public static string VERSION #endif public const ProtocolVersion DefaultProtocol = ProtocolVersion.v1_20_2; - public static string PersistentDataPath { get; } = "persistentdata"; - public static string PermissionPath { get; } = "permissions"; + public const string PersistentDataPath = "persistentdata"; + public const string PermissionPath = "permissions"; internal static readonly ConcurrentDictionary throttler = new(); @@ -112,7 +112,7 @@ public Server( Port = Config.Port; - Operators = new OperatorList(this); + Operators = new OperatorList(this, loggerFactory); _logger.LogDebug(message: "Initializing command handler..."); CommandsHandler = new CommandHandler(); diff --git a/Obsidian/Utilities/OperatorList.cs b/Obsidian/Utilities/OperatorList.cs index 8b1d15952..95b5eb4b4 100644 --- a/Obsidian/Utilities/OperatorList.cs +++ b/Obsidian/Utilities/OperatorList.cs @@ -1,129 +1,119 @@ using Microsoft.Extensions.Logging; -using Obsidian.API.Logging; using System.Collections.Immutable; using System.IO; namespace Obsidian.Utilities; -public class OperatorList : IOperatorList +public sealed class OperatorList : IOperatorList { - private List ops; - private readonly List reqs; - private readonly Server server; + private readonly List operators = new(); + private readonly Dictionary requests = new(); + private readonly IServer server; private readonly ILogger _logger; - private string Path => System.IO.Path.Combine(System.IO.Path.Combine("config"), "ops.json"); + private string OpsFilePath => Path.Combine("config", "ops.json"); - public OperatorList(Server server) + public OperatorList(IServer server, ILoggerFactory loggerFactory) { - this.ops = new List(); - this.reqs = new List(); this.server = server; - var loggerProvider = new LoggerProvider(); - _logger = loggerProvider.CreateLogger("OperatorList"); + _logger = loggerFactory.CreateLogger(); } public async Task InitializeAsync() { - var fi = new FileInfo(this.Path); + var fi = new FileInfo(this.OpsFilePath); if (fi.Exists) { await using var fs = fi.OpenRead(); - this.ops = await fs.FromJsonAsync>(); + var ops = await fs.FromJsonAsync>(); + + this.operators.AddRange(ops!); } else { await using var fs = fi.Create(); - await this.ops.ToJsonAsync(fs); + await this.operators.ToJsonAsync(fs); } } - public void AddOperator(IPlayer p) + public bool CreateRequest(IPlayer player) { - ops.Add(new Operator { Username = p.Username, Uuid = p.Uuid }); - - UpdateList(); - } + if (!server.Configuration.AllowOperatorRequests) + return false; - public bool CreateRequest(IPlayer p) - { - if (!server.Config.AllowOperatorRequests) + if (this.requests.Values.Any(x => x.Player == player)) { + _logger.LogWarning("{Username} tried to put in another request but already has one pending.", player.Username); return false; } - var result = reqs.All(r => r.Player != p); + var request = new OperatorRequest(player); + requests.Add(request.Code, request); - if (result) - { - var req = new OperatorRequest(p); - reqs.Add(req); - - _logger.LogWarning($"New operator request from {p.Username}: {req.Code}"); - } + _logger.LogInformation("New operator request from {Username}: {Code}", player.Username, request.Code); - return result; + return true; } - public bool ProcessRequest(IPlayer p, string code) + public bool ProcessRequest(IPlayer player, string code) { - var result = reqs.FirstOrDefault(r => r.Player == p && r.Code == code); + if (!this.requests.TryGetValue(code, out var request)) + return false; - if (result == null) + if (!requests.Remove(request.Code)) { + _logger.LogWarning("Failed to process request with code: {code}", code); return false; } - reqs.Remove(result); - - AddOperator(p); + AddOperator(player); return true; } - public void AddOperator(string username) + public void AddOperator(IPlayer player, int level = 4, bool bypassesPlayerLimit = false) { - this.ops.Add(new Operator { Username = username, Uuid = Guid.Empty }); + this.operators.Add(new Operator { Username = player.Username, Uuid = player.Uuid, Level = level, BypassesPlayerLimit = bypassesPlayerLimit }); this.UpdateList(); } - public void RemoveOperator(IPlayer p) + public void RemoveOperator(IPlayer player) { - this.ops.RemoveAll(x => x.Uuid == p.Uuid || x.Username == p.Username); - this.UpdateList(); - } + this.operators.RemoveAll(x => x.Uuid == player.Uuid); - public void RemoveOperator(string value) - { - this.ops.RemoveAll(x => x.Username == value || x.Uuid == Guid.Parse(value)); this.UpdateList(); } - public bool IsOperator(IPlayer p) => this.ops.Any(x => x.Username == p.Username || p.Uuid == x.Uuid); + public bool IsOperator(IPlayer player) => this.operators.Any(x => x.Uuid == player.Uuid); public ImmutableList GetOnlineOperators() => server.Players.Where(IsOperator).ToImmutableList(); - private void UpdateList() - { - File.WriteAllText(Path, ops.ToJson()); - } + private void UpdateList() => + File.WriteAllText(OpsFilePath, operators.ToJson()); - private class Operator + private readonly struct Operator { - public string Username { get; set; } + public required string Username { get; init; } + + public required Guid Uuid { get; init; } + + public required int Level { get; init; } - public Guid Uuid { get; set; } + public required bool BypassesPlayerLimit { get; init; } } - private class OperatorRequest + private readonly struct OperatorRequest { - public IPlayer Player; - public string Code; + public IPlayer Player { get; } + public string Code { get; } + public OperatorRequest(IPlayer player) { - Player = player ?? throw new ArgumentNullException(nameof(player)); + ArgumentNullException.ThrowIfNull(player); + + Player = player; static string GetCode() { From 5c112add9fd48110f720c9a20466d72bd02597af Mon Sep 17 00:00:00 2001 From: Tides Date: Mon, 20 Nov 2023 03:48:33 -0500 Subject: [PATCH 23/28] Last bit refactor --- Obsidian.ConsoleApp/Program.cs | 1 - Obsidian/Entities/Entity.cs | 12 ++++++------ Obsidian/ScoreboardManager.cs | 11 +++++------ Obsidian/Server.cs | 2 +- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Obsidian.ConsoleApp/Program.cs b/Obsidian.ConsoleApp/Program.cs index c73fa285f..b4d38b119 100644 --- a/Obsidian.ConsoleApp/Program.cs +++ b/Obsidian.ConsoleApp/Program.cs @@ -36,7 +36,6 @@ builder.Services.AddObsidian(env); // Give the server some time to shut down after CTRL-C or SIGTERM. -//TODO SERVICES SET STOP CONCURRENTLY builder.Services.Configure(opts => { opts.ShutdownTimeout = TimeSpan.FromSeconds(10); diff --git a/Obsidian/Entities/Entity.cs b/Obsidian/Entities/Entity.cs index 2ea853fca..3770dbf2d 100644 --- a/Obsidian/Entities/Entity.cs +++ b/Obsidian/Entities/Entity.cs @@ -76,7 +76,7 @@ internal virtual async Task UpdateAsync(VectorF position, bool onGround) { var delta = (Vector)((position * 32 - Position * 32) * 128); - this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionPacket + this.PacketBroadcaster.BroadcastToWorld(this.World, new UpdateEntityPositionPacket { EntityId = EntityId, @@ -100,7 +100,7 @@ internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch if (isNewRotation) { - this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionAndRotationPacket + this.PacketBroadcaster.BroadcastToWorld(this.World, new UpdateEntityPositionAndRotationPacket { EntityId = EntityId, @@ -112,7 +112,7 @@ internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch OnGround = onGround }, EntityId); - this.PacketBroadcaster.QueuePacketToWorld(this.World, new SetHeadRotationPacket + this.PacketBroadcaster.BroadcastToWorld(this.World, new SetHeadRotationPacket { EntityId = EntityId, HeadYaw = yaw @@ -120,7 +120,7 @@ internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch } else { - this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionPacket + this.PacketBroadcaster.BroadcastToWorld(this.World, new UpdateEntityPositionPacket { EntityId = EntityId, @@ -140,7 +140,7 @@ internal virtual Task UpdateAsync(Angle yaw, Angle pitch, bool onGround) if (isNewRotation) { - this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityRotationPacket + this.PacketBroadcaster.BroadcastToWorld(this.World, new UpdateEntityRotationPacket { EntityId = EntityId, OnGround = onGround, @@ -148,7 +148,7 @@ internal virtual Task UpdateAsync(Angle yaw, Angle pitch, bool onGround) Pitch = pitch }, EntityId); - this.PacketBroadcaster.QueuePacketToWorld(this.World, new SetHeadRotationPacket + this.PacketBroadcaster.BroadcastToWorld(this.World, new SetHeadRotationPacket { EntityId = EntityId, HeadYaw = yaw diff --git a/Obsidian/ScoreboardManager.cs b/Obsidian/ScoreboardManager.cs index 04bc4cf0f..915a10747 100644 --- a/Obsidian/ScoreboardManager.cs +++ b/Obsidian/ScoreboardManager.cs @@ -1,29 +1,28 @@ using Microsoft.Extensions.Logging; -using Obsidian.API.Logging; namespace Obsidian; public sealed class ScoreboardManager : IScoreboardManager { private readonly Server server; + private readonly ILogger logger; - internal readonly HashSet scoreboards = new HashSet(); + internal readonly HashSet scoreboards = new(); public IScoreboard DefaultScoreboard { get; } - public ScoreboardManager(Server server) + public ScoreboardManager(Server server, ILoggerFactory loggerFactory) { this.server = server; + this.logger = loggerFactory.CreateLogger(); this.DefaultScoreboard = this.CreateScoreboard("default"); } public IScoreboard CreateScoreboard(string name) { - var loggerProvider = new LoggerProvider(LogLevel.Warning); - var logger = loggerProvider.CreateLogger("ScoreboardManager"); if (!this.scoreboards.Add(name)) - logger.LogWarning($"Scoreboard with the name: {name} already exists. This might cause some issues and override already existing scoreboards displaying on clients."); + this.logger.LogWarning("Scoreboard with the name: {name} already exists. This might cause some issues and override already existing scoreboards displaying on clients.", name); return new Scoreboard(name, this.server); } diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 9519ebe3c..447d942a5 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -113,6 +113,7 @@ public Server( Port = Config.Port; Operators = new OperatorList(this, loggerFactory); + ScoreboardManager = new ScoreboardManager(this, loggerFactory); _logger.LogDebug(message: "Initializing command handler..."); CommandsHandler = new CommandHandler(); @@ -254,7 +255,6 @@ public async Task RunAsync() await (Operators as OperatorList).InitializeAsync(); - ScoreboardManager = new ScoreboardManager(this); _logger.LogInformation("Loading plugins..."); Directory.CreateDirectory("plugins"); From 4ab15e628067743a186c4e1291eb4dcb9c2a74f3 Mon Sep 17 00:00:00 2001 From: Tides Date: Mon, 20 Nov 2023 10:04:52 -0500 Subject: [PATCH 24/28] Make user cache a service instead --- .../{IConfig.cs => IServerConfiguration.cs} | 9 ++- Obsidian.API/_Types/Config/RconConfig.cs | 2 +- Obsidian.API/_Types/WhitelistedPlayer.cs | 6 ++ Obsidian/Client.cs | 50 ++++++++-------- Obsidian/Hosting/DependencyInjection.cs | 3 + Obsidian/Net/ClientHandler.cs | 4 +- Obsidian/Server.cs | 18 +++--- .../UserCache.cs => Services/IUserCache.cs} | 60 +++++++++++-------- Obsidian/Utilities/Config.cs | 4 +- Obsidian/Utilities/WhitelistedPlayer.cs | 7 --- 10 files changed, 94 insertions(+), 69 deletions(-) rename Obsidian.API/_Interfaces/{IConfig.cs => IServerConfiguration.cs} (90%) create mode 100644 Obsidian.API/_Types/WhitelistedPlayer.cs rename Obsidian/{Utilities/UserCache.cs => Services/IUserCache.cs} (60%) delete mode 100644 Obsidian/Utilities/WhitelistedPlayer.cs diff --git a/Obsidian.API/_Interfaces/IConfig.cs b/Obsidian.API/_Interfaces/IServerConfiguration.cs similarity index 90% rename from Obsidian.API/_Interfaces/IConfig.cs rename to Obsidian.API/_Interfaces/IServerConfiguration.cs index 0373c0b52..ebe0555e3 100644 --- a/Obsidian.API/_Interfaces/IConfig.cs +++ b/Obsidian.API/_Interfaces/IServerConfiguration.cs @@ -1,4 +1,4 @@ -using Obsidian.API._Types.Config; +using Obsidian.API.Config; namespace Obsidian.API; @@ -38,6 +38,8 @@ public interface IServerConfiguration public bool AllowOperatorRequests { get; set; } + public bool WhitelistEnabled { get; set; } + /// /// Whether each login/client gets a random username where multiple connections from the same host will be allowed. /// @@ -75,8 +77,13 @@ public interface IServerConfiguration /// See more at https://wiki.vg/RCON public bool EnableRcon => Rcon is not null; + public bool VerboseExceptionLogging { get; set; } + /// /// Remote Console configuration /// public RconConfig? Rcon { get; set; } + + public List WhitelistedIPs { get; set; } + public List Whitelisted { get; set; } } diff --git a/Obsidian.API/_Types/Config/RconConfig.cs b/Obsidian.API/_Types/Config/RconConfig.cs index f3e1ec7e1..b03ad9d39 100644 --- a/Obsidian.API/_Types/Config/RconConfig.cs +++ b/Obsidian.API/_Types/Config/RconConfig.cs @@ -1,4 +1,4 @@ -namespace Obsidian.API._Types.Config; +namespace Obsidian.API.Config; public class RconConfig { diff --git a/Obsidian.API/_Types/WhitelistedPlayer.cs b/Obsidian.API/_Types/WhitelistedPlayer.cs new file mode 100644 index 000000000..4de04cc88 --- /dev/null +++ b/Obsidian.API/_Types/WhitelistedPlayer.cs @@ -0,0 +1,6 @@ +namespace Obsidian.API; +public readonly struct WhitelistedPlayer +{ + public required string Name { get; init; } + public required Guid Id { get; init; } +} diff --git a/Obsidian/Client.cs b/Obsidian/Client.cs index cb3b22abd..5f4eba256 100644 --- a/Obsidian/Client.cs +++ b/Obsidian/Client.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; using Obsidian.API.Events; -using Obsidian.API.Logging; using Obsidian.API.Utilities; using Obsidian.Concurrency; using Obsidian.Entities; @@ -17,6 +16,7 @@ using Obsidian.Net.Packets.Play.Clientbound; using Obsidian.Net.Packets.Status; using Obsidian.Registries; +using Obsidian.Services; using Obsidian.Utilities.Mojang; using Obsidian.WorldData; using System.Diagnostics; @@ -59,6 +59,14 @@ public sealed class Client : IDisposable /// internal MessageSigningData? messageSigningData; + /// + /// The current server configuration. + /// + private readonly IServerConfiguration configuration; + + + private readonly IUserCache userCache; + /// /// Whether the client has compression enabled on the Minecraft stream. /// @@ -119,11 +127,6 @@ public sealed class Client : IDisposable /// private readonly ConnectionContext connectionContext; - /// - /// The current server configuration. - /// - private readonly ServerConfiguration config; - /// /// Whether the stream has encryption enabled. This can be set to false when the client is connecting through LAN or when the server is in offline mode. /// @@ -169,22 +172,23 @@ public sealed class Client : IDisposable /// public string? Brand { get; set; } - public Client(ConnectionContext connectionContext, ServerConfiguration config, int playerId, Server originServer) + public Client(ConnectionContext connectionContext, int playerId, + IServerConfiguration configuration, ILoggerFactory loggerFactory, IUserCache playerCache) { this.connectionContext = connectionContext; - this.config = config; - var loggerProvider = new LoggerProvider(config.LogLevel); - Logger = loggerProvider.CreateLogger("Client"); + this.configuration = configuration; id = playerId; - Server = originServer; - LoadedChunks = []; packetCryptography = new(); - handler = new(config); + handler = new(configuration); networkStream = new(connectionContext.Transport); minecraftStream = new(networkStream); + this.configuration = configuration; + this.userCache = playerCache; + this.Logger = loggerFactory.CreateLogger($"Client{playerId}"); + missedKeepAlives = []; var linkOptions = new DataflowLinkOptions { PropagateCompletion = true }; var blockOptions = new ExecutionDataflowBlockOptions { CancellationToken = cancellationSource.Token, EnsureOrdered = true }; @@ -241,8 +245,6 @@ public async Task StartConnectionAsync() if (State == ClientState.Play && data.Length < 1) Disconnect(); - - switch (State) { case ClientState.Status: // Server ping/list @@ -415,22 +417,22 @@ private async Task HandleHandshakeAsync(byte[] data) private async Task HandleLoginStartAsync(byte[] data) { var loginStart = LoginStart.Deserialize(data); - var username = config.MulitplayerDebugMode ? $"Player{Globals.Random.Next(1, 999)}" : loginStart.Username; + var username = configuration.MulitplayerDebugMode ? $"Player{Globals.Random.Next(1, 999)}" : loginStart.Username; var world = (World)Server.DefaultWorld; Logger.LogDebug("Received login request from user {Username}", loginStart.Username); await Server.DisconnectIfConnectedAsync(username); - if (config.OnlineMode) + if (configuration.OnlineMode) { - cachedUser = await UserCache.GetUserFromNameAsync(loginStart.Username ?? throw new NullReferenceException(nameof(loginStart.PlayerUuid))); + cachedUser = await this.userCache.GetCachedUserFromNameAsync(loginStart.Username ?? throw new NullReferenceException(nameof(loginStart.PlayerUuid))); if (cachedUser is null) { await DisconnectAsync("Account not found in the Mojang database"); return; } - else if (config.WhitelistEnabled && !config.Whitelisted.Any(x => x.Id == cachedUser.Id)) + else if (configuration.WhitelistEnabled && !configuration.Whitelisted.Any(x => x.Id == cachedUser.Id)) { await DisconnectAsync("You are not whitelisted on this server\nContact server administrator"); return; @@ -449,7 +451,7 @@ private async Task HandleLoginStartAsync(byte[] data) VerifyToken = randomToken }); } - else if (config.WhitelistEnabled && !config.Whitelisted.Any(x => x.Name == username)) + else if (configuration.WhitelistEnabled && !configuration.Whitelisted.Any(x => x.Name == username)) { await DisconnectAsync("You are not whitelisted on this server\nContact server administrator"); } @@ -489,7 +491,7 @@ private async Task HandleEncryptionResponseAsync(byte[] data) } var serverId = sharedKey.Concat(packetCryptography.PublicKey).MinecraftShaDigest(); - if (await UserCache.HasJoinedAsync(Player.Username, serverId) is not MojangUser user) + if (await this.userCache.HasJoinedAsync(Player.Username, serverId) is not MojangUser user) { Logger.LogWarning("Failed to auth {Username}", Player.Username); await DisconnectAsync("Unable to authenticate..."); @@ -622,7 +624,7 @@ internal void SendKeepAlive(DateTimeOffset time) { long keepAliveId = time.ToUnixTimeMilliseconds(); // first, check if there's any KeepAlives that are older than 30 seconds - if (missedKeepAlives.Any(x => keepAliveId - x > config.KeepAliveTimeoutInterval)) + if (missedKeepAlives.Any(x => keepAliveId - x > this.configuration.KeepAliveTimeoutInterval)) { // kick player, failed to respond within 30s cancellationSource.Cancel(); @@ -663,7 +665,7 @@ internal async Task AddPlayerToListAsync(IPlayer player) Name = player.Username, }; - if (config.OnlineMode) + if (this.configuration.OnlineMode) addAction.Properties.AddRange(player.SkinProperties); var list = new List() @@ -694,7 +696,7 @@ internal async Task SendPlayerInfoAsync() Name = player.Username, }; - if (config.OnlineMode) + if (this.configuration.OnlineMode) addPlayerInforAction.Properties.AddRange(player.SkinProperties); var list = new List diff --git a/Obsidian/Hosting/DependencyInjection.cs b/Obsidian/Hosting/DependencyInjection.cs index 570f9382b..23769e88b 100644 --- a/Obsidian/Hosting/DependencyInjection.cs +++ b/Obsidian/Hosting/DependencyInjection.cs @@ -18,6 +18,9 @@ public static IServiceCollection AddObsidian(this IServiceCollection services, I services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + + services.AddHttpClient(); services.AddHostedService(sp => sp.GetRequiredService()); services.AddHostedService(); diff --git a/Obsidian/Net/ClientHandler.cs b/Obsidian/Net/ClientHandler.cs index 9c86d7579..5031ecb14 100644 --- a/Obsidian/Net/ClientHandler.cs +++ b/Obsidian/Net/ClientHandler.cs @@ -12,10 +12,10 @@ namespace Obsidian.Net; public class ClientHandler { private ConcurrentDictionary Packets { get; } = new ConcurrentDictionary(); - private ServerConfiguration config; + private IServerConfiguration config; private readonly ILogger _logger; - public ClientHandler(ServerConfiguration config) + public ClientHandler(IServerConfiguration config) { this.config = config; var loggerProvider = new LoggerProvider(LogLevel.Error); diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index 447d942a5..a432d7275 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -21,6 +21,7 @@ using Obsidian.Net.Rcon; using Obsidian.Plugins; using Obsidian.Registries; +using Obsidian.Services; using Obsidian.WorldData; using System.Diagnostics; using System.IO; @@ -60,7 +61,9 @@ public static string VERSION private readonly ConcurrentQueue _chatMessagesQueue = new(); private readonly ConcurrentHashSet _clients = new(); + private readonly ILoggerFactory loggerFactory; private readonly RconServer _rconServer; + private readonly IUserCache userCache; private readonly ILogger _logger; private IConnectionListener? _tcpListener; @@ -86,8 +89,6 @@ public static string VERSION public IServerConfiguration Configuration => Config; public string Version => VERSION; - - public string Brand { get; } = "obsidian"; public int Port { get; } public IWorld DefaultWorld => WorldManager.DefaultWorld; @@ -101,7 +102,8 @@ public Server( IServerEnvironment environment, ILoggerFactory loggerFactory, IWorldManager worldManager, - RconServer rconServer) + RconServer rconServer, + IUserCache playerCache) { Config = environment.Configuration; _logger = loggerFactory.CreateLogger(); @@ -131,6 +133,8 @@ public Server( _logger.LogDebug("Registering command context type..."); _logger.LogDebug("Done registering commands."); + this.userCache = playerCache; + this.loggerFactory = loggerFactory; this.WorldManager = worldManager; Events.PlayerLeave += OnPlayerLeave; @@ -249,7 +253,7 @@ public async Task RunAsync() await RecipesRegistry.InitializeAsync(); - await UserCache.LoadAsync(this._cancelTokenSource.Token); + await this.userCache.LoadAsync(this._cancelTokenSource.Token); _logger.LogInformation($"Loading properties..."); @@ -311,7 +315,7 @@ private async Task HandleServerShutdown() await WorldManager.FlushLoadedWorldsAsync(); await WorldManager.DisposeAsync(); - await UserCache.SaveAsync(); + await this.userCache.SaveAsync(); } private async Task AcceptClientsAsync() @@ -364,7 +368,7 @@ private async Task AcceptClientsAsync() } // TODO Entity ids need to be unique on the entire server, not per world - var client = new Client(connection, Config, Math.Max(0, _clients.Count + this.DefaultWorld.GetTotalLoadedEntities()), this); + var client = new Client(connection, Math.Max(0, _clients.Count + this.DefaultWorld.GetTotalLoadedEntities()), this.Configuration, this.loggerFactory, this.userCache); _clients.Add(client); @@ -485,7 +489,7 @@ private async Task ServerSaveAsync() { _logger.LogInformation("Saving world..."); await WorldManager.FlushLoadedWorldsAsync(); - await UserCache.SaveAsync(); + await this.userCache.SaveAsync(); } } catch { } diff --git a/Obsidian/Utilities/UserCache.cs b/Obsidian/Services/IUserCache.cs similarity index 60% rename from Obsidian/Utilities/UserCache.cs rename to Obsidian/Services/IUserCache.cs index b7f8648c5..700285c45 100644 --- a/Obsidian/Utilities/UserCache.cs +++ b/Obsidian/Services/IUserCache.cs @@ -1,4 +1,4 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Obsidian.Entities; using Obsidian.Utilities.Mojang; using System.Diagnostics; using System.IO; @@ -9,28 +9,27 @@ using System.Threading; using System.Web; -namespace Obsidian.Utilities; - -public static class UserCache +namespace Obsidian.Services; +public sealed class UserCache(HttpClient httpClient) : IUserCache { private const string userWithNameEndpoint = "https://api.mojang.com/users/profiles/minecraft/"; private const string userWithIdEndpoint = "https://sessionserver.mojang.com/session/minecraft/profile/"; private const string verifySessionEndpoint = "https://sessionserver.mojang.com/session/minecraft/hasJoined"; - private static readonly HttpClient httpClient = Globals.HttpClient; + private readonly FileInfo cacheFile = new("usercache.json"); - private static ConcurrentDictionary cache = new(); + private ConcurrentDictionary cachedUsers; - private static readonly FileInfo cacheFile = new("usercache.json"); + public ConcurrentDictionary OnlinePlayers { get; } = new (); - public static async Task GetUserFromNameAsync(string username) + public async Task GetCachedUserFromNameAsync(string username) { var escapedUsername = Sanitize(username); CachedUser? cachedUser; - if (cache.Any(x => x.Value.Name == username)) + if (cachedUsers.Any(x => x.Value.Name == username)) { - cachedUser = cache.First(x => x.Value.Name == username).Value; + cachedUser = cachedUsers.First(x => x.Value.Name == username).Value; if (!cachedUser.Expired) return cachedUser; @@ -41,7 +40,7 @@ public static class UserCache if (user is null) return null; - if (cache.TryGetValue(user.Id, out cachedUser)) + if (cachedUsers.TryGetValue(user.Id, out cachedUser)) { if (cachedUser.Expired) { @@ -59,23 +58,19 @@ public static class UserCache ExpiresOn = DateTimeOffset.UtcNow.AddMonths(1) }; - cache.TryAdd(cachedUser.Id, cachedUser); + cachedUsers.TryAdd(cachedUser.Id, cachedUser); return cachedUser; } - public static async Task GetUserFromUuidAsync(Guid uuid) + public async Task GetCachedUserFromUuidAsync(Guid uuid) { - if (cache.TryGetValue(uuid, out var user) && !user.Expired) + if (cachedUsers.TryGetValue(uuid, out var user) && !user.Expired) return user; var escapedUuid = Sanitize(uuid.ToString("N")); - var mojangUser = await httpClient.GetFromJsonAsync($"{userWithIdEndpoint}{escapedUuid}", Globals.JsonOptions); - - if (mojangUser is null) - throw new UnreachableException();//This isn't supposed to happen - + var mojangUser = await httpClient.GetFromJsonAsync($"{userWithIdEndpoint}{escapedUuid}", Globals.JsonOptions) ?? throw new UnreachableException(); user = new() { Name = mojangUser!.Name, @@ -83,12 +78,12 @@ public static async Task GetUserFromUuidAsync(Guid uuid) ExpiresOn = DateTimeOffset.UtcNow.AddMonths(1) }; - cache.TryAdd(uuid, user); + cachedUsers.TryAdd(uuid, user); return user; } - public static async Task HasJoinedAsync(string username, string serverId) + public async Task HasJoinedAsync(string username, string serverId) { var escapedUsername = Sanitize(username); var escapedServerId = Sanitize(serverId); @@ -96,16 +91,16 @@ public static async Task GetUserFromUuidAsync(Guid uuid) return await httpClient.GetFromJsonAsync($"{verifySessionEndpoint}?username={escapedUsername}&serverId={escapedServerId}", Globals.JsonOptions); } - public static async Task SaveAsync() + public async Task SaveAsync(CancellationToken cancellationToken = default) { await using var sw = cacheFile.Open(FileMode.Truncate, FileAccess.Write); - await JsonSerializer.SerializeAsync(sw, cache, Globals.JsonOptions); + await JsonSerializer.SerializeAsync(sw, cachedUsers, Globals.JsonOptions); await sw.FlushAsync(); } - public static async Task LoadAsync(CancellationToken cancellationToken = default) + public async Task LoadAsync(CancellationToken cancellationToken = default) { await using var sr = cacheFile.Open(FileMode.OpenOrCreate, FileAccess.Read); @@ -117,8 +112,23 @@ public static async Task LoadAsync(CancellationToken cancellationToken = default if (userCache is null) return; - cache = new(userCache); + cachedUsers = new(userCache); } private static string Sanitize(string value) => HttpUtility.UrlEncode(Encoding.UTF8.GetBytes(value)); } + +public interface IUserCache +{ + public ConcurrentDictionary OnlinePlayers { get; } + + public Task GetCachedUserFromNameAsync(string username); + + public Task GetCachedUserFromUuidAsync(Guid uuid); + + public Task HasJoinedAsync(string username, string serverId); + + public Task SaveAsync(CancellationToken cancellationToken = default); + + public Task LoadAsync(CancellationToken cancellationToken = default); +} diff --git a/Obsidian/Utilities/Config.cs b/Obsidian/Utilities/Config.cs index 99180004b..6e5adafec 100644 --- a/Obsidian/Utilities/Config.cs +++ b/Obsidian/Utilities/Config.cs @@ -1,10 +1,10 @@ using Microsoft.Extensions.Logging; -using Obsidian.API._Types.Config; +using Obsidian.API.Config; using System.Text.Json.Serialization; namespace Obsidian.Utilities; -public class ServerConfiguration : IServerConfiguration +public sealed class ServerConfiguration : IServerConfiguration { public string Motd { get; set; } = $"§k||||§r §5Obsidian §cPre§r-§cRelease §r§k||||§r \n§r§lRunning on .NET §l§c{Environment.Version} §r§l<3"; diff --git a/Obsidian/Utilities/WhitelistedPlayer.cs b/Obsidian/Utilities/WhitelistedPlayer.cs deleted file mode 100644 index f674242eb..000000000 --- a/Obsidian/Utilities/WhitelistedPlayer.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Obsidian.Utilities; - -public sealed class WhitelistedPlayer -{ - public required string Name { get; set; } - public required Guid Id { get; set; } -} From f26c8ced266e58648eb21b9546979631a23cca7c Mon Sep 17 00:00:00 2001 From: Tides Date: Mon, 20 Nov 2023 10:16:46 -0500 Subject: [PATCH 25/28] Add sealed modifier to most classes (final commit) --- Obsidian/Blocks/BlockMetaBuilder.cs | 2 +- Obsidian/Events/MinecraftEventHandler.cs | 2 +- Obsidian/Net/Actions/BossBar/BossBarAction.cs | 4 +- .../Net/Actions/BossBar/BossBarAddAction.cs | 2 +- .../Actions/BossBar/BossBarRemoveAction.cs | 2 +- .../BossBar/BossBarUpdateFlagsAction.cs | 2 +- .../BossBar/BossBarUpdateHealthAction.cs | 2 +- .../BossBar/BossBarUpdateStyleAction.cs | 2 +- .../BossBar/BossBarUpdateTitleAction.cs | 2 +- Obsidian/Net/ClientHandler.cs | 2 +- Obsidian/Net/MixedCodec.cs | 1 + Obsidian/Net/PacketCryptography.cs | 2 +- Obsidian/Net/Rcon/RconCommandSender.cs | 2 +- Obsidian/Net/Rcon/RconConnection.cs | 2 +- Obsidian/Net/Rcon/RconPacket.cs | 2 +- .../WindowProperties/AnvilWindowProperty.cs | 2 +- .../WindowProperties/BeaconWindowProperty.cs | 2 +- .../BrewingStandWindowProperty.cs | 2 +- .../EnchantmentTableWindowProperty.cs | 2 +- .../WindowProperties/FurnaceWindowProperty.cs | 2 +- .../WindowProperties/LecternWindowProperty.cs | 2 +- .../WindowProperties/LoomWindowProperty.cs | 2 +- .../StonecutterWindowProperty.cs | 2 +- Obsidian/Registries/BlocksRegistry.cs | 2 +- Obsidian/Registries/CodecRegistry.cs | 2 +- Obsidian/Registries/ItemsRegistry.cs | 2 +- Obsidian/Server.cs | 2 +- Obsidian/Team.cs | 2 +- .../Utilities/Collections/DenseCollection.cs | 2 +- Obsidian/Utilities/Collections/NibbleArray.cs | 79 ------------------- .../Converters/DefaultEnumConverter.cs | 4 +- .../Utilities/Converters/HexColorConverter.cs | 2 +- .../Converters/StringToBoolConverter.cs | 4 +- Obsidian/Utilities/Extensions.cs | 3 - .../{Config.cs => ServerConfiguration.cs} | 0 Obsidian/Utilities/ServerStatus.cs | 2 +- Obsidian/WorldData/World.cs | 2 +- 37 files changed, 37 insertions(+), 118 deletions(-) delete mode 100644 Obsidian/Utilities/Collections/NibbleArray.cs rename Obsidian/Utilities/{Config.cs => ServerConfiguration.cs} (100%) diff --git a/Obsidian/Blocks/BlockMetaBuilder.cs b/Obsidian/Blocks/BlockMetaBuilder.cs index a4d79c862..5a973f8b6 100644 --- a/Obsidian/Blocks/BlockMetaBuilder.cs +++ b/Obsidian/Blocks/BlockMetaBuilder.cs @@ -3,7 +3,7 @@ namespace Obsidian.Blocks; -public class BlockMetaBuilder +public sealed class BlockMetaBuilder { public ChatMessage? Name { get; internal set; } diff --git a/Obsidian/Events/MinecraftEventHandler.cs b/Obsidian/Events/MinecraftEventHandler.cs index 1b4526da3..15fb452f7 100644 --- a/Obsidian/Events/MinecraftEventHandler.cs +++ b/Obsidian/Events/MinecraftEventHandler.cs @@ -3,7 +3,7 @@ namespace Obsidian.Events; -public class MinecraftEventHandler +public sealed class MinecraftEventHandler { public AsyncEvent PacketReceived = new(nameof(PacketReceived), HandleException); public AsyncEvent QueuePacket = new(nameof(QueuePacket), HandleException); diff --git a/Obsidian/Net/Actions/BossBar/BossBarAction.cs b/Obsidian/Net/Actions/BossBar/BossBarAction.cs index 38d18a479..68599d79e 100644 --- a/Obsidian/Net/Actions/BossBar/BossBarAction.cs +++ b/Obsidian/Net/Actions/BossBar/BossBarAction.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.Actions.BossBar; -public class BossBarAction +public abstract class BossBarAction { public Guid Uuid { get; set; } @@ -20,7 +20,7 @@ public virtual void WriteTo(MinecraftStream stream) stream.WriteVarInt(this.Action); } - public virtual async Task WriteToAsync(MinecraftStream stream) + public async virtual Task WriteToAsync(MinecraftStream stream) { if (this.Uuid == default) throw new InvalidOperationException("Uuid must be assigned a value."); diff --git a/Obsidian/Net/Actions/BossBar/BossBarAddAction.cs b/Obsidian/Net/Actions/BossBar/BossBarAddAction.cs index 8b614098a..fc59a181b 100644 --- a/Obsidian/Net/Actions/BossBar/BossBarAddAction.cs +++ b/Obsidian/Net/Actions/BossBar/BossBarAddAction.cs @@ -2,7 +2,7 @@ namespace Obsidian.Net.Actions.BossBar; -public class BossBarAddAction : BossBarAction +public sealed class BossBarAddAction : BossBarAction { public ChatMessage Title { get; set; } diff --git a/Obsidian/Net/Actions/BossBar/BossBarRemoveAction.cs b/Obsidian/Net/Actions/BossBar/BossBarRemoveAction.cs index 75f7f235c..f5c3f44bb 100644 --- a/Obsidian/Net/Actions/BossBar/BossBarRemoveAction.cs +++ b/Obsidian/Net/Actions/BossBar/BossBarRemoveAction.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.Actions.BossBar; -public class BossBarRemoveAction : BossBarAction +public sealed class BossBarRemoveAction : BossBarAction { public BossBarRemoveAction() : base(1) { } } diff --git a/Obsidian/Net/Actions/BossBar/BossBarUpdateFlagsAction.cs b/Obsidian/Net/Actions/BossBar/BossBarUpdateFlagsAction.cs index fa14d154d..3f9e21ade 100644 --- a/Obsidian/Net/Actions/BossBar/BossBarUpdateFlagsAction.cs +++ b/Obsidian/Net/Actions/BossBar/BossBarUpdateFlagsAction.cs @@ -2,7 +2,7 @@ namespace Obsidian.Net.Actions.BossBar; -public class BossBarUpdateFlagsAction : BossBarAction +public sealed class BossBarUpdateFlagsAction : BossBarAction { public BossBarFlags Flags { get; set; } diff --git a/Obsidian/Net/Actions/BossBar/BossBarUpdateHealthAction.cs b/Obsidian/Net/Actions/BossBar/BossBarUpdateHealthAction.cs index c687d58cf..275f40eaa 100644 --- a/Obsidian/Net/Actions/BossBar/BossBarUpdateHealthAction.cs +++ b/Obsidian/Net/Actions/BossBar/BossBarUpdateHealthAction.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.Actions.BossBar; -public class BossBarUpdateHealthAction : BossBarAction +public sealed class BossBarUpdateHealthAction : BossBarAction { public float Health { get; set; } diff --git a/Obsidian/Net/Actions/BossBar/BossBarUpdateStyleAction.cs b/Obsidian/Net/Actions/BossBar/BossBarUpdateStyleAction.cs index 67a994fb6..6c896d4d6 100644 --- a/Obsidian/Net/Actions/BossBar/BossBarUpdateStyleAction.cs +++ b/Obsidian/Net/Actions/BossBar/BossBarUpdateStyleAction.cs @@ -2,7 +2,7 @@ namespace Obsidian.Net.Actions.BossBar; -public class BossBarUpdateStyleAction : BossBarAction +public sealed class BossBarUpdateStyleAction : BossBarAction { public BossBarColor Color { get; set; } public BossBarDivisionType Division { get; set; } diff --git a/Obsidian/Net/Actions/BossBar/BossBarUpdateTitleAction.cs b/Obsidian/Net/Actions/BossBar/BossBarUpdateTitleAction.cs index 2d6192796..b4ce4de42 100644 --- a/Obsidian/Net/Actions/BossBar/BossBarUpdateTitleAction.cs +++ b/Obsidian/Net/Actions/BossBar/BossBarUpdateTitleAction.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.Actions.BossBar; -public class BossBarUpdateTitleAction : BossBarAction +public sealed class BossBarUpdateTitleAction : BossBarAction { public ChatMessage Title { get; set; } diff --git a/Obsidian/Net/ClientHandler.cs b/Obsidian/Net/ClientHandler.cs index 5031ecb14..93b053515 100644 --- a/Obsidian/Net/ClientHandler.cs +++ b/Obsidian/Net/ClientHandler.cs @@ -9,7 +9,7 @@ namespace Obsidian.Net; -public class ClientHandler +public sealed class ClientHandler { private ConcurrentDictionary Packets { get; } = new ConcurrentDictionary(); private IServerConfiguration config; diff --git a/Obsidian/Net/MixedCodec.cs b/Obsidian/Net/MixedCodec.cs index 9c237fa86..6651c5dd5 100644 --- a/Obsidian/Net/MixedCodec.cs +++ b/Obsidian/Net/MixedCodec.cs @@ -1,2 +1,3 @@ namespace Obsidian.Net; + public sealed class MixedCodec { } diff --git a/Obsidian/Net/PacketCryptography.cs b/Obsidian/Net/PacketCryptography.cs index 62dd0f0fd..cf466195d 100644 --- a/Obsidian/Net/PacketCryptography.cs +++ b/Obsidian/Net/PacketCryptography.cs @@ -8,7 +8,7 @@ namespace Obsidian.Net; -public class PacketCryptography +public sealed class PacketCryptography { private RsaKeyPairGenerator provider; diff --git a/Obsidian/Net/Rcon/RconCommandSender.cs b/Obsidian/Net/Rcon/RconCommandSender.cs index 44a775007..8aaa4a881 100644 --- a/Obsidian/Net/Rcon/RconCommandSender.cs +++ b/Obsidian/Net/Rcon/RconCommandSender.cs @@ -2,7 +2,7 @@ namespace Obsidian.Net.Rcon; -public class RconCommandSender : ICommandSender +public sealed class RconCommandSender : ICommandSender { public IPlayer Player => null!; public CommandIssuers Issuer => CommandIssuers.RemoteConsole; diff --git a/Obsidian/Net/Rcon/RconConnection.cs b/Obsidian/Net/Rcon/RconConnection.cs index cf997ad6b..c0b50f0c7 100644 --- a/Obsidian/Net/Rcon/RconConnection.cs +++ b/Obsidian/Net/Rcon/RconConnection.cs @@ -18,7 +18,7 @@ namespace Obsidian.Net.Rcon; -public class RconConnection +public sealed class RconConnection { public uint Id { get; } public TcpClient Client { get; } diff --git a/Obsidian/Net/Rcon/RconPacket.cs b/Obsidian/Net/Rcon/RconPacket.cs index 36cdec100..6cac15d9e 100644 --- a/Obsidian/Net/Rcon/RconPacket.cs +++ b/Obsidian/Net/Rcon/RconPacket.cs @@ -8,7 +8,7 @@ namespace Obsidian.Net.Rcon; -public class RconPacket +public sealed class RconPacket { private readonly Encoding encoding; diff --git a/Obsidian/Net/WindowProperties/AnvilWindowProperty.cs b/Obsidian/Net/WindowProperties/AnvilWindowProperty.cs index ffd6f305e..4cf2f440e 100644 --- a/Obsidian/Net/WindowProperties/AnvilWindowProperty.cs +++ b/Obsidian/Net/WindowProperties/AnvilWindowProperty.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.WindowProperties; -public class AnvilWindowProperty : IWindowProperty +public sealed class AnvilWindowProperty : IWindowProperty { public short Property { get; } diff --git a/Obsidian/Net/WindowProperties/BeaconWindowProperty.cs b/Obsidian/Net/WindowProperties/BeaconWindowProperty.cs index caccb8f0f..4682c312d 100644 --- a/Obsidian/Net/WindowProperties/BeaconWindowProperty.cs +++ b/Obsidian/Net/WindowProperties/BeaconWindowProperty.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.WindowProperties; -public class BeaconWindowProperty : IWindowProperty +public sealed class BeaconWindowProperty : IWindowProperty { public short Property { get; } diff --git a/Obsidian/Net/WindowProperties/BrewingStandWindowProperty.cs b/Obsidian/Net/WindowProperties/BrewingStandWindowProperty.cs index 9ab0ea281..e0da4b1f1 100644 --- a/Obsidian/Net/WindowProperties/BrewingStandWindowProperty.cs +++ b/Obsidian/Net/WindowProperties/BrewingStandWindowProperty.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.WindowProperties; -public class BrewingStandWindowProperty : IWindowProperty +public sealed class BrewingStandWindowProperty : IWindowProperty { public short Property { get; } diff --git a/Obsidian/Net/WindowProperties/EnchantmentTableWindowProperty.cs b/Obsidian/Net/WindowProperties/EnchantmentTableWindowProperty.cs index 4acbca0ce..c269c9c87 100644 --- a/Obsidian/Net/WindowProperties/EnchantmentTableWindowProperty.cs +++ b/Obsidian/Net/WindowProperties/EnchantmentTableWindowProperty.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.WindowProperties; -public class EnchantmentTableWindowProperty : IWindowProperty +public sealed class EnchantmentTableWindowProperty : IWindowProperty { public short Property { get; } diff --git a/Obsidian/Net/WindowProperties/FurnaceWindowProperty.cs b/Obsidian/Net/WindowProperties/FurnaceWindowProperty.cs index 1d63c5fdb..ce5b1aac3 100644 --- a/Obsidian/Net/WindowProperties/FurnaceWindowProperty.cs +++ b/Obsidian/Net/WindowProperties/FurnaceWindowProperty.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.WindowProperties; -public class FurnaceWindowProperty : IWindowProperty +public sealed class FurnaceWindowProperty : IWindowProperty { public short Property { get; } diff --git a/Obsidian/Net/WindowProperties/LecternWindowProperty.cs b/Obsidian/Net/WindowProperties/LecternWindowProperty.cs index 4cfbc5da8..b6a55de96 100644 --- a/Obsidian/Net/WindowProperties/LecternWindowProperty.cs +++ b/Obsidian/Net/WindowProperties/LecternWindowProperty.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.WindowProperties; -public class LecternWindowProperty : IWindowProperty +public sealed class LecternWindowProperty : IWindowProperty { public short Property { get; } diff --git a/Obsidian/Net/WindowProperties/LoomWindowProperty.cs b/Obsidian/Net/WindowProperties/LoomWindowProperty.cs index c623d17e9..685479c8e 100644 --- a/Obsidian/Net/WindowProperties/LoomWindowProperty.cs +++ b/Obsidian/Net/WindowProperties/LoomWindowProperty.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.WindowProperties; -public class LoomWindowProperty : IWindowProperty +public sealed class LoomWindowProperty : IWindowProperty { public short Property { get; } diff --git a/Obsidian/Net/WindowProperties/StonecutterWindowProperty.cs b/Obsidian/Net/WindowProperties/StonecutterWindowProperty.cs index a41801e5e..0934bbebb 100644 --- a/Obsidian/Net/WindowProperties/StonecutterWindowProperty.cs +++ b/Obsidian/Net/WindowProperties/StonecutterWindowProperty.cs @@ -1,6 +1,6 @@ namespace Obsidian.Net.WindowProperties; -public class StonecutterWindowProperty : IWindowProperty +public sealed class StonecutterWindowProperty : IWindowProperty { public short Property { get; } diff --git a/Obsidian/Registries/BlocksRegistry.cs b/Obsidian/Registries/BlocksRegistry.cs index b159d8fb8..bf576fccb 100644 --- a/Obsidian/Registries/BlocksRegistry.cs +++ b/Obsidian/Registries/BlocksRegistry.cs @@ -2,7 +2,7 @@ namespace Obsidian.Registries; -internal partial class BlocksRegistry +internal static partial class BlocksRegistry { public static int GlobalBitsPerBlocks { get; private set; } static BlocksRegistry() diff --git a/Obsidian/Registries/CodecRegistry.cs b/Obsidian/Registries/CodecRegistry.cs index 2ad8a3f96..68486bb73 100644 --- a/Obsidian/Registries/CodecRegistry.cs +++ b/Obsidian/Registries/CodecRegistry.cs @@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis; namespace Obsidian.Registries; -public partial class CodecRegistry +public static partial class CodecRegistry { public static bool TryGetChatType(string resourceId, [MaybeNullWhen(false)] out ChatCodec codec) => ChatType.All.TryGetValue(resourceId, out codec); diff --git a/Obsidian/Registries/ItemsRegistry.cs b/Obsidian/Registries/ItemsRegistry.cs index f831230d6..ec38ed288 100644 --- a/Obsidian/Registries/ItemsRegistry.cs +++ b/Obsidian/Registries/ItemsRegistry.cs @@ -2,7 +2,7 @@ using Obsidian.API.Utilities; namespace Obsidian.Registries; -public partial class ItemsRegistry +public static partial class ItemsRegistry { public static Item Get(int id) => Items.Values.SingleOrDefault(x => x.Id == id); public static Item Get(Material mat) => Items.GetValueOrDefault(mat); diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index a432d7275..acdfd6608 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -33,7 +33,7 @@ namespace Obsidian; -public partial class Server : IServer +public sealed partial class Server : IServer { #if RELEASE public const string VERSION = "0.1"; diff --git a/Obsidian/Team.cs b/Obsidian/Team.cs index 40cb65f5e..ee1583cfc 100644 --- a/Obsidian/Team.cs +++ b/Obsidian/Team.cs @@ -2,7 +2,7 @@ namespace Obsidian; -public class Team : ITeam +public sealed class Team : ITeam { private readonly Scoreboard scoreboard; private readonly Server server; diff --git a/Obsidian/Utilities/Collections/DenseCollection.cs b/Obsidian/Utilities/Collections/DenseCollection.cs index 4e5a1907c..b0ffa2986 100644 --- a/Obsidian/Utilities/Collections/DenseCollection.cs +++ b/Obsidian/Utilities/Collections/DenseCollection.cs @@ -2,7 +2,7 @@ namespace Obsidian.Utilities.Collections; -public class DenseCollection : IEnumerable where T : class +public sealed class DenseCollection : IEnumerable where T : class { private readonly object lockObject = new object(); diff --git a/Obsidian/Utilities/Collections/NibbleArray.cs b/Obsidian/Utilities/Collections/NibbleArray.cs deleted file mode 100644 index 24d5ca598..000000000 --- a/Obsidian/Utilities/Collections/NibbleArray.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Obsidian.Nbt; -using System.Collections.ObjectModel; - -namespace Obsidian.Utilities.Collections; - -/// -/// Represents an array of 4-bit values. -/// -public class NibbleArray //: INbtSerializable -{ - /// - /// The data in the nibble array. Each byte contains - /// two nibbles, stored in big-endian. - /// - public byte[] Data { get; set; } - - public NibbleArray() - { - } - - /// - /// Creates a new nibble array with the given number of nibbles. - /// - public NibbleArray(int length) - { - this.Data = new byte[length / 2]; - } - - /// - /// Gets the current number of nibbles in this array. - /// - //[NbtIgnore] - public int Length - { - get { return this.Data.Length * 2; } - } - - /// - /// Gets or sets a nibble at the given index. - /// - //[NbtIgnore] - public byte this[int index] - { - get => (byte)((this.Data[index / 2] >> (index % 2 * 4)) & 0xF); - set - { - value &= 0xF; - this.Data[index / 2] &= (byte)(0xF << ((index + 1) % 2 * 4)); - this.Data[index / 2] |= (byte)(value << (index % 2 * 4)); - } - } - - public INbtTag Serialize(string tagName) - { - return new NbtArray(tagName, Data); - } - - public void Deserialize(INbtTag value) - { - this.Data = ((NbtArray)value).GetArray().ToArray(); - } -} - -public class ReadOnlyNibbleArray -{ - private NibbleArray NibbleArray { get; set; } - - public ReadOnlyNibbleArray(NibbleArray array) - { - this.NibbleArray = array; - } - - public byte this[int index] => this.NibbleArray[index]; - - public ReadOnlyCollection Data - { - get { return Array.AsReadOnly(this.NibbleArray.Data); } - } -} diff --git a/Obsidian/Utilities/Converters/DefaultEnumConverter.cs b/Obsidian/Utilities/Converters/DefaultEnumConverter.cs index 5c82cfd7f..683d23d9e 100644 --- a/Obsidian/Utilities/Converters/DefaultEnumConverter.cs +++ b/Obsidian/Utilities/Converters/DefaultEnumConverter.cs @@ -5,7 +5,7 @@ namespace Obsidian.Utilities.Converters; -public class DefaultEnumConverter : JsonConverter +public sealed class DefaultEnumConverter : JsonConverter { public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum && typeToConvert == typeof(T); @@ -22,7 +22,7 @@ public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerial public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToString().ToSnakeCase()); } -public class CraftingTypeConverter : JsonConverter +public sealed class CraftingTypeConverter : JsonConverter { public override CraftingType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { diff --git a/Obsidian/Utilities/Converters/HexColorConverter.cs b/Obsidian/Utilities/Converters/HexColorConverter.cs index 9c17ea983..607559a4e 100644 --- a/Obsidian/Utilities/Converters/HexColorConverter.cs +++ b/Obsidian/Utilities/Converters/HexColorConverter.cs @@ -3,7 +3,7 @@ namespace Obsidian.Utilities.Converters; -public class HexColorConverter : JsonConverter +public sealed class HexColorConverter : JsonConverter { public override HexColor Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new HexColor(reader.GetString()); diff --git a/Obsidian/Utilities/Converters/StringToBoolConverter.cs b/Obsidian/Utilities/Converters/StringToBoolConverter.cs index b3c9863f3..dea095509 100644 --- a/Obsidian/Utilities/Converters/StringToBoolConverter.cs +++ b/Obsidian/Utilities/Converters/StringToBoolConverter.cs @@ -3,7 +3,7 @@ namespace Obsidian.Utilities.Converters; -public class StringToBoolConverter : JsonConverter +public sealed class StringToBoolConverter : JsonConverter { public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { @@ -17,7 +17,7 @@ public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) => throw new NotImplementedException(); } -public class IntToBoolConverter : JsonConverter +public sealed class IntToBoolConverter : JsonConverter { public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { diff --git a/Obsidian/Utilities/Extensions.cs b/Obsidian/Utilities/Extensions.cs index d77c2a3eb..f8f02c17b 100644 --- a/Obsidian/Utilities/Extensions.cs +++ b/Obsidian/Utilities/Extensions.cs @@ -1,11 +1,8 @@ using Microsoft.AspNetCore.Connections; -using Obsidian.API; using Obsidian.Entities; using Obsidian.Net; using Obsidian.Net.Packets; -using Obsidian.Net.Packets.Play.Clientbound; using Obsidian.Registries; -using Obsidian.Services; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; diff --git a/Obsidian/Utilities/Config.cs b/Obsidian/Utilities/ServerConfiguration.cs similarity index 100% rename from Obsidian/Utilities/Config.cs rename to Obsidian/Utilities/ServerConfiguration.cs diff --git a/Obsidian/Utilities/ServerStatus.cs b/Obsidian/Utilities/ServerStatus.cs index 831589c3a..7c36418d2 100644 --- a/Obsidian/Utilities/ServerStatus.cs +++ b/Obsidian/Utilities/ServerStatus.cs @@ -7,7 +7,7 @@ namespace Obsidian.Utilities; -public class ServerStatus : IServerStatus +public sealed class ServerStatus : IServerStatus { private readonly ILogger _logger; private static ReadOnlySpan PngHeader => [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]; diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 51b455459..58e086b06 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -14,7 +14,7 @@ namespace Obsidian.WorldData; -public class World : IWorld +public sealed class World : IWorld { public IWorldManager WorldManager { get; } From 8bfc979674453a357e9c051b048ce5a71f28af84 Mon Sep 17 00:00:00 2001 From: Tides Date: Mon, 20 Nov 2023 10:48:14 -0500 Subject: [PATCH 26/28] OKAY ACTUAL FINAL COMMIT --- Obsidian.API/Logging/Logger.cs | 2 +- .../_Interfaces/IServerConfiguration.cs | 7 ++ Obsidian/Client.cs | 78 +++++++++---------- Obsidian/Commands/MainCommandModule.cs | 2 +- Obsidian/Entities/Player.cs | 8 +- Obsidian/Net/ClientHandler.cs | 8 +- Obsidian/Server.Events.cs | 4 +- Obsidian/Server.cs | 32 ++++---- Obsidian/Utilities/ServerConfiguration.cs | 2 +- Obsidian/Utilities/ServerStatus.cs | 51 +++++------- 10 files changed, 92 insertions(+), 102 deletions(-) diff --git a/Obsidian.API/Logging/Logger.cs b/Obsidian.API/Logging/Logger.cs index ee57cc747..30b72868b 100644 --- a/Obsidian.API/Logging/Logger.cs +++ b/Obsidian.API/Logging/Logger.cs @@ -82,5 +82,5 @@ void PrintLinePrefix() public bool IsEnabled(LogLevel logLevel) => logLevel >= MinimumLevel; - public IDisposable BeginScope(TState state) => throw new NotImplementedException(); + public IDisposable? BeginScope(TState state) where TState : notnull => null; } diff --git a/Obsidian.API/_Interfaces/IServerConfiguration.cs b/Obsidian.API/_Interfaces/IServerConfiguration.cs index ebe0555e3..69d9cff50 100644 --- a/Obsidian.API/_Interfaces/IServerConfiguration.cs +++ b/Obsidian.API/_Interfaces/IServerConfiguration.cs @@ -4,6 +4,13 @@ namespace Obsidian.API; public interface IServerConfiguration { + public bool? Baah { get; set; } + public bool CanThrottle => this.ConnectionThrottle > 0; + public bool UDPBroadcast { get; set; } + public bool IpWhitelistEnabled { get; set; } + + public long ConnectionThrottle { get; set; } + /// /// Server description. /// diff --git a/Obsidian/Client.cs b/Obsidian/Client.cs index 5f4eba256..60bd08a1b 100644 --- a/Obsidian/Client.cs +++ b/Obsidian/Client.cs @@ -60,9 +60,9 @@ public sealed class Client : IDisposable internal MessageSigningData? messageSigningData; /// - /// The current server configuration. + /// The server that the client is connected to. /// - private readonly IServerConfiguration configuration; + internal readonly Server server; private readonly IUserCache userCache; @@ -155,17 +155,13 @@ public sealed class Client : IDisposable /// /// Used to log actions caused by the client. /// - public ILogger Logger { get; private set; } + public ILogger Logger { get; } /// /// The player that the client is logged in as. /// public Player? Player { get; private set; } - /// - /// The server that the client is connected to. - /// - public Server Server { get; private set; } /// /// The client brand. This is the name that the client used to identify itself (Fabric, Forge, Quilt, etc.) @@ -173,19 +169,19 @@ public sealed class Client : IDisposable public string? Brand { get; set; } public Client(ConnectionContext connectionContext, int playerId, - IServerConfiguration configuration, ILoggerFactory loggerFactory, IUserCache playerCache) + ILoggerFactory loggerFactory, IUserCache playerCache, + Server server) { this.connectionContext = connectionContext; - this.configuration = configuration; id = playerId; LoadedChunks = []; packetCryptography = new(); - handler = new(configuration); + handler = new(server.Configuration); networkStream = new(connectionContext.Transport); minecraftStream = new(networkStream); - this.configuration = configuration; + this.server = server; this.userCache = playerCache; this.Logger = loggerFactory.CreateLogger($"Client{playerId}"); @@ -274,7 +270,7 @@ public async Task StartConnectionAsync() { case 0x00: { - if (this.Server.Config.CanThrottle) + if (this.server.Configuration.CanThrottle) { string ip = ((IPEndPoint)connectionContext.RemoteEndPoint!).Address.ToString(); @@ -289,7 +285,7 @@ public async Task StartConnectionAsync() } else { - Server.throttler.TryAdd(ip, DateTimeOffset.UtcNow.AddMilliseconds(this.Server.Config.ConnectionThrottle)); + Server.throttler.TryAdd(ip, DateTimeOffset.UtcNow.AddMilliseconds(this.server.Configuration.ConnectionThrottle)); } } @@ -319,9 +315,9 @@ public async Task StartConnectionAsync() case ClientState.Configuration: Debug.Assert(Player is not null); - var configurationPacketReceived = new PacketReceivedEventArgs(Player, this.Server, id, data); + var configurationPacketReceived = new PacketReceivedEventArgs(Player, this.server, id, data); - await Server.Events.PacketReceived.InvokeAsync(configurationPacketReceived); + await this.server.Events.PacketReceived.InvokeAsync(configurationPacketReceived); if (!configurationPacketReceived.IsCancelled) await this.handler.HandleConfigurationPackets(id, data, this); @@ -330,9 +326,9 @@ public async Task StartConnectionAsync() case ClientState.Play: Debug.Assert(Player is not null); - var playPacketReceived = new PacketReceivedEventArgs(Player, this.Server, id, data); + var playPacketReceived = new PacketReceivedEventArgs(Player, this.server, id, data); - await Server.Events.PacketReceived.InvokeAsync(playPacketReceived); + await this.server.Events.PacketReceived.InvokeAsync(playPacketReceived); if (!playPacketReceived.IsCancelled) await handler.HandlePlayPackets(id, data, this); @@ -349,7 +345,7 @@ public async Task StartConnectionAsync() if (State == ClientState.Play) { Debug.Assert(Player is not null); - await Server.Events.PlayerLeave.InvokeAsync(new PlayerLeaveEventArgs(Player, this.Server, DateTimeOffset.Now)); + await this.server.Events.PlayerLeave.InvokeAsync(new PlayerLeaveEventArgs(Player, this.server, DateTimeOffset.Now)); } Disconnected?.Invoke(this); @@ -367,9 +363,9 @@ private void Configure() private async Task HandleServerStatusRequestAsync() { - var status = new ServerStatus(Server); + var status = new ServerStatus(this.server); - _ = await Server.Events.ServerStatusRequest.InvokeAsync(new ServerStatusRequestEventArgs(Server, status)); + _ = await this.server.Events.ServerStatusRequest.InvokeAsync(new ServerStatusRequestEventArgs(this.server, status)); SendPacket(new RequestResponse(status)); } @@ -389,13 +385,13 @@ private async Task HandleHandshakeAsync(byte[] data) if (nextState == ClientState.Login) { - if ((int)handshake.Version > (int)Server.Protocol) + if ((int)handshake.Version > (int)Server.DefaultProtocol) { - await DisconnectAsync($"Outdated server! I'm still on {Server.Protocol.GetDescription()}."); + await DisconnectAsync($"Outdated server! I'm still on {Server.DefaultProtocol.GetDescription()}."); } - else if ((int)handshake.Version < (int)Server.Protocol) + else if ((int)handshake.Version < (int)Server.DefaultProtocol) { - await DisconnectAsync($"Outdated client! Please use {Server.Protocol.GetDescription()}."); + await DisconnectAsync($"Outdated client! Please use {Server.DefaultProtocol.GetDescription()}."); } } else if (nextState is not ClientState.Status or ClientState.Login or ClientState.Handshaking) @@ -404,7 +400,7 @@ private async Task HandleHandshakeAsync(byte[] data) await DisconnectAsync($"Invalid client state! Expected Status or Login, received {nextState}."); } - State = nextState == ClientState.Login && handshake.Version != Server.Protocol ? ClientState.Closed : nextState; + State = nextState == ClientState.Login && handshake.Version != Server.DefaultProtocol ? ClientState.Closed : nextState; var versionDesc = handshake.Version.GetDescription(); @@ -417,13 +413,13 @@ private async Task HandleHandshakeAsync(byte[] data) private async Task HandleLoginStartAsync(byte[] data) { var loginStart = LoginStart.Deserialize(data); - var username = configuration.MulitplayerDebugMode ? $"Player{Globals.Random.Next(1, 999)}" : loginStart.Username; - var world = (World)Server.DefaultWorld; + var username = this.server.Configuration.MulitplayerDebugMode ? $"Player{Globals.Random.Next(1, 999)}" : loginStart.Username; + var world = (World)this.server.DefaultWorld; Logger.LogDebug("Received login request from user {Username}", loginStart.Username); - await Server.DisconnectIfConnectedAsync(username); + await this.server.DisconnectIfConnectedAsync(username); - if (configuration.OnlineMode) + if (this.server.Configuration.OnlineMode) { cachedUser = await this.userCache.GetCachedUserFromNameAsync(loginStart.Username ?? throw new NullReferenceException(nameof(loginStart.PlayerUuid))); @@ -432,7 +428,7 @@ private async Task HandleLoginStartAsync(byte[] data) await DisconnectAsync("Account not found in the Mojang database"); return; } - else if (configuration.WhitelistEnabled && !configuration.Whitelisted.Any(x => x.Id == cachedUser.Id)) + else if (this.server.Configuration.WhitelistEnabled && !this.server.Configuration.Whitelisted.Any(x => x.Id == cachedUser.Id)) { await DisconnectAsync("You are not whitelisted on this server\nContact server administrator"); return; @@ -451,7 +447,7 @@ private async Task HandleLoginStartAsync(byte[] data) VerifyToken = randomToken }); } - else if (configuration.WhitelistEnabled && !configuration.Whitelisted.Any(x => x.Name == username)) + else if (this.server.Configuration.WhitelistEnabled && !this.server.Configuration.Whitelisted.Any(x => x.Name == username)) { await DisconnectAsync("You are not whitelisted on this server\nContact server administrator"); } @@ -525,7 +521,7 @@ internal async Task ConnectAsync() this.State = ClientState.Play; await Player.LoadAsync(); - if (!Server.OnlinePlayers.TryAdd(Player.Uuid, Player)) + if (!this.server.OnlinePlayers.TryAdd(Player.Uuid, Player)) { Logger.LogError("Failed to add player {Username} to online players. Undefined behavior ahead!", Player.Username); } @@ -561,7 +557,7 @@ await QueuePacketAsync(new UpdateRecipeBookPacket await SendPlayerInfoAsync(); await Player.UpdateChunksAsync(distance: 7); await SendInfoAsync(); - await Server.Events.PlayerJoin.InvokeAsync(new PlayerJoinEventArgs(Player, this.Server, DateTimeOffset.Now)); + await this.server.Events.PlayerJoin.InvokeAsync(new PlayerJoinEventArgs(Player, this.server, DateTimeOffset.Now)); } #region Packet sending @@ -624,7 +620,7 @@ internal void SendKeepAlive(DateTimeOffset time) { long keepAliveId = time.ToUnixTimeMilliseconds(); // first, check if there's any KeepAlives that are older than 30 seconds - if (missedKeepAlives.Any(x => keepAliveId - x > this.configuration.KeepAliveTimeoutInterval)) + if (missedKeepAlives.Any(x => keepAliveId - x > this.server.Configuration.KeepAliveTimeoutInterval)) { // kick player, failed to respond within 30s cancellationSource.Cancel(); @@ -665,7 +661,7 @@ internal async Task AddPlayerToListAsync(IPlayer player) Name = player.Username, }; - if (this.configuration.OnlineMode) + if (this.server.Configuration.OnlineMode) addAction.Properties.AddRange(player.SkinProperties); var list = new List() @@ -689,14 +685,14 @@ internal async Task SendPlayerInfoAsync() } var dict = new Dictionary>(); - foreach (var player in Server.OnlinePlayers.Values) + foreach (var player in this.server.OnlinePlayers.Values) { var addPlayerInforAction = new AddPlayerInfoAction() { Name = player.Username, }; - if (this.configuration.OnlineMode) + if (this.server.Configuration.OnlineMode) addPlayerInforAction.Properties.AddRange(player.SkinProperties); var list = new List @@ -742,7 +738,7 @@ internal void SendPacket(IClientboundPacket packet) internal async Task QueuePacketAsync(IClientboundPacket packet) { - var args = await Server.Events.QueuePacket.InvokeAsync(new QueuePacketEventArgs(this, packet)); + var args = await this.server.Events.QueuePacket.InvokeAsync(new QueuePacketEventArgs(this, packet)); if (args.IsCancelled) { Logger.LogDebug("Packet {PacketId} was sent to the queue, however an event handler registered in {Name} has cancelled it.", args.Packet.Id, nameof(Server.Events)); @@ -764,15 +760,15 @@ internal async Task SendChunkAsync(Chunk chunk) private async Task SendServerBrand() { await using var stream = new MinecraftStream(); - await stream.WriteStringAsync(Server.Brand); + await stream.WriteStringAsync(this.server.Brand); await QueuePacketAsync(new PluginMessagePacket("minecraft:brand", stream.ToArray())); Logger.LogDebug("Sent server brand."); } private async Task SendPlayerListDecoration() { - var header = string.IsNullOrWhiteSpace(Server.Config.Header) ? null : ChatMessage.Simple(Server.Config.Header); - var footer = string.IsNullOrWhiteSpace(Server.Config.Footer) ? null : ChatMessage.Simple(Server.Config.Footer); + var header = string.IsNullOrWhiteSpace(this.server.Configuration.Header) ? null : ChatMessage.Simple(this.server.Configuration.Header); + var footer = string.IsNullOrWhiteSpace(this.server.Configuration.Footer) ? null : ChatMessage.Simple(this.server.Configuration.Footer); await QueuePacketAsync(new SetTabListHeaderAndFooterPacket(header, footer)); Logger.LogDebug("Sent player list decoration"); diff --git a/Obsidian/Commands/MainCommandModule.cs b/Obsidian/Commands/MainCommandModule.cs index a2ff380cd..714718be4 100644 --- a/Obsidian/Commands/MainCommandModule.cs +++ b/Obsidian/Commands/MainCommandModule.cs @@ -277,7 +277,7 @@ public async Task RequestOpAsync(CommandContext ctx, string? code = null) if (ctx.Server is not Server server || ctx.Player is not IPlayer player) return; - if (!server.Config.AllowOperatorRequests) + if (!server.Configuration.AllowOperatorRequests) { await player.SendMessageAsync("§cOperator requests are disabled on this server."); return; diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs index bf020c3ed..d6f5db09f 100644 --- a/Obsidian/Entities/Player.cs +++ b/Obsidian/Entities/Player.cs @@ -223,11 +223,11 @@ public async override Task TeleportAsync(VectorF pos) var tid = Globals.Random.Next(0, 999); - await client.Server.Events.PlayerTeleported.InvokeAsync( + await client.server.Events.PlayerTeleported.InvokeAsync( new PlayerTeleportEventArgs ( this, - this.client.Server, + this.client.server, Position, pos )); @@ -784,7 +784,7 @@ public async Task GrantPermissionAsync(string permissionNode) await SavePermsAsync(); if (result) - await this.client.Server.Events.PermissionGranted.InvokeAsync(new PermissionGrantedEventArgs(this, this.client.Server, permissionNode)); + await this.client.server.Events.PermissionGranted.InvokeAsync(new PermissionGrantedEventArgs(this, this.client.server, permissionNode)); return result; } @@ -806,7 +806,7 @@ public async Task RevokePermissionAsync(string permissionNode) parent.Children.Remove(childToRemove); await this.SavePermsAsync(); - await this.client.Server.Events.PermissionRevoked.InvokeAsync(new PermissionRevokedEventArgs(this, this.client.Server, permissionNode)); + await this.client.server.Events.PermissionRevoked.InvokeAsync(new PermissionRevokedEventArgs(this, this.client.server, permissionNode)); return true; } diff --git a/Obsidian/Net/ClientHandler.cs b/Obsidian/Net/ClientHandler.cs index 93b053515..7ba594079 100644 --- a/Obsidian/Net/ClientHandler.cs +++ b/Obsidian/Net/ClientHandler.cs @@ -102,7 +102,7 @@ public async Task HandleConfigurationPackets(int id, byte[] data, Client client) try { packet.Populate(data); - await packet.HandleAsync(client.Server, client.Player); + await packet.HandleAsync(client.server, client.Player); } catch (Exception e) { @@ -208,7 +208,7 @@ public async Task HandlePlayPackets(int id, byte[] data, Client client) try { packet.Populate(data); - await packet.HandleAsync(client.Server, client.Player!); + await packet.HandleAsync(client.server, client.Player!); } catch (Exception e) { @@ -225,11 +225,11 @@ public async Task HandlePlayPackets(int id, byte[] data, Client client) try { packet.Populate(data); - await packet.HandleAsync(client.Server, client.Player!); + await packet.HandleAsync(client.server, client.Player!); } catch (Exception e) { - if (client.Server.Config.VerboseExceptionLogging) + if (client.server.Configuration.VerboseExceptionLogging) _logger.LogError(e, "{message}", e.Message); } ObjectPool.Shared.Return(packet); diff --git a/Obsidian/Server.Events.cs b/Obsidian/Server.Events.cs index 107e70e85..d8fd4c1c2 100644 --- a/Obsidian/Server.Events.cs +++ b/Obsidian/Server.Events.cs @@ -315,7 +315,7 @@ private async Task OnPlayerLeave(PlayerLeaveEventArgs e) await other.client.QueuePacketAsync(destroy); } - BroadcastMessage(string.Format(Config.LeaveMessage, e.Player.Username)); + BroadcastMessage(string.Format(Configuration.LeaveMessage, e.Player.Username)); } private async Task OnPlayerJoin(PlayerJoinEventArgs e) @@ -326,7 +326,7 @@ private async Task OnPlayerJoin(PlayerJoinEventArgs e) BroadcastMessage(new ChatMessage { - Text = string.Format(Config.JoinMessage, e.Player.Username), + Text = string.Format(Configuration.JoinMessage, e.Player.Username), Color = HexColor.Yellow }); diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index acdfd6608..ccf3008ed 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -84,9 +84,7 @@ public static string VERSION public HashSet RegisteredChannels { get; } = new(); public CommandHandler CommandsHandler { get; } - - public ServerConfiguration Config { get; } - public IServerConfiguration Configuration => Config; + public IServerConfiguration Configuration { get; } public string Version => VERSION; public string Brand { get; } = "obsidian"; @@ -105,14 +103,14 @@ public Server( RconServer rconServer, IUserCache playerCache) { - Config = environment.Configuration; + Configuration = environment.Configuration; _logger = loggerFactory.CreateLogger(); _logger.LogInformation("SHA / Version: {VERSION}", VERSION); _cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(lifetime.ApplicationStopping); _cancelTokenSource.Token.Register(() => _logger.LogWarning("Obsidian is shutting down...")); _rconServer = rconServer; - Port = Config.Port; + Port = Configuration.Port; Operators = new OperatorList(this, loggerFactory); ScoreboardManager = new ScoreboardManager(this, loggerFactory); @@ -146,7 +144,7 @@ public Server( Directory.CreateDirectory(PermissionPath); Directory.CreateDirectory(PersistentDataPath); - if (Config.UDPBroadcast) + if (Configuration.UDPBroadcast) { _ = Task.Run(async () => { @@ -156,10 +154,10 @@ public Server( byte[] bytes = []; // Cached motd as utf-8 bytes while (await timer.WaitForNextTickAsync(_cancelTokenSource.Token)) { - if (Config.Motd != lastMotd) + if (Configuration.Motd != lastMotd) { - lastMotd = Config.Motd; - bytes = Encoding.UTF8.GetBytes($"[MOTD]{Config.Motd.Replace('[', '(').Replace(']', ')')}[/MOTD][AD]{Config.Port}[/AD]"); + lastMotd = Configuration.Motd; + bytes = Encoding.UTF8.GetBytes($"[MOTD]{Configuration.Motd.Replace('[', '(').Replace(']', ')')}[/MOTD][AD]{Configuration.Port}[/AD]"); } await udpClient.SendAsync(bytes, bytes.Length); } @@ -244,7 +242,7 @@ public async Task RunAsync() var loadTimeStopwatch = Stopwatch.StartNew(); // Check if MPDM and OM are enabled, if so, we can't handle connections - if (Config.MulitplayerDebugMode && Config.OnlineMode) + if (Configuration.MulitplayerDebugMode && Configuration.OnlineMode) { _logger.LogError("Incompatible Config: Multiplayer debug mode can't be enabled at the same time as online mode since usernames will be overwritten"); await StopAsync(); @@ -266,9 +264,9 @@ public async Task RunAsync() PluginManager.DirectoryWatcher.Filters = new[] { ".cs", ".dll" }; PluginManager.DirectoryWatcher.Watch("plugins"); - await Task.WhenAll(Config.DownloadPlugins.Select(path => PluginManager.LoadPluginAsync(path))); + await Task.WhenAll(Configuration.DownloadPlugins.Select(path => PluginManager.LoadPluginAsync(path))); - if (!Config.OnlineMode) + if (!Configuration.OnlineMode) _logger.LogInformation($"Starting in offline mode..."); CommandsRegistry.Register(this); @@ -351,14 +349,14 @@ private async Task AcceptClientsAsync() string ip = ((IPEndPoint)connection.RemoteEndPoint!).Address.ToString(); - if (Config.IpWhitelistEnabled && !Config.WhitelistedIPs.Contains(ip)) + if (Configuration.IpWhitelistEnabled && !Configuration.WhitelistedIPs.Contains(ip)) { _logger.LogInformation("{ip} is not whitelisted. Closing connection", ip); connection.Abort(); return; } - if (this.Config.CanThrottle) + if (this.Configuration.CanThrottle) { if (throttler.TryGetValue(ip, out var time) && time <= DateTimeOffset.UtcNow) { @@ -368,7 +366,7 @@ private async Task AcceptClientsAsync() } // TODO Entity ids need to be unique on the entire server, not per world - var client = new Client(connection, Math.Max(0, _clients.Count + this.DefaultWorld.GetTotalLoadedEntities()), this.Configuration, this.loggerFactory, this.userCache); + var client = new Client(connection, Math.Max(0, _clients.Count + this.DefaultWorld.GetTotalLoadedEntities()), this.loggerFactory, this.userCache, this); _clients.Add(client); @@ -510,7 +508,7 @@ private async Task LoopAsync() await Events.ServerTick.InvokeAsync(); keepAliveTicks++; - if (keepAliveTicks > (Config.KeepAliveInterval / 50)) // to clarify: one tick is 50 milliseconds. 50 * 200 = 10000 millis means 10 seconds + if (keepAliveTicks > (Configuration.KeepAliveInterval / 50)) // to clarify: one tick is 50 milliseconds. 50 * 200 = 10000 millis means 10 seconds { var keepAliveTime = DateTimeOffset.Now; @@ -520,7 +518,7 @@ private async Task LoopAsync() keepAliveTicks = 0; } - if (Config.Baah.HasValue) + if (Configuration.Baah.HasValue) { foreach (Player player in Players) { diff --git a/Obsidian/Utilities/ServerConfiguration.cs b/Obsidian/Utilities/ServerConfiguration.cs index 6e5adafec..12d16851c 100644 --- a/Obsidian/Utilities/ServerConfiguration.cs +++ b/Obsidian/Utilities/ServerConfiguration.cs @@ -52,7 +52,7 @@ public sealed class ServerConfiguration : IServerConfiguration /// /// Allows the server to advertise itself as a LAN server to devices on your network. /// - public bool UDPBroadcast = true; // Enabled because it's super useful for debugging tbh + public bool UDPBroadcast { get; set; } = true; // Enabled because it's super useful for debugging tbh public int PregenerateChunkRange { get; set; } = 15; // by default, pregenerate range from -15 to 15 diff --git a/Obsidian/Utilities/ServerStatus.cs b/Obsidian/Utilities/ServerStatus.cs index 7c36418d2..f0f9f5e12 100644 --- a/Obsidian/Utilities/ServerStatus.cs +++ b/Obsidian/Utilities/ServerStatus.cs @@ -27,16 +27,17 @@ public sealed class ServerStatus : IServerStatus /// /// Generates a server status from the specified . /// - public ServerStatus(Server server, bool anonymous = false) + public ServerStatus(IServer server, bool anonymous = false) { ArgumentNullException.ThrowIfNull(server); var loggerProvider = new LoggerProvider(); _logger = loggerProvider.CreateLogger("ServerStatus"); - Version = ServerVersion.Of(server); + Version = ServerVersion.Create(); if (!anonymous) Players = new ServerPlayers(server); - Description = new ServerDescription(server); + Description = new ServerDescription(server.Configuration); + var faviconFile = "favicon.png"; if (File.Exists(faviconFile)) { @@ -71,47 +72,41 @@ public ServerVersion(string name, ProtocolVersion protocol) Protocol = protocol; } - public static ServerVersion Of(Server server) + public static ServerVersion Create() { - return new ServerVersion($"Obsidian {server.Protocol.GetDescription()}", server.Protocol); + return new ServerVersion($"Obsidian {Server.DefaultProtocol.GetDescription()}", Server.DefaultProtocol); } } -public class ServerPlayers : IServerPlayers +public sealed class ServerPlayers : IServerPlayers { public int Max { get; set; } public int Online { get; set; } public List Sample { get; set; } = new(); - public ServerPlayers(Server server) + public ServerPlayers(IServer server) { - Max = server.Config.MaxPlayers; + Max = server.Configuration.MaxPlayers; foreach (Player player in server.Players) { - Sample.Add(new - { - name = player.Username, - id = player.Uuid - }); + if (!player.ClientInformation.AllowServerListings) + continue; + + this.AddPlayer(player.Username, player.Uuid); Online++; // Don't move out of the loop. The contents exposed through the enumerator may contain modifications made to the dictionary after GetEnumerator was called. } } - public void Clear() - { + public void Clear() => Sample.Clear(); - } - public void AddPlayer(string username, Guid uuid) + public void AddPlayer(string username, Guid uuid) => Sample.Add(new { - Sample.Add(new - { - name = username, - id = uuid - }); - } + name = username, + id = uuid + }); } public sealed class ServerDescription : IServerDescription @@ -122,13 +117,7 @@ public sealed class ServerDescription : IServerDescription [JsonInclude] private string text; - public ServerDescription(Server server) - { - text = FormatText(server.Config.Motd); - } + public ServerDescription(IServerConfiguration configuration) => this.text = FormatText(configuration.Motd); - private static string FormatText(string text) - { - return text.Replace('&', '§'); - } + private static string FormatText(string text) => text.Replace('&', '§'); } From 6c8f026bd9ae2bd9930622f3ccce185ed9506e72 Mon Sep 17 00:00:00 2001 From: Jon Pro Date: Tue, 21 Nov 2023 10:28:43 -0600 Subject: [PATCH 27/28] Fix bug loading worlds from disk --- Obsidian/WorldData/World.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs index 58e086b06..275c32c1e 100644 --- a/Obsidian/WorldData/World.cs +++ b/Obsidian/WorldData/World.cs @@ -64,6 +64,8 @@ public sealed class World : IWorld public string? ParentWorldName { get; private set; } private WorldLight worldLight; + private static Semaphore _regionLock; + /// /// Used to log actions caused by the client. @@ -75,6 +77,7 @@ internal World(ILogger logger, Type generatorType, IWorldManager worldManager) Logger = logger; Generator = Activator.CreateInstance(generatorType) as IWorldGenerator ?? throw new ArgumentException("Invalid generator type.", nameof(generatorType)); worldLight = new(this); + _regionLock = new(1, 1); this.WorldManager = worldManager; } @@ -535,14 +538,16 @@ public Region LoadRegionByChunk(int chunkX, int chunkZ) return LoadRegion(regionX, regionZ); } - private SemaphoreSlim semaphore = new(1, 1); - public Region LoadRegion(int regionX, int regionZ) { + _regionLock.WaitOne(); long value = NumericsHelper.IntsToLong(regionX, regionZ); if (Regions.TryGetValue(value, out var region)) + { + _regionLock.Release(); return region; + } this.Logger.LogDebug("Trying to add {x}:{z}", regionX, regionZ); @@ -554,6 +559,7 @@ public Region LoadRegion(int regionX, int regionZ) //DOesn't need to be blocking _ = region.InitAsync(); + _regionLock.Release(); return region; } From bc6599d17bb11ebac2fe01e80e4a6cf75d1f6854 Mon Sep 17 00:00:00 2001 From: Tides Date: Fri, 24 Nov 2023 12:47:53 -0500 Subject: [PATCH 28/28] Document the non self explanatory properties in config --- .../_Interfaces/IServerConfiguration.cs | 23 +++++++++++++++++-- Obsidian/Server.cs | 2 +- Obsidian/Utilities/ServerConfiguration.cs | 8 +------ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Obsidian.API/_Interfaces/IServerConfiguration.cs b/Obsidian.API/_Interfaces/IServerConfiguration.cs index 69d9cff50..ccacca743 100644 --- a/Obsidian.API/_Interfaces/IServerConfiguration.cs +++ b/Obsidian.API/_Interfaces/IServerConfiguration.cs @@ -5,10 +5,22 @@ namespace Obsidian.API; public interface IServerConfiguration { public bool? Baah { get; set; } + + /// + /// Returns true if has a value greater than 0. + /// public bool CanThrottle => this.ConnectionThrottle > 0; - public bool UDPBroadcast { get; set; } + + /// + /// Allows the server to advertise itself as a LAN server to devices on your network. + /// + public bool AllowLan { get; set; } + public bool IpWhitelistEnabled { get; set; } + /// + /// The time in milliseconds to wait before an ip is allowed to try and connect again. + /// public long ConnectionThrottle { get; set; } /// @@ -40,9 +52,17 @@ public interface IServerConfiguration /// Maximum amount of players that is allowed to be connected at the same time. /// public int MaxPlayers { get; set; } + public int PregenerateChunkRange { get; set; } + + /// + /// The speed at which world time & rain time go by. + /// public int TimeTickSpeedMultiplier { get; set; } + /// + /// Allow people to requests to become an operator. + /// public bool AllowOperatorRequests { get; set; } public bool WhitelistEnabled { get; set; } @@ -62,7 +82,6 @@ public interface IServerConfiguration /// public string Footer { get; set; } - /// /// Interval between KeepAlive packets send by the server. /// diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs index ccf3008ed..afe3977f3 100644 --- a/Obsidian/Server.cs +++ b/Obsidian/Server.cs @@ -144,7 +144,7 @@ public Server( Directory.CreateDirectory(PermissionPath); Directory.CreateDirectory(PersistentDataPath); - if (Configuration.UDPBroadcast) + if (Configuration.AllowLan) { _ = Task.Run(async () => { diff --git a/Obsidian/Utilities/ServerConfiguration.cs b/Obsidian/Utilities/ServerConfiguration.cs index 12d16851c..965dd5837 100644 --- a/Obsidian/Utilities/ServerConfiguration.cs +++ b/Obsidian/Utilities/ServerConfiguration.cs @@ -34,9 +34,6 @@ public sealed class ServerConfiguration : IServerConfiguration public bool IpWhitelistEnabled { get; set; } - [JsonIgnore] - public bool CanThrottle => this.ConnectionThrottle > 0; - public List WhitelistedIPs { get; set; } = new(); public List Whitelisted { get; set; } = new(); @@ -49,10 +46,7 @@ public sealed class ServerConfiguration : IServerConfiguration public string[] DownloadPlugins { get; set; } = []; public RconConfig? Rcon { get; set; } - /// - /// Allows the server to advertise itself as a LAN server to devices on your network. - /// - public bool UDPBroadcast { get; set; } = true; // Enabled because it's super useful for debugging tbh + public bool AllowLan { get; set; } = true; // Enabled because it's super useful for debugging tbh public int PregenerateChunkRange { get; set; } = 15; // by default, pregenerate range from -15 to 15