diff --git a/Obsidian.API/Configuration/ServerConfiguration.cs b/Obsidian.API/Configuration/ServerConfiguration.cs
index 9304fa2b6..cba40e458 100644
--- a/Obsidian.API/Configuration/ServerConfiguration.cs
+++ b/Obsidian.API/Configuration/ServerConfiguration.cs
@@ -1,15 +1,16 @@
-using Obsidian.API.Configuration;
-using System.Text.Json.Serialization;
+using System.Text.Json.Serialization;
namespace Obsidian.API.Configuration;
public sealed class ServerConfiguration
{
private byte viewDistance = 10;
+ private byte simulationDistance = 10;
+ private ushort entityBroadcastRangePercentage = 100;
// Anything lower than 3 will cause weird artifacts on the client.
private const byte MinimumViewDistance = 3;
-
+ private const byte MinimumSimulationDistance = 5;
///
/// Enabled Remote Console operation.
///
@@ -84,6 +85,18 @@ public byte ViewDistance
set => viewDistance = value >= MinimumViewDistance ? value : MinimumViewDistance;
}
+ public byte SimulationDistance
+ {
+ get => simulationDistance;
+ set => simulationDistance = value > this.ViewDistance ? value >= MinimumSimulationDistance ? value : MinimumSimulationDistance : ViewDistance;
+ }
+
+ public ushort EntityBroadcastRangePercentage
+ {
+ get => entityBroadcastRangePercentage;
+ set => Math.Max((ushort)10, value);
+ }
+
public int PregenerateChunkRange { get; set; } = 15; // by default, pregenerate range from -15 to 15;
public ServerListQuery ServerListQuery { get; set; } = ServerListQuery.Full;
diff --git a/Obsidian.API/_Enums/Pose.cs b/Obsidian.API/_Enums/Pose.cs
index b5e7b7b05..6ee351e93 100644
--- a/Obsidian.API/_Enums/Pose.cs
+++ b/Obsidian.API/_Enums/Pose.cs
@@ -14,5 +14,21 @@ public enum Pose : int
Sneaking,
- Dying
+ LongJumper,
+
+ Dying,
+
+ Croaking,
+
+ UsingTongue,
+
+ Sitting,
+
+ Roaring,
+
+ Smiffing,
+
+ Emerging,
+
+ Digging
}
diff --git a/Obsidian.API/_Interfaces/IEntity.cs b/Obsidian.API/_Interfaces/IEntity.cs
index 3252d86bd..5692f9dfe 100644
--- a/Obsidian.API/_Interfaces/IEntity.cs
+++ b/Obsidian.API/_Interfaces/IEntity.cs
@@ -44,16 +44,25 @@ public interface IEntity
public bool Summonable { get; }
public bool IsFireImmune { get; }
- public Task RemoveAsync();
- public Task TickAsync();
- public Task DamageAsync(IEntity source, float amount = 1.0f);
+ public ValueTask RemoveAsync();
+ public ValueTask TickAsync();
+ public ValueTask DamageAsync(IEntity source, float amount = 1.0f);
- public Task KillAsync(IEntity source);
- public Task KillAsync(IEntity source, ChatMessage message);
+ public ValueTask KillAsync(IEntity source);
+ public ValueTask KillAsync(IEntity source, ChatMessage message);
- public Task TeleportAsync(IWorld world);
- public Task TeleportAsync(IEntity to);
- public Task TeleportAsync(VectorF pos);
+ public ValueTask TeleportAsync(IWorld world);
+ public ValueTask TeleportAsync(IEntity to);
+ public ValueTask TeleportAsync(VectorF pos);
+
+ public bool IsInRange(IEntity entity, float distance);
+
+ ///
+ /// Spawns the specified entity to player nearby in the world.
+ ///
+ /// The velocity the entity should spawn with
+ /// Additional data for the entity. More info here:
+ public void SpawnEntity(Velocity? velocity = null, int additionalData = 0);
public IEnumerable GetEntitiesNear(float distance);
diff --git a/Obsidian.API/_Interfaces/IWorld.cs b/Obsidian.API/_Interfaces/IWorld.cs
index 2762ada58..502fdf79f 100644
--- a/Obsidian.API/_Interfaces/IWorld.cs
+++ b/Obsidian.API/_Interfaces/IWorld.cs
@@ -1,5 +1,3 @@
-using System.Collections.Concurrent;
-
namespace Obsidian.API;
public interface IWorld : IAsyncDisposable
@@ -20,20 +18,18 @@ public interface IWorld : IAsyncDisposable
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);
- public Task SetBlockAsync(int x, int y, int z, IBlock block);
+ public ValueTask GetBlockAsync(Vector location);
+ public ValueTask GetBlockAsync(int x, int y, int z);
+ public ValueTask SetBlockAsync(Vector location, IBlock block);
+ public ValueTask SetBlockAsync(int x, int y, int z, IBlock block);
- public Task SetBlockUntrackedAsync(int x, int y, int z, IBlock block, bool doBlockUpdate);
+ public ValueTask SetBlockUntrackedAsync(int x, int y, int z, IBlock block, bool doBlockUpdate);
- public Task SetBlockUntrackedAsync(Vector location, IBlock block, bool doBlockUpdate);
+ public ValueTask SetBlockUntrackedAsync(Vector location, IBlock block, bool doBlockUpdate);
- public Task GetWorldSurfaceHeightAsync(int x, int z);
+ public ValueTask GetWorldSurfaceHeightAsync(int x, int z);
- public Task SpawnEntityAsync(VectorF position, EntityType type);
+ public IEntity SpawnEntity(VectorF position, EntityType type);
public void SpawnExperienceOrbs(VectorF position, short count);
public Task DoWorldTickAsync();
diff --git a/Obsidian/Client.cs b/Obsidian/Client.cs
index 2dfe7e169..ded0ce516 100644
--- a/Obsidian/Client.cs
+++ b/Obsidian/Client.cs
@@ -126,6 +126,7 @@ public sealed class Client : IDisposable
/// The connection context associated with the .
///
private readonly ConnectionContext connectionContext;
+ private readonly ILoggerFactory loggerFactory;
///
/// 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.
@@ -155,7 +156,7 @@ public sealed class Client : IDisposable
///
/// Used to log actions caused by the client.
///
- public ILogger Logger { get; }
+ public ILogger Logger { get; private set; }
///
/// The player that the client is logged in as.
@@ -168,13 +169,13 @@ public sealed class Client : IDisposable
///
public string? Brand { get; set; }
- public Client(ConnectionContext connectionContext, int playerId,
+ public Client(ConnectionContext connectionContext,
ILoggerFactory loggerFactory, IUserCache playerCache,
Server server)
{
this.connectionContext = connectionContext;
+ this.loggerFactory = loggerFactory;
- id = playerId;
LoadedChunks = [];
packetCryptography = new();
handler = new(server.Configuration);
@@ -183,7 +184,7 @@ public Client(ConnectionContext connectionContext, int playerId,
this.server = server;
this.userCache = playerCache;
- this.Logger = loggerFactory.CreateLogger($"Client{playerId}");
+ this.Logger = loggerFactory.CreateLogger("ConnectionHandler");
missedKeepAlives = [];
var linkOptions = new DataflowLinkOptions { PropagateCompletion = true };
@@ -330,7 +331,16 @@ public async Task StartConnectionAsync()
if (result == EventResult.Cancelled)
return;
- await handler.HandlePlayPackets(id, data, this);
+ try
+ {
+ await handler.HandlePlayPackets(id, data, this);
+ }
+ catch (Exception ex)
+ {
+ this.Logger.LogDebug(ex, "Exception thrown");
+ }
+
+
break;
case ClientState.Closed:
default:
@@ -442,6 +452,8 @@ private async Task HandleLoginStartAsync(byte[] data)
return;
}
+ this.InitializeId();
+
Player = new Player(this.cachedUser.Uuid, loginStart.Username, this, world);
packetCryptography.GenerateKeyPair();
@@ -462,6 +474,8 @@ private async Task HandleLoginStartAsync(byte[] data)
}
else
{
+ this.InitializeId();
+
Player = new Player(GuidHelper.FromStringHash($"OfflinePlayer:{username}"), username, this, world);
this.SendPacket(new LoginSuccess(Player.Uuid, Player.Username)
@@ -471,6 +485,12 @@ private async Task HandleLoginStartAsync(byte[] data)
}
}
+ private void InitializeId()
+ {
+ this.id = Server.GetNextEntityId();
+ this.Logger = this.loggerFactory.CreateLogger($"Client({this.id})");
+ }
+
private async Task HandleEncryptionResponseAsync(byte[] data)
{
if (Player is null)
diff --git a/Obsidian/Commands/MainCommandModule.cs b/Obsidian/Commands/MainCommandModule.cs
index 49a5228a0..0df371bb0 100644
--- a/Obsidian/Commands/MainCommandModule.cs
+++ b/Obsidian/Commands/MainCommandModule.cs
@@ -323,7 +323,7 @@ public async Task SpawnEntityAsync(string entityType)
return;
}
- await player.World.SpawnEntityAsync(player.Position, type);
+ player.World.SpawnEntity(player.Position, type);
await player.SendMessageAsync($"Spawning: {type}");
}
@@ -342,7 +342,7 @@ public async Task DerpAsync(string entityType)
return;
}
- var frogge = await player.World.SpawnEntityAsync(player.Position, type);
+ var frogge = player.World.SpawnEntity(player.Position, type);
var server = (this.Server as Server)!;
_ = Task.Run(async () =>
diff --git a/Obsidian/Entities/AbstractHorse.cs b/Obsidian/Entities/AbstractHorse.cs
index 1df591984..0539fbf5b 100644
--- a/Obsidian/Entities/AbstractHorse.cs
+++ b/Obsidian/Entities/AbstractHorse.cs
@@ -15,7 +15,7 @@ public async override Task WriteAsync(MinecraftStream stream)
await stream.WriteEntityMetdata(16, EntityMetadataType.Byte, this.HorseMask);
if (this.Owner != default)
- await stream.WriteEntityMetdata(17, EntityMetadataType.OptUuid, Owner, true);
+ await stream.WriteEntityMetdata(17, EntityMetadataType.OptionalUUID, Owner, true);
}
public override void Write(MinecraftStream stream)
@@ -25,7 +25,7 @@ public override void Write(MinecraftStream stream)
stream.WriteEntityMetadataType(16, EntityMetadataType.Byte);
stream.WriteUnsignedByte((byte)HorseMask);
- stream.WriteEntityMetadataType(17, EntityMetadataType.OptUuid);
+ stream.WriteEntityMetadataType(17, EntityMetadataType.OptionalUUID);
stream.WriteBoolean(true);
if (true)
stream.WriteUuid(Owner);
diff --git a/Obsidian/Entities/Entity.cs b/Obsidian/Entities/Entity.cs
index c092f443b..4eb312bbb 100644
--- a/Obsidian/Entities/Entity.cs
+++ b/Obsidian/Entities/Entity.cs
@@ -68,7 +68,7 @@ public class Entity : IEquatable, IEntity
public IGoalController? GoalController { get; set; }
#region Update methods
- internal virtual async Task UpdateAsync(VectorF position, bool onGround)
+ internal virtual async ValueTask UpdateAsync(VectorF position, bool onGround)
{
var isNewLocation = position != Position;
@@ -76,7 +76,7 @@ internal virtual async Task UpdateAsync(VectorF position, bool onGround)
{
var delta = (Vector)((position * 32 - Position * 32) * 128);
- this.PacketBroadcaster.BroadcastToWorld(this.World, new UpdateEntityPositionPacket
+ this.PacketBroadcaster.BroadcastToWorldInRange(this.World, position, new UpdateEntityPositionPacket
{
EntityId = EntityId,
@@ -89,7 +89,7 @@ internal virtual async Task UpdateAsync(VectorF position, bool onGround)
await UpdatePositionAsync(position, onGround);
}
- internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch, bool onGround)
+ internal virtual async ValueTask UpdateAsync(VectorF position, Angle yaw, Angle pitch, bool onGround)
{
var isNewLocation = position != Position;
var isNewRotation = yaw != Yaw || pitch != Pitch;
@@ -100,7 +100,7 @@ internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch
if (isNewRotation)
{
- this.PacketBroadcaster.BroadcastToWorld(this.World, new UpdateEntityPositionAndRotationPacket
+ this.PacketBroadcaster.BroadcastToWorldInRange(this.World, position, new UpdateEntityPositionAndRotationPacket
{
EntityId = EntityId,
@@ -116,7 +116,7 @@ internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch
}
else
{
- this.PacketBroadcaster.BroadcastToWorld(this.World, new UpdateEntityPositionPacket
+ this.PacketBroadcaster.BroadcastToWorldInRange(this.World, position, new UpdateEntityPositionPacket
{
EntityId = EntityId,
@@ -130,7 +130,7 @@ internal virtual async Task UpdateAsync(VectorF position, Angle yaw, Angle pitch
await UpdatePositionAsync(position, yaw, pitch, onGround);
}
- internal virtual Task UpdateAsync(Angle yaw, Angle pitch, bool onGround)
+ internal virtual ValueTask UpdateAsync(Angle yaw, Angle pitch, bool onGround)
{
var isNewRotation = yaw != Yaw || pitch != Pitch;
@@ -140,11 +140,24 @@ internal virtual Task UpdateAsync(Angle yaw, Angle pitch, bool onGround)
this.SetHeadRotation(yaw);
}
- return Task.CompletedTask;
+ return default;
}
+ public bool IsInRange(IEntity entity, float distance)
+ {
+ if (this.World != entity.World)
+ return false;
+
+ var locationDifference = LocationDiff.GetDifference(this.Position, entity.Position);
+
+ distance *= distance;
+
+ return locationDifference.CalculatedDifference <= distance;
+ }
+
+
public void SetHeadRotation(Angle headYaw) =>
- this.PacketBroadcaster.BroadcastToWorld(this.World, new SetHeadRotationPacket
+ this.PacketBroadcaster.BroadcastToWorldInRange(this.World, this.Position, new SetHeadRotationPacket
{
EntityId = EntityId,
HeadYaw = headYaw
@@ -152,7 +165,7 @@ public void SetHeadRotation(Angle headYaw) =>
public void SetRotation(Angle yaw, Angle pitch, bool onGround = true)
{
- this.PacketBroadcaster.BroadcastToWorld(this.World, new UpdateEntityRotationPacket
+ this.PacketBroadcaster.BroadcastToWorldInRange(this.World, this.Position, new UpdateEntityRotationPacket
{
EntityId = EntityId,
OnGround = onGround,
@@ -214,11 +227,11 @@ public VectorF GetLookDirection()
return new(-cosPitch * sinYaw, -sinPitch, cosPitch * cosYaw);
}
- public async Task RemoveAsync() => await this.world.DestroyEntityAsync(this);
+ public virtual async ValueTask RemoveAsync() => await this.world.DestroyEntityAsync(this);
- private EntityBitMask GenerateBitmask()
+ protected EntityBitMask GenerateBitmask()
{
- var mask = EntityBitMask.None;
+ EntityBitMask mask = EntityBitMask.None;
if (Sneaking)
{
@@ -252,7 +265,7 @@ public virtual async Task WriteAsync(MinecraftStream stream)
await stream.WriteEntityMetdata(1, EntityMetadataType.VarInt, Air);
- await stream.WriteEntityMetdata(2, EntityMetadataType.OptChat, CustomName!, CustomName != null);
+ await stream.WriteEntityMetdata(2, EntityMetadataType.OptionalTextComponent, CustomName!, CustomName != null);
await stream.WriteEntityMetdata(3, EntityMetadataType.Boolean, CustomNameVisible);
await stream.WriteEntityMetdata(4, EntityMetadataType.Boolean, Silent);
@@ -270,7 +283,7 @@ public virtual void Write(MinecraftStream stream)
stream.WriteEntityMetadataType(1, EntityMetadataType.VarInt);
stream.WriteVarInt(Air);
- stream.WriteEntityMetadataType(2, EntityMetadataType.OptChat);
+ stream.WriteEntityMetadataType(2, EntityMetadataType.OptionalTextComponent);
stream.WriteBoolean(CustomName is not null);
if (CustomName is not null)
stream.WriteChat(CustomName);
@@ -285,7 +298,7 @@ public virtual void Write(MinecraftStream stream)
stream.WriteBoolean(NoGravity);
stream.WriteEntityMetadataType(6, EntityMetadataType.Pose);
- stream.WriteVarInt((int)Pose);
+ stream.WriteVarInt((int)this.Pose);
stream.WriteEntityMetadataType(7, EntityMetadataType.VarInt);
stream.WriteVarInt(PowderedSnowTicks);
@@ -293,10 +306,11 @@ public virtual void Write(MinecraftStream stream)
public IEnumerable GetEntitiesNear(float distance) => world.GetEntitiesInRange(Position, distance).Where(x => x != this);
- public virtual Task TickAsync() => Task.CompletedTask;
+ //TODO GRAVITY
+ public virtual ValueTask TickAsync() => default;
//TODO check for other entities and handle accordingly
- public async Task DamageAsync(IEntity source, float amount = 1.0f)
+ public async ValueTask DamageAsync(IEntity source, float amount = 1.0f)
{
Health -= amount;
@@ -305,7 +319,7 @@ public async Task DamageAsync(IEntity source, float amount = 1.0f)
this.PacketBroadcaster.QueuePacketToWorld(this.World, new EntityAnimationPacket
{
EntityId = EntityId,
- Animation = EntityAnimationType.TakeDamage
+ Animation = EntityAnimationType.CriticalEffect
});
if (living is Player player)
@@ -318,8 +332,8 @@ public async Task DamageAsync(IEntity source, float amount = 1.0f)
}
}
- public virtual Task KillAsync(IEntity source) => Task.CompletedTask;
- public virtual Task KillAsync(IEntity source, ChatMessage message) => Task.CompletedTask;
+ public virtual ValueTask KillAsync(IEntity source) => default;
+ public virtual ValueTask KillAsync(IEntity source, ChatMessage message) => default;
public bool Equals([AllowNull] Entity other)
{
@@ -348,9 +362,9 @@ public bool Equals([AllowNull] Entity other)
public override int GetHashCode() => EntityId.GetHashCode();
- public virtual Task TeleportAsync(IWorld world) => Task.CompletedTask;
+ public virtual ValueTask TeleportAsync(IWorld world) => default;
- public async virtual Task TeleportAsync(IEntity to)
+ public async virtual ValueTask TeleportAsync(IEntity to)
{
if (to is not Entity target)
return;
@@ -360,56 +374,33 @@ public async virtual Task TeleportAsync(IEntity to)
await world.DestroyEntityAsync(this);
world = target.world;
- await world.SpawnEntityAsync(to.Position, Type);
-
- return;
- }
-
- if (VectorF.Distance(Position, to.Position) > 8)
- {
- this.PacketBroadcaster.QueuePacketToWorld(this.World, new TeleportEntityPacket
- {
- EntityId = EntityId,
- OnGround = OnGround,
- Position = to.Position,
- Pitch = Pitch,
- Yaw = Yaw,
- });
+ world.SpawnEntity(to.Position, Type);
return;
}
- var delta = (Vector)(to.Position * 32 - Position * 32) * 128;
-
- this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionAndRotationPacket
- {
- EntityId = EntityId,
- Delta = delta,
- OnGround = OnGround,
- Pitch = Pitch,
- Yaw = Yaw
- });
+ await this.TeleportAsync(to.Position);
}
- public async virtual Task TeleportAsync(VectorF pos)
+ public virtual ValueTask TeleportAsync(VectorF pos)
{
if (VectorF.Distance(Position, pos) > 8)
{
- this.PacketBroadcaster.QueuePacketToWorld(this.World, new TeleportEntityPacket
+ this.PacketBroadcaster.QueuePacketToWorld(this.World, 0, new TeleportEntityPacket
{
EntityId = EntityId,
OnGround = OnGround,
Position = pos,
Pitch = Pitch,
- Yaw = Yaw,
+ Yaw = Yaw
});
- return;
+ return default;
}
var delta = (Vector)(pos * 32 - Position * 32) * 128;
- this.PacketBroadcaster.QueuePacketToWorld(this.World, new UpdateEntityPositionAndRotationPacket
+ this.PacketBroadcaster.QueuePacketToWorld(this.World, 0, new UpdateEntityPositionAndRotationPacket
{
EntityId = EntityId,
Delta = delta,
@@ -417,6 +408,33 @@ public async virtual Task TeleportAsync(VectorF pos)
Pitch = Pitch,
Yaw = Yaw
});
+
+ return default;
+ }
+
+ public virtual void SpawnEntity(Velocity? velocity = null, int additionalData = 0)
+ {
+ this.PacketBroadcaster.QueuePacketToWorldInRange(this.World, this.Position, new BundledPacket
+ {
+ Packets = [
+ new SpawnEntityPacket
+ {
+ EntityId = this.EntityId,
+ Uuid = this.Uuid,
+ Type = this.Type,
+ Position = this.Position,
+ Pitch = this.Pitch,
+ Yaw = this.Yaw,
+ Data = additionalData,
+ Velocity = velocity ?? new Velocity(0, 0, 0)
+ },
+ new SetEntityMetadataPacket
+ {
+ EntityId = this.EntityId,
+ Entity = this
+ }
+ ]
+ }, this.EntityId);
}
public bool TryAddAttribute(string attributeResourceName, float value) =>
diff --git a/Obsidian/Entities/EntityMetadataType.cs b/Obsidian/Entities/EntityMetadataType.cs
index fe6e70817..7106a14f0 100644
--- a/Obsidian/Entities/EntityMetadataType.cs
+++ b/Obsidian/Entities/EntityMetadataType.cs
@@ -2,53 +2,35 @@
public enum EntityMetadataType : int
{
- Byte,
-
- VarInt,
-
- VarLong,
-
- Float,
-
- String,
-
- Chat,
-
- OptChat,
-
- Slot,
-
- Boolean,
-
- Rotation,
-
- Position,
-
- OptPosition,
-
- Direction,
-
- OptUuid,
-
- BlockId,
-
- OptBlockId,
-
- Nbt,
-
- Particle,
-
- VillagerData,
-
- OptVarInt,
-
- Pose,
-
- CatVariant,
-
- FrogVariant,
-
- GlobalPos,
-
- PaintingVariant
+ Byte = 0,
+ VarInt = 1,
+ Long = 2,
+ Float = 3,
+ String = 4,
+ TextComponent = 5,
+ OptionalTextComponent = 6,
+ Slot = 7,
+ Boolean = 8,
+ Rotations = 9,
+ BlockPos = 10,
+ OptionalBlockPos = 11,
+ Direction = 12,
+ OptionalUUID = 13,
+ BlockState = 14,
+ OptionalBlockState = 15,
+ Nbt = 16,
+ Particle = 17,
+ Particles = 18,
+ VillagerData = 19,
+ OptionalUnsignedInt = 20,
+ Pose = 21,
+ CatVariant = 22,
+ WolfVariant = 23,
+ FrongVariant = 24,
+ OptionalGlobalPos = 25,
+ PaintingVariant = 26,
+ SnifferState = 27,
+ ArmadilloState = 28,
+ Vector3 = 29,
+ Quaternion = 30
}
diff --git a/Obsidian/Entities/FallingBlock.cs b/Obsidian/Entities/FallingBlock.cs
index 4ede94f3e..3a41d92a5 100644
--- a/Obsidian/Entities/FallingBlock.cs
+++ b/Obsidian/Entities/FallingBlock.cs
@@ -29,7 +29,7 @@ public FallingBlock(VectorF position) : base()
DeltaPosition = VectorF.Zero;
}
- public async override Task TickAsync()
+ public async override ValueTask TickAsync()
{
AliveTime++;
LastPosition = Position;
diff --git a/Obsidian/Entities/ItemEntity.cs b/Obsidian/Entities/ItemEntity.cs
index f88560983..faf2f048c 100644
--- a/Obsidian/Entities/ItemEntity.cs
+++ b/Obsidian/Entities/ItemEntity.cs
@@ -6,6 +6,8 @@ namespace Obsidian.Entities;
[MinecraftEntity("minecraft:item")]
public partial class ItemEntity : Entity
{
+ private static readonly TimeSpan DropWaitTime = TimeSpan.FromSeconds(3);
+
public int Id { get; set; }
public Material Material => ItemsRegistry.Get(this.Id).Type;
@@ -20,7 +22,7 @@ public partial class ItemEntity : Entity
public ItemEntity() => this.Type = EntityType.Item;
- public override async Task WriteAsync(MinecraftStream stream)
+ public async override Task WriteAsync(MinecraftStream stream)
{
await base.WriteAsync(stream);
@@ -35,24 +37,24 @@ public override void Write(MinecraftStream stream)
stream.WriteItemStack(new ItemStack(this.Material, this.Count, this.ItemMeta));
}
- public override async Task TickAsync()
+ public async override ValueTask TickAsync()
{
await base.TickAsync();
- if (!CanPickup && this.TimeDropped.Subtract(DateTimeOffset.UtcNow).TotalSeconds > 5)
+ if (!CanPickup && DateTimeOffset.UtcNow - this.TimeDropped > DropWaitTime)
this.CanPickup = true;
- foreach (var ent in this.world.GetNonPlayerEntitiesInRange(this.Position, 1.5f))
+ foreach (var ent in this.world.GetNonPlayerEntitiesInRange(this.Position, 0.5f))
{
- if (ent is ItemEntity item)
- {
- if (item == this)
- continue;
+ if (ent is not ItemEntity item)
+ continue;
+
+ if (item == this)
+ continue;
- this.Count += item.Count;
+ this.Count += item.Count;
- await item.RemoveAsync();//TODO find a better way to removed item entities that merged
- }
+ await item.RemoveAsync();//TODO find a better way to removed item entities that merged
}
}
}
diff --git a/Obsidian/Entities/Living.cs b/Obsidian/Entities/Living.cs
index 8c8eeef76..5c1be7eec 100644
--- a/Obsidian/Entities/Living.cs
+++ b/Obsidian/Entities/Living.cs
@@ -31,7 +31,7 @@ public Living()
activePotionEffects = new ConcurrentDictionary();
}
- public override Task TickAsync()
+ public override ValueTask TickAsync()
{
foreach (var (potion, data) in activePotionEffects)
{
@@ -43,7 +43,7 @@ public override Task TickAsync()
}
}
- return Task.CompletedTask;
+ return default;
}
public bool HasPotionEffect(PotionEffect potion)
@@ -86,7 +86,7 @@ public override async Task WriteAsync(MinecraftStream stream)
await stream.WriteEntityMetdata(9, EntityMetadataType.Float, this.Health);
- await stream.WriteEntityMetdata(10, EntityMetadataType.VarInt, (int)this.ActiveEffectColor);
+ await stream.WriteEntityMetdata(10, EntityMetadataType.Particles, (int)this.ActiveEffectColor);
await stream.WriteEntityMetdata(11, EntityMetadataType.Boolean, this.AmbientPotionEffect);
@@ -94,7 +94,7 @@ public override async Task WriteAsync(MinecraftStream stream)
await stream.WriteEntityMetdata(13, EntityMetadataType.VarInt, this.AbsorbedStingers);
- await stream.WriteEntityMetdata(14, EntityMetadataType.OptPosition, this.BedBlockPosition, this.BedBlockPosition != Vector.Zero);
+ await stream.WriteEntityMetdata(14, EntityMetadataType.OptionalBlockPos, this.BedBlockPosition, this.BedBlockPosition != Vector.Zero);
}
public override void Write(MinecraftStream stream)
@@ -107,8 +107,8 @@ public override void Write(MinecraftStream stream)
stream.WriteEntityMetadataType(9, EntityMetadataType.Float);
stream.WriteFloat(Health);
- stream.WriteEntityMetadataType(10, EntityMetadataType.VarInt);
- stream.WriteVarInt((int)ActiveEffectColor);
+ stream.WriteEntityMetadataType(10, EntityMetadataType.Particles);//This is a list of integers?
+ stream.WriteVarInt(0);
stream.WriteEntityMetadataType(11, EntityMetadataType.Boolean);
stream.WriteBoolean(AmbientPotionEffect);
@@ -119,7 +119,7 @@ public override void Write(MinecraftStream stream)
stream.WriteEntityMetadataType(13, EntityMetadataType.VarInt);
stream.WriteVarInt(AbsorbedStingers);
- stream.WriteEntityMetadataType(14, EntityMetadataType.OptPosition);
+ stream.WriteEntityMetadataType(14, EntityMetadataType.OptionalBlockPos);
stream.WriteBoolean(BedBlockPosition != default);
if (BedBlockPosition != default)
stream.WritePositionF(BedBlockPosition);
diff --git a/Obsidian/Entities/Player.cs b/Obsidian/Entities/Player.cs
index d125a311c..1288bf01c 100644
--- a/Obsidian/Entities/Player.cs
+++ b/Obsidian/Entities/Player.cs
@@ -1,5 +1,6 @@
// This would be saved in a file called [playeruuid].dat which holds a bunch of NBT data.
// https://wiki.vg/Map_Format
+using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Obsidian.API._Types;
using Obsidian.API.Events;
@@ -12,9 +13,11 @@
using Obsidian.Net.Scoreboard;
using Obsidian.Registries;
using Obsidian.WorldData;
+using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Linq;
using System.Net;
namespace Obsidian.Entities;
@@ -31,7 +34,7 @@ public sealed partial class Player : Living, IPlayer
///
protected ILogger Logger { get; private set; }
- internal HashSet visiblePlayers = [];
+ internal HashSet visiblePlayers = [];
//TODO: better name??
internal short inventorySlot = 36;
@@ -237,7 +240,7 @@ public async Task OpenInventoryAsync(BaseContainer container)
await client.QueuePacketAsync(new SetContainerContentPacket(nextId, container.ToList()));
}
- public async override Task TeleportAsync(VectorF pos)
+ public async override ValueTask TeleportAsync(VectorF pos)
{
LastPosition = Position;
Position = pos;
@@ -263,7 +266,7 @@ await client.QueuePacketAsync(new SynchronizePlayerPositionPacket
TeleportId = tid;
}
- public async override Task TeleportAsync(IEntity to)
+ public async override ValueTask TeleportAsync(IEntity to)
{
LastPosition = Position;
Position = to.Position;
@@ -280,7 +283,7 @@ await client.QueuePacketAsync(new SynchronizePlayerPositionPacket
});
}
- public async override Task TeleportAsync(IWorld world)
+ public async override ValueTask TeleportAsync(IWorld world)
{
if (world is not World w)
{
@@ -398,7 +401,7 @@ await client.QueuePacketAsync(new SynchronizePlayerPositionPacket
}
//TODO make IDamageSource
- public async override Task KillAsync(IEntity source, ChatMessage deathMessage)
+ public async override ValueTask KillAsync(IEntity source, ChatMessage deathMessage)
{
//await this.client.QueuePacketAsync(new PlayerDied
//{
@@ -412,7 +415,7 @@ public async override Task KillAsync(IEntity source, ChatMessage deathMessage)
await RemoveAsync();
if (source is Player attacker)
- attacker.visiblePlayers.Remove(EntityId);
+ attacker.visiblePlayers.Remove(this);
}
public async override Task WriteAsync(MinecraftStream stream)
@@ -453,13 +456,13 @@ public override void Write(MinecraftStream stream)
if (LeftShoulder is not null)
{
stream.WriteEntityMetadataType(19, EntityMetadataType.Nbt);
- stream.WriteVarInt(LeftShoulder);
+ stream.WriteNbtCompound(new NbtCompound());
}
if (RightShoulder is not null)
{
stream.WriteEntityMetadataType(20, EntityMetadataType.Nbt);
- stream.WriteVarInt(RightShoulder);
+ stream.WriteNbtCompound(new NbtCompound());
}
}
@@ -680,7 +683,7 @@ public async Task LoadAsync(bool loadFromPersistentWorld = true)
//TODO use inventory if has using global data set to true
if (persistentDataReader.ReadNextTag() is NbtCompound persistentDataCompound)
{
- var worldName = persistentDataCompound.GetString("worldName");
+ var worldName = persistentDataCompound.GetString("worldName")!;
Logger?.LogInformation("persistent world: {worldName}", worldName);
@@ -869,7 +872,7 @@ public byte GetNextContainerId()
public override string ToString() => Username;
- internal async override Task UpdateAsync(VectorF position, bool onGround)
+ internal async override ValueTask UpdateAsync(VectorF position, bool onGround)
{
await base.UpdateAsync(position, onGround);
@@ -880,7 +883,7 @@ internal async override Task UpdateAsync(VectorF position, bool onGround)
await PickupNearbyItemsAsync();
}
- internal async override Task UpdateAsync(VectorF position, Angle yaw, Angle pitch, bool onGround)
+ internal async override ValueTask UpdateAsync(VectorF position, Angle yaw, Angle pitch, bool onGround)
{
await base.UpdateAsync(position, yaw, pitch, onGround);
@@ -891,49 +894,63 @@ internal async override Task UpdateAsync(VectorF position, Angle yaw, Angle pitc
await PickupNearbyItemsAsync();
}
- internal async override Task UpdateAsync(Angle yaw, Angle pitch, bool onGround)
+ internal async override ValueTask UpdateAsync(Angle yaw, Angle pitch, bool onGround)
{
await base.UpdateAsync(yaw, pitch, onGround);
await PickupNearbyItemsAsync();
}
- private async Task TrySpawnPlayerAsync(VectorF position)
+ private async ValueTask TrySpawnPlayerAsync(VectorF position)
{
- foreach (var player in world.GetPlayersInRange(position, ClientInformation.ViewDistance))
+ //TODO PROPER DISTANCE CALCULATION
+ var entityBroadcastDistance = this.world.Configuration.EntityBroadcastRangePercentage;
+
+ foreach (var player in world.GetPlayersInRange(position, entityBroadcastDistance))
{
if (player == this)
continue;
- if (player.Alive && !visiblePlayers.Contains(player.EntityId))
+ if (player.Alive && !visiblePlayers.Contains(player))
{
- visiblePlayers.Add(player.EntityId);
+ visiblePlayers.Add(player);
- await client.QueuePacketAsync(new SpawnPlayerPacket
- {
- EntityId = player.EntityId,
- Uuid = player.Uuid,
- Position = player.Position,
- Yaw = player.Yaw,
- Pitch = player.Pitch
- });
+ player.SpawnEntity();
}
}
- var removed = visiblePlayers.Where(x => !world.Players.Any(p => p.Value == x)).ToArray();
- visiblePlayers.RemoveWhere(x => !world.Players.Any(p => p.Value == x));
+ if (visiblePlayers.Count == 0)
+ return;
+
+ var removed = ArrayPool.Shared.Rent(visiblePlayers.Count);
+
+ var index = 0;
+ visiblePlayers.RemoveWhere(visiblePlayer =>
+ {
+ if (!visiblePlayer.IsInRange(this, entityBroadcastDistance))
+ {
+ removed[index++] = visiblePlayer.EntityId;
+ return true;
+ }
+ return false;
+ });
+
+ if (index > 0)
+ await client.QueuePacketAsync(new RemoveEntitiesPacket(removed.ToArray()));
- if (removed.Length > 0)
- await client.QueuePacketAsync(new RemoveEntitiesPacket(removed));
+ ArrayPool.Shared.Return(removed);
}
- private async Task PickupNearbyItemsAsync(float distance = 0.5f)
+ private async Task PickupNearbyItemsAsync(float distance = 1.5f)
{
foreach (var entity in world.GetNonPlayerEntitiesInRange(Position, distance))
{
if (entity is not ItemEntity item)
continue;
+ if (!item.CanPickup)
+ continue;
+
this.PacketBroadcaster.QueuePacketToWorld(this.World, new PickupItemPacket
{
CollectedEntityId = item.EntityId,
diff --git a/Obsidian/Events/MainEventHandler.cs b/Obsidian/Events/MainEventHandler.cs
index d093eef1a..dd7e439cf 100644
--- a/Obsidian/Events/MainEventHandler.cs
+++ b/Obsidian/Events/MainEventHandler.cs
@@ -336,7 +336,7 @@ public async Task OnPlayerLeave(PlayerLeaveEventArgs e)
continue;
await other.client.RemovePlayerFromListAsync(player);
- if (other.visiblePlayers.Contains(player.EntityId))
+ if (other.visiblePlayers.Contains(player))
await other.client.QueuePacketAsync(destroy);
}
@@ -349,9 +349,10 @@ public async Task OnPlayerJoin(PlayerJoinEventArgs e)
var joined = e.Player as Player;
var server = e.Server as Server;
- joined.world.TryAddPlayer(joined);
+ joined!.world.TryAddPlayer(joined);
+ joined!.world.TryAddEntity(joined);
- server.BroadcastMessage(new ChatMessage
+ server!.BroadcastMessage(new ChatMessage
{
Text = string.Format(server.Configuration.Messages.Join, e.Player.Username),
Color = HexColor.Yellow
diff --git a/Obsidian/Net/MinecraftStream.Writing.cs b/Obsidian/Net/MinecraftStream.Writing.cs
index 268b094c9..89711af69 100644
--- a/Obsidian/Net/MinecraftStream.Writing.cs
+++ b/Obsidian/Net/MinecraftStream.Writing.cs
@@ -1,4 +1,5 @@
-using Obsidian.API.Advancements;
+using Obsidian.API;
+using Obsidian.API.Advancements;
using Obsidian.API.Crafting;
using Obsidian.API.Inventory;
using Obsidian.API.Registry.Codecs.ArmorTrims.TrimMaterial;
@@ -20,6 +21,7 @@
using Obsidian.Registries;
using Obsidian.Serialization.Attributes;
using System.Buffers.Binary;
+using System.IO;
using System.Text;
using System.Text.Json;
@@ -650,74 +652,20 @@ public void WriteItemStack(ItemStack value)
value ??= new ItemStack(0, 0) { Present = true };
var item = value.AsItem();
- WriteVarInt(item.Id);
-
- if (item.Id != 0)
- {
- WriteByte((sbyte)value.Count);
-
- NbtWriter writer = new(this, true);
-
- ItemMeta meta = value.ItemMeta;
-
- if (meta.HasTags())
- {
- writer.WriteByte("Unbreakable", (byte)(meta.Unbreakable ? 1 : 0));
-
- if (meta.Durability > 0)
- writer.WriteInt("Damage", meta.Durability);
-
- if (meta.CustomModelData > 0)
- writer.WriteInt("CustomModelData", meta.CustomModelData);
+ var meta = value.ItemMeta;
- if (meta.CanDestroy is not null)
- {
- writer.WriteListStart("CanDestroy", NbtTagType.String, meta.CanDestroy.Count);
-
- foreach (var block in meta.CanDestroy)
- writer.WriteString(block);
-
- writer.EndList();
- }
-
- if (meta.Name is not null)
- {
- writer.WriteCompoundStart("display");
-
- writer.WriteString("Name", new List { meta.Name }.ToJson());
-
- if (meta.Lore is not null)
- {
- writer.WriteListStart("Lore", NbtTagType.String, meta.Lore.Count);
-
- foreach (var lore in meta.Lore)
- writer.WriteString(new List { lore }.ToJson());
-
- writer.EndList();
- }
-
- writer.EndCompound();
- }
- else if (meta.Lore is not null)
- {
- writer.WriteCompoundStart("display");
-
- writer.WriteListStart("Lore", NbtTagType.String, meta.Lore.Count);
-
- foreach (var lore in meta.Lore)
- writer.WriteString(new List { lore }.ToJson());
+ WriteVarInt(value.Count);
- writer.EndList();
+ //Stop serializing if item is invalid
+ if (value.Count <= 0)
+ return;
- writer.EndCompound();
- }
- }
- writer.WriteString("id", item.UnlocalizedName);
- writer.WriteByte("Count", (byte)value.Count);
+ WriteVarInt(item.Id);
+ WriteVarInt(0);
+ WriteVarInt(0);
- writer.EndCompound();
- writer.TryFinish();
- }
+ if (!meta.HasTags())
+ return;
}
[WriteMethod]
@@ -811,11 +759,11 @@ public async Task WriteEntityMetdata(byte index, EntityMetadataType type, object
await WriteStringAsync((string)value);
break;
- case EntityMetadataType.Chat:
+ case EntityMetadataType.TextComponent:
await WriteChatAsync((ChatMessage)value);
break;
- case EntityMetadataType.OptChat:
+ case EntityMetadataType.OptionalTextComponent:
await WriteBooleanAsync(optional);
if (optional)
@@ -830,14 +778,14 @@ public async Task WriteEntityMetdata(byte index, EntityMetadataType type, object
await WriteBooleanAsync((bool)value);
break;
- case EntityMetadataType.Rotation:
+ case EntityMetadataType.Rotations:
break;
- case EntityMetadataType.Position:
+ case EntityMetadataType.BlockPos:
await WritePositionFAsync((VectorF)value);
break;
- case EntityMetadataType.OptPosition:
+ case EntityMetadataType.OptionalBlockPos:
await WriteBooleanAsync(optional);
if (optional)
@@ -848,21 +796,21 @@ public async Task WriteEntityMetdata(byte index, EntityMetadataType type, object
case EntityMetadataType.Direction:
break;
- case EntityMetadataType.OptUuid:
+ case EntityMetadataType.OptionalUUID:
await WriteBooleanAsync(optional);
if (optional)
await WriteUuidAsync((Guid)value);
break;
- case EntityMetadataType.OptBlockId:
+ case EntityMetadataType.OptionalBlockState:
await WriteVarIntAsync((int)value);
break;
case EntityMetadataType.Nbt:
case EntityMetadataType.Particle:
case EntityMetadataType.VillagerData:
- case EntityMetadataType.OptVarInt:
+ case EntityMetadataType.OptionalUnsignedInt:
if (optional)
{
await WriteVarIntAsync(0);
diff --git a/Obsidian/Net/Packets/Play/Clientbound/BundleDelimiterPacket.cs b/Obsidian/Net/Packets/Play/Clientbound/BundleDelimiterPacket.cs
deleted file mode 100644
index 6da553fa5..000000000
--- a/Obsidian/Net/Packets/Play/Clientbound/BundleDelimiterPacket.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Obsidian.Net.Packets.Play.Clientbound;
-public sealed partial class BundleDelimiterPacket : IClientboundPacket
-{
- public int Id => 0x00;
-
- public void Serialize(MinecraftStream stream) { }
-}
diff --git a/Obsidian/Net/Packets/Play/Clientbound/BundledPacket.cs b/Obsidian/Net/Packets/Play/Clientbound/BundledPacket.cs
new file mode 100644
index 000000000..3c6a18e2b
--- /dev/null
+++ b/Obsidian/Net/Packets/Play/Clientbound/BundledPacket.cs
@@ -0,0 +1,26 @@
+namespace Obsidian.Net.Packets.Play.Clientbound;
+public sealed class BundledPacket : IClientboundPacket
+{
+ public required List Packets { get; set; }
+
+ public int Id => 0x00;
+
+ public void Serialize(MinecraftStream stream)
+ {
+ using var packetStream = new MinecraftStream();
+
+ foreach (var packet in this.Packets)
+ packet.Serialize(packetStream);
+
+ stream.Lock.Wait();
+ stream.WriteVarInt(Id.GetVarIntLength());
+ stream.WriteVarInt(Id);
+
+ packetStream.Position = 0;
+ packetStream.CopyTo(stream);
+
+ stream.WriteVarInt(Id.GetVarIntLength());
+ stream.WriteVarInt(Id);
+ stream.Lock.Release();
+ }
+}
diff --git a/Obsidian/Net/Packets/Play/Clientbound/EntityAnimationPacket.cs b/Obsidian/Net/Packets/Play/Clientbound/EntityAnimationPacket.cs
index 184b98961..4f209e3a2 100644
--- a/Obsidian/Net/Packets/Play/Clientbound/EntityAnimationPacket.cs
+++ b/Obsidian/Net/Packets/Play/Clientbound/EntityAnimationPacket.cs
@@ -16,8 +16,7 @@ public partial class EntityAnimationPacket : IClientboundPacket
public enum EntityAnimationType : byte
{
SwingMainArm,
- TakeDamage,
- LeaveBed,
+ LeaveBed = 2,
SwingOffhand,
CriticalEffect,
MagicalCriticalEffect
diff --git a/Obsidian/Net/Packets/Play/Clientbound/SetBlockDestroyStagePacket.cs b/Obsidian/Net/Packets/Play/Clientbound/SetBlockDestroyStagePacket.cs
index 975a1c6ec..474b619a1 100644
--- a/Obsidian/Net/Packets/Play/Clientbound/SetBlockDestroyStagePacket.cs
+++ b/Obsidian/Net/Packets/Play/Clientbound/SetBlockDestroyStagePacket.cs
@@ -8,7 +8,7 @@ public partial class SetBlockDestroyStagePacket : IClientboundPacket
public int EntityId { get; init; }
[Field(1)]
- public VectorF Position { get; init; }
+ public Vector Position { get; init; }
///
/// 0-9 to set it, any other value to remove it.
diff --git a/Obsidian/Net/Packets/Play/Clientbound/SpawnPlayerPacket.cs b/Obsidian/Net/Packets/Play/Clientbound/SpawnPlayerPacket.cs
deleted file mode 100644
index f27296e3b..000000000
--- a/Obsidian/Net/Packets/Play/Clientbound/SpawnPlayerPacket.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using Obsidian.Serialization.Attributes;
-
-namespace Obsidian.Net.Packets.Play.Clientbound;
-
-public partial class SpawnPlayerPacket : IClientboundPacket
-{
- [Field(0), VarLength]
- public int EntityId { get; init; }
-
- [Field(1)]
- public Guid Uuid { get; init; }
-
- [Field(2), DataFormat(typeof(double))]
- public VectorF Position { get; init; }
-
- [Field(3)]
- public Angle Yaw { get; init; }
-
- [Field(4)]
- public Angle Pitch { get; init; }
-
- public int Id => 0x03;
-}
diff --git a/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs
index e286515a8..d0b6d40e9 100644
--- a/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs
+++ b/Obsidian/Net/Packets/Play/Serverbound/ClickContainerPacket.cs
@@ -132,7 +132,7 @@ public async ValueTask HandleAsync(Server server, Player player)
var item = new ItemEntity
{
- EntityId = player + player.world.GetTotalLoadedEntities() + 1,
+ EntityId = Server.GetNextEntityId(),
Count = 1,
Id = removedItem.AsItem().Id,
Glowing = true,
diff --git a/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs b/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs
index af2a9ffa5..9f89630b9 100644
--- a/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs
+++ b/Obsidian/Net/Packets/Play/Serverbound/PlayerActionPacket.cs
@@ -49,70 +49,58 @@ private void BroadcastPlayerAction(Player player, IBlock block)
switch (this.Status)
{
case PlayerActionStatus.DropItem:
- {
- DropItem(player, 1);
- break;
- }
+ {
+ DropItem(player, 1);
+ break;
+ }
case PlayerActionStatus.DropItemStack:
- {
- DropItem(player, 64);
- break;
- }
+ {
+ 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
- });
+ player.PacketBroadcaster.QueuePacketToWorld(player.world, 0, new SetBlockDestroyStagePacket
+ {
+ EntityId = player,
+ Position = this.Position,
+ DestroyStage = -1
+ }, player.EntityId);
- var droppedItem = ItemsRegistry.Get(block.Material);
+ var droppedItem = ItemsRegistry.Get(block.Material);
- if (droppedItem.Id == 0) { break; }
+ 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;
- }
+ var item = new ItemEntity
+ {
+ EntityId = Server.GetNextEntityId(),
+ Count = 1,
+ Id = droppedItem.Id,
+ World = player.world,
+ Position = (VectorF)this.Position + 0.5f,
+ PacketBroadcaster = player.PacketBroadcaster,
+ };
+
+ player.world.TryAddEntity(item);
+
+ item.SpawnEntity(Velocity.FromBlockPerTick(GetRandDropVelocity(), GetRandDropVelocity(), GetRandDropVelocity()));
+
+ break;
+ }
}
}
- private void DropItem(Player player, sbyte amountToRemove)
+ private static float GetRandDropVelocity()
+ {
+ var f = Globals.Random.NextFloat();
+
+ return f * 0.5f;
+ }
+
+ private static void DropItem(Player player, sbyte amountToRemove)
{
var droppedItem = player.GetHeldItem();
@@ -123,10 +111,9 @@ private void DropItem(Player player, sbyte amountToRemove)
var item = new ItemEntity
{
- EntityId = player + player.world.GetTotalLoadedEntities() + 1,
+ EntityId = Server.GetNextEntityId(),
Count = amountToRemove,
Id = droppedItem.AsItem().Id,
- Glowing = true,
World = player.world,
PacketBroadcaster = player.PacketBroadcaster,
Position = loc
@@ -138,22 +125,7 @@ private void DropItem(Player player, sbyte amountToRemove)
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
- {
- 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
- });
+ item.SpawnEntity(vel);
player.Inventory.RemoveItem(player.inventorySlot, amountToRemove);
diff --git a/Obsidian/Server.cs b/Obsidian/Server.cs
index 8d2d4edc4..b6b694018 100644
--- a/Obsidian/Server.cs
+++ b/Obsidian/Server.cs
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Connections;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -30,6 +31,7 @@
using System.Net;
using System.Net.Sockets;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
@@ -37,6 +39,8 @@ namespace Obsidian;
public sealed partial class Server : IServer
{
+ private static int EntityCounter = 0;
+
#if RELEASE
public const string VERSION = "0.1";
#else
@@ -232,13 +236,14 @@ public void BroadcastMessage(PlayerChatMessagePacket message)
///
public void BroadcastMessage(string message)
{
- var chatMessage = ChatMessage.Simple(string.Empty)
- .AddExtra(message);
+ var chatMessage = ChatMessage.Simple(message);
_chatMessagesQueue.Enqueue(new SystemChatMessagePacket(chatMessage, false));
_logger.LogInformation(message);
}
+ public static int GetNextEntityId() => Interlocked.Increment(ref EntityCounter);
+
///
/// Starts this server asynchronously.
///
@@ -387,7 +392,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.loggerFactory, this.userCache, this);
+ var client = new Client(connection, this.loggerFactory, this.userCache, this);
_clients.Add(client);
_ = ExecuteAsync(client);
diff --git a/Obsidian/Services/PacketBroadcaster.cs b/Obsidian/Services/PacketBroadcaster.cs
index f3ed6dca8..39681c39e 100644
--- a/Obsidian/Services/PacketBroadcaster.cs
+++ b/Obsidian/Services/PacketBroadcaster.cs
@@ -24,8 +24,10 @@ public PacketBroadcaster(IServer server, ILoggerFactory loggerFactory, IServerEn
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[] excludedIds) =>
- this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = excludedIds }, 1);
+ public void QueuePacketToWorld(IWorld world, IClientboundPacket packet, params int[] excludedIds)
+ {
+ this.priorityQueue.Enqueue(new() { Packet = packet, ToWorld = world, ExcludedIds = excludedIds }, 1);
+ }
public void QueuePacketToWorld(IWorld world, int priority, IClientboundPacket packet, params int[] excludedIds) =>
this.priorityQueue.Enqueue(new() { Packet = packet, ExcludedIds = excludedIds, ToWorld = world }, priority);
@@ -35,20 +37,52 @@ public void QueuePacket(IClientboundPacket packet, int priority, params int[] ex
public void Broadcast(IClientboundPacket packet, params int[] excludedIds)
{
- foreach (var player in this.server.Players.Cast().Where(player => excludedIds.Contains(player.EntityId)))
+ foreach (var player in this.server.Players.Cast().Where(player => !excludedIds.Contains(player.EntityId)))
player.client.SendPacket(packet);
}
+ public void BroadcastToWorldInRange(IWorld toWorld, VectorF location, IClientboundPacket packet, params int[] excludedIds)
+ {
+ if (toWorld is not World world)
+ return;
+
+ foreach (var player in world.GetPlayersInRange(location, world.Configuration.EntityBroadcastRangePercentage))
+ player.client.SendPacket(packet);
+ }
+
+ public void QueuePacketToWorldInRange(IWorld toWorld, VectorF location, IClientboundPacket packet, params int[] excludedIds)
+ {
+ if (toWorld is not World world)
+ return;
+
+ var includedIDs = world.GetPlayersInRange(location, world.Configuration.EntityBroadcastRangePercentage)
+ .Select(x => x.EntityId)
+ .ToHashSet();
+
+ excludedIds = excludedIds.Concat(world.Players.Values
+ .Select(x => x.EntityId)
+ .Where(x => !includedIDs.Contains(x)))
+ .ToArray();
+
+ this.priorityQueue.Enqueue(new()
+ {
+ Packet =packet,
+ ToWorld = world,
+ ExcludedIds = excludedIds,
+ }, 1);
+ }
+
+
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)))
+ foreach (var player in world.Players.Values.Where(player => !excludedIds.Contains(player.EntityId)))
player.client.SendPacket(packet);
}
- protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+ protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
using var timer = new PeriodicTimer(TimeSpan.FromMilliseconds(20));
@@ -56,7 +90,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
- if (!this.priorityQueue.TryDequeue(out var queuedPacket, out _))
+ if (!this.priorityQueue.TryDequeue(out var queuedPacket, out var priority))
continue;
if (queuedPacket.ToWorld is World toWorld)
@@ -69,6 +103,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
foreach (var player in this.server.Players.Cast().Where(player => queuedPacket.ExcludedIds != null && !queuedPacket.ExcludedIds.Contains(player.EntityId)))
await player.client.QueuePacketAsync(queuedPacket.Packet);
+
}
}
catch (Exception e) when (e is not OperationCanceledException)
@@ -104,6 +139,8 @@ public interface IPacketBroadcaster
/// The list of entity ids to exlude from the broadcast.
public void BroadcastToWorld(IWorld toWorld, IClientboundPacket packet, params int[] excludedIds);
+ public void BroadcastToWorldInRange(IWorld world, VectorF location, IClientboundPacket packet, params int[] excludedIds);
+
///
/// Puts the packet in a priority queue for processing then broadcasting when dequeued.
///
@@ -113,6 +150,8 @@ public interface IPacketBroadcaster
/// /// 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);
+ public void QueuePacketToWorldInRange(IWorld world, VectorF location, IClientboundPacket packet, params int[] excludedIds);
+
///
/// Puts the packet in a priority queue for processing then broadcasting when dequeued.
///
diff --git a/Obsidian/Utilities/Extensions.StreamWriting.cs b/Obsidian/Utilities/Extensions.StreamWriting.cs
new file mode 100644
index 000000000..8167f9b3a
--- /dev/null
+++ b/Obsidian/Utilities/Extensions.StreamWriting.cs
@@ -0,0 +1,22 @@
+using Obsidian.Net;
+
+namespace Obsidian.Utilities;
+public partial class Extensions
+{
+ public static void Write(this ItemStack itemStack, MinecraftStream stream)
+ {
+ var item = itemStack.AsItem();
+ var meta = itemStack.ItemMeta;
+
+ stream.WriteVarInt(itemStack.Count);
+
+ //Stop serializing if item is invalid
+ if (itemStack.Count <= 0)
+ return;
+
+ stream.WriteVarInt(item.Id);
+
+ if (!meta.HasTags())
+ return;
+ }
+}
diff --git a/Obsidian/Utilities/LocationDiff.cs b/Obsidian/Utilities/LocationDiff.cs
new file mode 100644
index 000000000..3d5066df5
--- /dev/null
+++ b/Obsidian/Utilities/LocationDiff.cs
@@ -0,0 +1,19 @@
+namespace Obsidian.Utilities;
+
+internal readonly struct LocationDiff
+{
+ public required float DifferenceX { get; init; }
+
+ public required float DifferenceY { get; init; }
+
+ public required float DifferenceZ { get; init; }
+
+ public float CalculatedDifference => this.DifferenceX * this.DifferenceX + this.DifferenceZ * this.DifferenceZ;
+
+ public static LocationDiff GetDifference(VectorF entityLocation, VectorF location) => new()
+ {
+ DifferenceX = entityLocation.X - location.X,
+ DifferenceY = entityLocation.Y - location.Y,
+ DifferenceZ = entityLocation.Z - location.Z,
+ };
+}
diff --git a/Obsidian/WorldData/Generators/GenHelper.cs b/Obsidian/WorldData/Generators/GenHelper.cs
index f1d7c0b13..1753dd1e4 100644
--- a/Obsidian/WorldData/Generators/GenHelper.cs
+++ b/Obsidian/WorldData/Generators/GenHelper.cs
@@ -35,11 +35,11 @@ public async ValueTask SetBlockAsync(Vector position, IBlock block, Chunk? chunk
public ValueTask SetBlockAsync(int x, int y, int z, IBlock block, Chunk? chunk) => SetBlockAsync(new Vector(x, y, z), block, chunk);
- public Task SetBlockAsync(int x, int y, int z, IBlock block) => world.SetBlockUntrackedAsync(x, y, z, block, false);
+ public ValueTask SetBlockAsync(int x, int y, int z, IBlock block) => world.SetBlockUntrackedAsync(x, y, z, block, false);
- public Task SetBlockAsync(Vector position, IBlock block) => world.SetBlockUntrackedAsync(position, block, false);
+ public ValueTask SetBlockAsync(Vector position, IBlock block) => world.SetBlockUntrackedAsync(position, block, false);
- public async Task GetBlockAsync(Vector position, Chunk? chunk)
+ public async ValueTask GetBlockAsync(Vector position, Chunk? chunk)
{
if (chunk is Chunk c && position.X >> 4 == c.X && position.Z >> 4 == c.Z)
{
@@ -48,11 +48,11 @@ public async ValueTask SetBlockAsync(Vector position, IBlock block, Chunk? chunk
return await world.GetBlockAsync(position);
}
- public Task GetBlockAsync(int x, int y, int z, Chunk? chunk) => GetBlockAsync(new Vector(x, y, z), chunk);
+ public ValueTask GetBlockAsync(int x, int y, int z, Chunk? chunk) => GetBlockAsync(new Vector(x, y, z), chunk);
- public Task GetBlockAsync(int x, int y, int z) => world.GetBlockAsync(x, y, z);
+ public ValueTask GetBlockAsync(int x, int y, int z) => world.GetBlockAsync(x, y, z);
- public Task GetBlockAsync(Vector position) => world.GetBlockAsync(position);
+ public ValueTask GetBlockAsync(Vector position) => world.GetBlockAsync(position);
public async ValueTask GetWorldHeightAsync(int x, int z, Chunk? chunk)
{
diff --git a/Obsidian/WorldData/Region.cs b/Obsidian/WorldData/Region.cs
index de3dab057..73bbd4748 100644
--- a/Obsidian/WorldData/Region.cs
+++ b/Obsidian/WorldData/Region.cs
@@ -134,13 +134,7 @@ internal async Task SerializeChunkAsync(Chunk chunk)
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()));
+ await Parallel.ForEachAsync(Entities.Values, cts, async (entity, cts) => await entity.TickAsync());
List neighborUpdates = [];
List delayed = [];
@@ -159,7 +153,7 @@ internal async Task BeginTickAsync(CancellationToken cts = default)
if (updateNeighbor) { neighborUpdates.Add(bu); }
}
}
- delayed.ForEach(i => AddBlockUpdate(i));
+ delayed.ForEach(AddBlockUpdate);
neighborUpdates.ForEach(async u => await u.world.BlockUpdateNeighborsAsync(u));
}
diff --git a/Obsidian/WorldData/World.cs b/Obsidian/WorldData/World.cs
index 28101f750..e7f623bc2 100644
--- a/Obsidian/WorldData/World.cs
+++ b/Obsidian/WorldData/World.cs
@@ -1,4 +1,5 @@
-using Microsoft.Extensions.Logging;
+using Microsoft.CodeAnalysis;
+using Microsoft.Extensions.Logging;
using Obsidian.API.Configuration;
using Obsidian.API.Registry.Codecs.Dimensions;
using Obsidian.API.Utilities;
@@ -83,8 +84,6 @@ internal World(ILogger logger, Type generatorType, IWorldManager worldManager)
this.WorldManager = 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)
@@ -128,7 +127,7 @@ public ValueTask DestroyEntityAsync(Entity entity)
/// Whether to enqueue a job to generate the chunk if it doesn't exist and return null.
/// When set to false, a partial Chunk is returned.
/// 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)
+ public async ValueTask GetChunkAsync(int chunkX, int chunkZ, bool scheduleGeneration = true)
{
Region? region = GetRegionForChunk(chunkX, chunkZ) ?? LoadRegion(chunkX >> Region.CubicRegionSizeShift, chunkZ >> Region.CubicRegionSizeShift);
@@ -175,17 +174,17 @@ public ValueTask DestroyEntityAsync(Entity entity)
///
/// When set to false, a partial Chunk is returned.
/// Null if the region or chunk doesn't exist yet. Otherwise the full chunk or a partial chunk.
- public Task GetChunkAsync(Vector worldLocation, bool scheduleGeneration = true) => GetChunkAsync(worldLocation.X.ToChunkCoord(), worldLocation.Z.ToChunkCoord(), scheduleGeneration);
+ public ValueTask GetChunkAsync(Vector worldLocation, bool scheduleGeneration = true) => GetChunkAsync(worldLocation.X.ToChunkCoord(), worldLocation.Z.ToChunkCoord(), scheduleGeneration);
- public Task GetBlockAsync(Vector location) => GetBlockAsync(location.X, location.Y, location.Z);
+ public ValueTask GetBlockAsync(Vector location) => GetBlockAsync(location.X, location.Y, location.Z);
- public async Task GetBlockAsync(int x, int y, int z)
+ public async ValueTask GetBlockAsync(int x, int y, int z)
{
var c = await GetChunkAsync(x.ToChunkCoord(), z.ToChunkCoord(), false);
return c?.GetBlock(x, y, z);
}
- public async Task GetWorldSurfaceHeightAsync(int x, int z)
+ public async ValueTask GetWorldSurfaceHeightAsync(int x, int z)
{
var c = await GetChunkAsync(x.ToChunkCoord(), z.ToChunkCoord(), false);
return c?.Heightmaps[ChunkData.HeightmapType.MotionBlocking]
@@ -200,25 +199,25 @@ public ValueTask DestroyEntityAsync(Entity entity)
return c?.GetBlockEntity(x, y, z);
}
- public Task SetBlockEntity(Vector blockPosition, NbtCompound tileEntityData) => SetBlockEntity(blockPosition.X, blockPosition.Y, blockPosition.Z, tileEntityData);
- public async Task SetBlockEntity(int x, int y, int z, NbtCompound tileEntityData)
+ public ValueTask SetBlockEntity(Vector blockPosition, NbtCompound tileEntityData) => SetBlockEntity(blockPosition.X, blockPosition.Y, blockPosition.Z, tileEntityData);
+ public async ValueTask SetBlockEntity(int x, int y, int z, NbtCompound tileEntityData)
{
var c = await GetChunkAsync(x.ToChunkCoord(), z.ToChunkCoord(), false);
c?.SetBlockEntity(x, y, z, tileEntityData);
}
- public Task SetBlockAsync(int x, int y, int z, IBlock block) => SetBlockAsync(new Vector(x, y, z), block);
+ public ValueTask SetBlockAsync(int x, int y, int z, IBlock block) => SetBlockAsync(new Vector(x, y, z), block);
- public async Task SetBlockAsync(Vector location, IBlock block)
+ public async ValueTask SetBlockAsync(Vector location, IBlock block)
{
await SetBlockUntrackedAsync(location.X, location.Y, location.Z, block);
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);
+ public ValueTask SetBlockAsync(int x, int y, int z, IBlock block, bool doBlockUpdate) => SetBlockAsync(new Vector(x, y, z), block, doBlockUpdate);
- public async Task SetBlockAsync(Vector location, IBlock block, bool doBlockUpdate)
+ public async ValueTask SetBlockAsync(Vector location, IBlock block, bool doBlockUpdate)
{
await SetBlockUntrackedAsync(location.X, location.Y, location.Z, block, doBlockUpdate);
this.BroadcastBlockChange(block, location);
@@ -237,9 +236,9 @@ private void BroadcastBlockChange(IBlock block, Vector location)
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 ValueTask 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)
+ public async ValueTask SetBlockUntrackedAsync(int x, int y, int z, IBlock block, bool doBlockUpdate = false)
{
if (doBlockUpdate)
{
@@ -295,19 +294,17 @@ public IEnumerable GetNonPlayerEntitiesInRange(VectorF location, float d
// Iterate over chunks, taking one from each region, then getting the region itself
for (int x = left; x <= right; x += Region.CubicRegionSize)
{
- for (int y = top; y >= bottom; y -= Region.CubicRegionSize)
+ for (int z = top; z >= bottom; z -= Region.CubicRegionSize)
{
- if (GetRegionForChunk(x, y) is not Region region)
+ if (GetRegionForChunk(x, z) is not Region region)
continue;
// Return entities in range
foreach ((_, Entity entity) in region.Entities)
{
- VectorF entityLocation = entity.Position;
- float differenceX = entityLocation.X - location.X;
- float differenceY = entityLocation.Y - location.Y;
+ var locationDifference = LocationDiff.GetDifference(entity.Position, location);
- if (differenceX * differenceX + differenceY * differenceY <= distance)
+ if (locationDifference.CalculatedDifference <= distance)
{
yield return entity;
}
@@ -339,11 +336,9 @@ public IEnumerable GetPlayersInRange(VectorF location, float distance)
foreach ((_, Player player) in Players)
{
- VectorF playerLocation = player.Position;
- float differenceX = playerLocation.X - location.X;
- float differenceY = playerLocation.Y - location.Y;
+ var locationDifference = LocationDiff.GetDifference(player.Position, location);
- if (differenceX * differenceX + differenceY * differenceY <= distance)
+ if (locationDifference.CalculatedDifference <= distance)
{
yield return player;
}
@@ -410,12 +405,6 @@ public async Task DoWorldTickAsync()
//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)
- //{
- // await ManageChunksAsync();
- //}
}
#region world loading/saving
@@ -571,7 +560,7 @@ public async Task UnloadRegionAsync(int regionX, int regionZ)
await r.FlushAsync();
}
- public async Task ScheduleBlockUpdateAsync(BlockUpdate blockUpdate)
+ public async ValueTask ScheduleBlockUpdateAsync(BlockUpdate blockUpdate)
{
blockUpdate.Block ??= await GetBlockAsync(blockUpdate.position);
(int chunkX, int chunkZ) = blockUpdate.position.ToChunkCoord();
@@ -645,41 +634,29 @@ public IEntity SpawnFallingBlock(VectorF position, Material mat)
FallingBlock entity = new(position)
{
Type = EntityType.FallingBlock,
- EntityId = GetTotalLoadedEntities() + 1,
+ EntityId = Server.GetNextEntityId(),
World = this,
PacketBroadcaster = this.PacketBroadcaster,
Block = BlocksRegistry.Get(mat)
};
- this.PacketBroadcaster.QueuePacketToWorld(this, new SpawnEntityPacket
- {
- EntityId = entity.EntityId,
- Uuid = entity.Uuid,
- Type = entity.Type,
- Position = entity.Position,
- Pitch = 0,
- Yaw = 0,
- Data = entity.Block.GetHashCode()
- });
+
+ entity.SpawnEntity(null, entity.Block.GetHashCode());
TryAddEntity(entity);
return entity;
}
- public async Task SpawnEntityAsync(VectorF position, EntityType type)
+ public IEntity SpawnEntity(VectorF position, EntityType type)
{
- // Arrow, Boat, DragonFireball, AreaEffectCloud, EndCrystal, EvokerFangs, ExperienceOrb,
- // FireworkRocket, FallingBlock, Item, ItemFrame, Fireball, LeashKnot, LightningBolt,
- // LlamaSpit, Minecart, ChestMinecart, CommandBlockMinecart, FurnaceMinecart, HopperMinecart
- // SpawnerMinecart, TntMinecart, Painting, Tnt, ShulkerBullet, SpectralArrow, EnderPearl, Snowball, SmallFireball,
- // Egg, ExperienceBottle, Potion, Trident, FishingBobber, EyeOfEnder
+ if (type == EntityType.ExperienceOrb)
+ throw new NotImplementedException($"EntityType {type} is not supported.");
if (type == EntityType.FallingBlock)
- {
return SpawnFallingBlock(position + (0, 20, 0), Material.Sand);
- }
+ //TODO improve this
Entity entity;
if (type.IsNonLiving())
{
@@ -687,54 +664,25 @@ public async Task SpawnEntityAsync(VectorF position, EntityType type)
{
Type = type,
Position = position,
- EntityId = GetTotalLoadedEntities() + 1,
+ EntityId = Server.GetNextEntityId(),
World = this,
PacketBroadcaster = this.PacketBroadcaster
};
-
- if (type == EntityType.ExperienceOrb || type == EntityType.ExperienceBottle)
- {
- //TODO
- }
- else
- {
- this.PacketBroadcaster.QueuePacketToWorld(this, new SpawnEntityPacket
- {
- EntityId = entity.EntityId,
- Uuid = entity.Uuid,
- Type = entity.Type,
- Position = position,
- Pitch = 0,
- Yaw = 0,
- Data = 0,
- Velocity = new Velocity(0, 0, 0)
- });
- }
}
else
{
entity = new Living
{
Position = position,
- EntityId = GetTotalLoadedEntities() + 1,
+ EntityId = Server.GetNextEntityId(),
Type = type,
World = this,
PacketBroadcaster = this.PacketBroadcaster
};
-
- this.PacketBroadcaster.QueuePacketToWorld(this, new SpawnEntityPacket
- {
- EntityId = entity.EntityId,
- Uuid = entity.Uuid,
- Type = type,
- Position = position,
- Pitch = 0,
- Yaw = 0,
- HeadYaw = 0,
- Velocity = new Velocity(0, 0, 0)
- });
}
+ entity.SpawnEntity();
+
TryAddEntity(entity);
return entity;
@@ -877,7 +825,8 @@ await Parallel.ForEachAsync(Enumerable.Range(-regionPregenRange, regionPregenRan
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);
- if (completedChunks % 1024 == 0) { // For Jon when he's doing large world gens
+ if (completedChunks % 1024 == 0)
+ { // For Jon when he's doing large world gens
await FlushRegionsAsync();
}
}