From ceaf0e624337093bb5d4dd15ab9f4cdb9b7457cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Mon, 30 Sep 2024 17:10:20 -0300 Subject: [PATCH 1/4] salable tiles query --- Maple2.File.Ingest/Mapper/MapDataMapper.cs | 17 ++++++- .../Game/Field/FieldAccelerationStructure.cs | 44 ++++++++++++++----- .../Metadata/FieldEntity/FieldEntity.cs | 16 +++++-- Maple2.Server.Game/Commands/DebugCommand.cs | 44 +++++++++++++++++++ .../Model/Field/Entity/FieldMobSpawn.cs | 1 - 5 files changed, 107 insertions(+), 15 deletions(-) diff --git a/Maple2.File.Ingest/Mapper/MapDataMapper.cs b/Maple2.File.Ingest/Mapper/MapDataMapper.cs index 21811d94e..305b9782d 100644 --- a/Maple2.File.Ingest/Mapper/MapDataMapper.cs +++ b/Maple2.File.Ingest/Mapper/MapDataMapper.cs @@ -9,12 +9,12 @@ using Maple2.Model.Common; using Maple2.Model.Game.Field; using Maple2.Model.Metadata; -using Maple2.Model.Metadata.FieldEntity; using Maple2.PacketLib.Tools; using Maple2.Tools.Extensions; using Maple2.Tools.VectorMath; using System.IO.Compression; using System.Numerics; +using Maple2.Model.Metadata.FieldEntity; namespace Maple2.File.Ingest.Mapper; @@ -145,6 +145,21 @@ private FieldAccelerationStructure ParseMapEntities(IEnumerable enti break; } + if (entity is IMS2CubeProp cube && cube.CubeSalableGroup != 0) { + entityBounds.Min = -new Vector3(75, 75, 0); + entityBounds.Max = new Vector3(75, 75, 150); + fieldEntity = new FieldSalableTile( + Id: entityId, + Position: placeable.Position, + Rotation: placeable.Rotation, + Scale: placeable.Scale, + Bounds: entityBounds, + SalableGroup: cube.CubeSalableGroup + ); + + break; + } + bool isFluid = false; if (entity is IMS2MapProperties meshMapProperties) { diff --git a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs index 06ac22df4..6daac0a5f 100644 --- a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs +++ b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs @@ -1,6 +1,4 @@ using Maple2.Model.Common; -using Maple2.Model.Metadata; -using Maple2.Model.Metadata.FieldEntity; using Maple2.PacketLib.Tools; using Maple2.Tools; using Maple2.Tools.Extensions; @@ -8,6 +6,8 @@ using Microsoft.VisualBasic; using System.Numerics; using System.Runtime.InteropServices; +using Maple2.Model.Metadata; +using Maple2.Model.Metadata.FieldEntity; namespace Maple2.Model.Game.Field; @@ -19,21 +19,21 @@ internal enum FieldEntityMembers : byte { Rotation = 0x4, Scale = 0x8, Bounds = 0x10, - Llid = 0x20 + Llid = 0x20, } /* * FieldAccelerationStructure is a helper class that specializes in various spatial queries for objects. * It's designed to maximize performance for finding objects in a 3D space with desired constraints. - * + * * Internally it uses specialized storage techniques with fast look up times, but knowledge on these * techniques isn't necessary for use. The Query functions allow you to find the objects you need without * knowing any of the implementation details. - * + * * You can do general queries or queries for specific entity types for all types of queries available. * Every entity matching the query constraints will be reported back through a callback, or in a list with * Query___List() methods. - * + * * Available query types include: * - Cell: Captures all entities in a specific grid cell, or range of cells. Doesn't capture freely floating entities. * - Point: Captures all entities intersecting with a specific point. @@ -54,7 +54,7 @@ internal enum FieldEntityMembers : byte { * - Frustum: Captures all entities overlapping with a frustum. Useful for map rendering. * - CellsInFrustum: Captures all entities overlapping with a frustum. Useful for map rendering. Doesn't capture freely floating entities. * - TreeInFrustum: Captures all entities overlapping with a frustum. Useful for map rendering. Doesn't capture cells. - * + * * Special purpose queries: * - Spawns: Captures all mob spawn candidates in a sphere. * - Fluids: Captures all fluids within a bounding box. @@ -178,12 +178,24 @@ public List QueryFluidsList(BoundingBox3 box) { public void QueryFluids(Vector3 min, Vector3 max, Action callback) { QueryCells(min, max, (entity) => { - if (entity is FieldFluidEntity fluid && fluid.IsSurface && !fluid.IsShallow) { + if (entity is FieldFluidEntity { IsSurface: true, IsShallow: false } fluid) { callback(fluid); } }); } + public void QuerySalableTiles(Vector3 min, Vector3 max, Action callback) { + QueryCells(min, max, (entity) => { + if (entity is FieldSalableTile tile) { + callback(tile); + } + }); + } + + public void QuerySalableTilesCenter(Vector3 center, Vector3 size, Action callback) { + QuerySalableTiles(center - 0.5f * size, center + 0.5f * size, callback); + } + public List QueryFluidsList(Vector3 min, Vector3 max) { List fluids = new(); @@ -681,7 +693,8 @@ public void WriteTo(FieldEntity entity, IByteWriter writer) { FieldFluidEntity => FieldEntityType.Fluid, FieldMeshColliderEntity => FieldEntityType.MeshCollider, FieldCellEntities => FieldEntityType.Cell, - _ => FieldEntityType.Unknown + FieldSalableTile => FieldEntityType.SalableTile, + _ => throw new InvalidDataException($"Writing unhandled field entity type: {entity.GetType().FullName}"), }; FieldEntityMembers memberFlags = FieldEntityMembers.None; @@ -700,7 +713,7 @@ public void WriteTo(FieldEntity entity, IByteWriter writer) { break; } - writer.Write(type); + writer.Write(type); writer.Write(memberFlags); if ((memberFlags & FieldEntityMembers.Id) != 0) { @@ -753,6 +766,9 @@ public void WriteTo(FieldEntity entity, IByteWriter writer) { WriteTo(childEntity, writer); } break; + case FieldSalableTile salableTile: + writer.Write(salableTile.SalableGroup); + break; default: throw new InvalidDataException($"Writing unhandled field entity type: {entity.GetType().FullName}"); } @@ -928,6 +944,14 @@ public FieldEntity ReadEntity(IByteReader reader) { Scale: scale, Bounds: bounds, Entities: children); + case FieldEntityType.SalableTile: + return new FieldSalableTile( + Id: id, + Position: position, + Rotation: rotation, + Scale: scale, + Bounds: bounds, + SalableGroup: reader.Read()); default: throw new InvalidDataException($"Reading unhandled field entity type: {type}"); } diff --git a/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs b/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs index 94aba85e8..9bd1a3688 100644 --- a/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs +++ b/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs @@ -1,5 +1,5 @@ -using Maple2.Tools.VectorMath; -using System.Numerics; +using System.Numerics; +using Maple2.Tools.VectorMath; namespace Maple2.Model.Metadata.FieldEntity; @@ -10,7 +10,8 @@ public enum FieldEntityType : byte { BoxCollider, // for cube tiles (IsWhiteBox = false) & arbitrarily sized white boxes MeshCollider, Fluid, - Cell // for cells culled from the grid & demoted to AABB tree + Cell, // for cells culled from the grid & demoted to AABB tree + SalableTile, } public record FieldEntityId( @@ -83,3 +84,12 @@ public record FieldCellEntities( float Scale, BoundingBox3 Bounds, List Entities) : FieldEntity(Id, Position, Rotation, Scale, Bounds); + +public record FieldSalableTile( + FieldEntityId Id, + Vector3 Position, + Vector3 Rotation, + float Scale, + BoundingBox3 Bounds, + int SalableGroup +) : FieldEntity(Id, Position, Rotation, Scale, Bounds); diff --git a/Maple2.Server.Game/Commands/DebugCommand.cs b/Maple2.Server.Game/Commands/DebugCommand.cs index 39b78befe..cb15dbead 100644 --- a/Maple2.Server.Game/Commands/DebugCommand.cs +++ b/Maple2.Server.Game/Commands/DebugCommand.cs @@ -150,6 +150,7 @@ public DebugQueryCommand(GameSession session, MapDataStorage mapDataStorage) : b AddCommand(new DebugQuerySpawnCommand(session, mapDataStorage)); AddCommand(new DebugQueryFluidCommand(session, mapDataStorage)); AddCommand(new DebugQueryVibrateCommand(session, mapDataStorage)); + AddCommand(new DebugQuerySalableTileCommand(session, mapDataStorage)); } private class DebugQuerySpawnCommand : Command { @@ -274,5 +275,48 @@ private void Handle(InvocationContext ctx, float x, float y, float z) { }); } } + + private class DebugQuerySalableTileCommand : Command { + private readonly GameSession session; + private readonly MapDataStorage mapDataStorage; + + public DebugQuerySalableTileCommand(GameSession session, MapDataStorage mapDataStorage) : base("salable", "Searches for nearby salable tiles.") { + this.session = session; + this.mapDataStorage = mapDataStorage; + + var x = new Argument("x", () => 300, "How far along the x axis to search from the player."); + var y = new Argument("y", () => 300, "How far along the y axis to search from the player."); + var z = new Argument("z", () => 300, "How far along the z axis to search from the player."); + + AddArgument(x); + AddArgument(y); + AddArgument(z); + + this.SetHandler(Handle, x, y, z); + } + + private void Handle(InvocationContext ctx, float x, float y, float z) { + if (!session.Field.MapMetadata.TryGet(session.Field.MapId, out MapMetadata? map)) { + return; + } + + if (!mapDataStorage.TryGet(map.XBlock, out FieldAccelerationStructure? mapData)) { + return; + } + + Vector3 center = session.Player.Position; + Vector3S cell = FieldAccelerationStructure.PointToCell(center); + + ctx.Console.Out.WriteLine($"Player at {center.X} {center.Y} {center.Z} in cell {cell.X} {cell.Y} {cell.Z}"); + + mapData.QuerySalableTilesCenter(session.Player.Position, 2 * new Vector3(x, y, z), (salableTile) => { + Vector3 position = salableTile.Position; + Vector3S toCell = FieldAccelerationStructure.PointToCell(position); + + + ctx.Console.Out.WriteLine($"SalableGroupId {salableTile.SalableGroup} found at {position.X} {position.Y} {position.Z} in cell {toCell.X} {toCell.Y} {toCell.Z}"); + }); + } + } } } diff --git a/Maple2.Server.Game/Model/Field/Entity/FieldMobSpawn.cs b/Maple2.Server.Game/Model/Field/Entity/FieldMobSpawn.cs index 8511b25d9..86609f215 100644 --- a/Maple2.Server.Game/Model/Field/Entity/FieldMobSpawn.cs +++ b/Maple2.Server.Game/Model/Field/Entity/FieldMobSpawn.cs @@ -2,7 +2,6 @@ using Maple2.Model.Game; using Maple2.Model.Game.Field; using Maple2.Model.Metadata; -using Maple2.Model.Metadata.FieldEntity; using Maple2.Server.Game.Manager.Field; using Maple2.Server.Game.Packets; using Maple2.Tools; From 73d8d008099852d7dad2790ba9ef78a08d8d0ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Wed, 2 Oct 2024 19:25:08 -0300 Subject: [PATCH 2/4] merge --- Maple2.Model/Game/Field/FieldAccelerationStructure.cs | 1 - Maple2.Server.Game/Model/Field/Entity/FieldMobSpawn.cs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs index 6daac0a5f..f447d4975 100644 --- a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs +++ b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs @@ -3,7 +3,6 @@ using Maple2.Tools; using Maple2.Tools.Extensions; using Maple2.Tools.VectorMath; -using Microsoft.VisualBasic; using System.Numerics; using System.Runtime.InteropServices; using Maple2.Model.Metadata; diff --git a/Maple2.Server.Game/Model/Field/Entity/FieldMobSpawn.cs b/Maple2.Server.Game/Model/Field/Entity/FieldMobSpawn.cs index 86609f215..8511b25d9 100644 --- a/Maple2.Server.Game/Model/Field/Entity/FieldMobSpawn.cs +++ b/Maple2.Server.Game/Model/Field/Entity/FieldMobSpawn.cs @@ -2,6 +2,7 @@ using Maple2.Model.Game; using Maple2.Model.Game.Field; using Maple2.Model.Metadata; +using Maple2.Model.Metadata.FieldEntity; using Maple2.Server.Game.Manager.Field; using Maple2.Server.Game.Packets; using Maple2.Tools; From 42fe0afe006eceaf96213f6deaf79edfbb3ebe2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Wed, 2 Oct 2024 19:37:59 -0300 Subject: [PATCH 3/4] . --- Maple2.Model/Game/Field/FieldAccelerationStructure.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs index f447d4975..6f5a2e54e 100644 --- a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs +++ b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs @@ -693,7 +693,7 @@ public void WriteTo(FieldEntity entity, IByteWriter writer) { FieldMeshColliderEntity => FieldEntityType.MeshCollider, FieldCellEntities => FieldEntityType.Cell, FieldSalableTile => FieldEntityType.SalableTile, - _ => throw new InvalidDataException($"Writing unhandled field entity type: {entity.GetType().FullName}"), + _ => FieldEntityType.Unknown, }; FieldEntityMembers memberFlags = FieldEntityMembers.None; @@ -712,7 +712,7 @@ public void WriteTo(FieldEntity entity, IByteWriter writer) { break; } - writer.Write(type); + writer.Write(type); writer.Write(memberFlags); if ((memberFlags & FieldEntityMembers.Id) != 0) { From c60732f7c355094a68a26976468b2ac9185442bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Wed, 2 Oct 2024 20:34:13 -0300 Subject: [PATCH 4/4] suggestions --- Maple2.File.Ingest/Mapper/MapDataMapper.cs | 8 ++++---- .../Game/Field/FieldAccelerationStructure.cs | 20 +++++++++---------- .../Metadata/FieldEntity/FieldEntity.cs | 6 +++--- Maple2.Server.Game/Commands/DebugCommand.cs | 12 +++++------ 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Maple2.File.Ingest/Mapper/MapDataMapper.cs b/Maple2.File.Ingest/Mapper/MapDataMapper.cs index 305b9782d..c8cacf960 100644 --- a/Maple2.File.Ingest/Mapper/MapDataMapper.cs +++ b/Maple2.File.Ingest/Mapper/MapDataMapper.cs @@ -146,15 +146,15 @@ private FieldAccelerationStructure ParseMapEntities(IEnumerable enti } if (entity is IMS2CubeProp cube && cube.CubeSalableGroup != 0) { - entityBounds.Min = -new Vector3(75, 75, 0); - entityBounds.Max = new Vector3(75, 75, 150); - fieldEntity = new FieldSalableTile( + entityBounds.Min = -new Vector3(HALF_BLOCK, HALF_BLOCK, 0); + entityBounds.Max = new Vector3(HALF_BLOCK, HALF_BLOCK, BLOCK_SIZE); + fieldEntity = new FieldSellableTile( Id: entityId, Position: placeable.Position, Rotation: placeable.Rotation, Scale: placeable.Scale, Bounds: entityBounds, - SalableGroup: cube.CubeSalableGroup + SellableGroup: cube.CubeSalableGroup ); break; diff --git a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs index 6f5a2e54e..fc99f6652 100644 --- a/Maple2.Model/Game/Field/FieldAccelerationStructure.cs +++ b/Maple2.Model/Game/Field/FieldAccelerationStructure.cs @@ -183,16 +183,16 @@ public void QueryFluids(Vector3 min, Vector3 max, Action callb }); } - public void QuerySalableTiles(Vector3 min, Vector3 max, Action callback) { + public void QuerySellableTiles(Vector3 min, Vector3 max, Action callback) { QueryCells(min, max, (entity) => { - if (entity is FieldSalableTile tile) { + if (entity is FieldSellableTile tile) { callback(tile); } }); } - public void QuerySalableTilesCenter(Vector3 center, Vector3 size, Action callback) { - QuerySalableTiles(center - 0.5f * size, center + 0.5f * size, callback); + public void QuerySellableTilesCenter(Vector3 center, Vector3 size, Action callback) { + QuerySellableTiles(center - 0.5f * size, center + 0.5f * size, callback); } public List QueryFluidsList(Vector3 min, Vector3 max) { @@ -692,7 +692,7 @@ public void WriteTo(FieldEntity entity, IByteWriter writer) { FieldFluidEntity => FieldEntityType.Fluid, FieldMeshColliderEntity => FieldEntityType.MeshCollider, FieldCellEntities => FieldEntityType.Cell, - FieldSalableTile => FieldEntityType.SalableTile, + FieldSellableTile => FieldEntityType.SellableTile, _ => FieldEntityType.Unknown, }; @@ -765,8 +765,8 @@ public void WriteTo(FieldEntity entity, IByteWriter writer) { WriteTo(childEntity, writer); } break; - case FieldSalableTile salableTile: - writer.Write(salableTile.SalableGroup); + case FieldSellableTile sellableTile: + writer.Write(sellableTile.SellableGroup); break; default: throw new InvalidDataException($"Writing unhandled field entity type: {entity.GetType().FullName}"); @@ -943,14 +943,14 @@ public FieldEntity ReadEntity(IByteReader reader) { Scale: scale, Bounds: bounds, Entities: children); - case FieldEntityType.SalableTile: - return new FieldSalableTile( + case FieldEntityType.SellableTile: + return new FieldSellableTile( Id: id, Position: position, Rotation: rotation, Scale: scale, Bounds: bounds, - SalableGroup: reader.Read()); + SellableGroup: reader.Read()); default: throw new InvalidDataException($"Reading unhandled field entity type: {type}"); } diff --git a/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs b/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs index 9bd1a3688..dcd800ae1 100644 --- a/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs +++ b/Maple2.Model/Metadata/FieldEntity/FieldEntity.cs @@ -10,8 +10,8 @@ public enum FieldEntityType : byte { BoxCollider, // for cube tiles (IsWhiteBox = false) & arbitrarily sized white boxes MeshCollider, Fluid, + SellableTile, Cell, // for cells culled from the grid & demoted to AABB tree - SalableTile, } public record FieldEntityId( @@ -85,11 +85,11 @@ public record FieldCellEntities( BoundingBox3 Bounds, List Entities) : FieldEntity(Id, Position, Rotation, Scale, Bounds); -public record FieldSalableTile( +public record FieldSellableTile( FieldEntityId Id, Vector3 Position, Vector3 Rotation, float Scale, BoundingBox3 Bounds, - int SalableGroup + int SellableGroup ) : FieldEntity(Id, Position, Rotation, Scale, Bounds); diff --git a/Maple2.Server.Game/Commands/DebugCommand.cs b/Maple2.Server.Game/Commands/DebugCommand.cs index cb15dbead..5102a7866 100644 --- a/Maple2.Server.Game/Commands/DebugCommand.cs +++ b/Maple2.Server.Game/Commands/DebugCommand.cs @@ -150,7 +150,7 @@ public DebugQueryCommand(GameSession session, MapDataStorage mapDataStorage) : b AddCommand(new DebugQuerySpawnCommand(session, mapDataStorage)); AddCommand(new DebugQueryFluidCommand(session, mapDataStorage)); AddCommand(new DebugQueryVibrateCommand(session, mapDataStorage)); - AddCommand(new DebugQuerySalableTileCommand(session, mapDataStorage)); + AddCommand(new DebugQuerySellableTileCommand(session, mapDataStorage)); } private class DebugQuerySpawnCommand : Command { @@ -276,11 +276,11 @@ private void Handle(InvocationContext ctx, float x, float y, float z) { } } - private class DebugQuerySalableTileCommand : Command { + private class DebugQuerySellableTileCommand : Command { private readonly GameSession session; private readonly MapDataStorage mapDataStorage; - public DebugQuerySalableTileCommand(GameSession session, MapDataStorage mapDataStorage) : base("salable", "Searches for nearby salable tiles.") { + public DebugQuerySellableTileCommand(GameSession session, MapDataStorage mapDataStorage) : base("sellable", "Searches for nearby sellable tiles.") { this.session = session; this.mapDataStorage = mapDataStorage; @@ -309,12 +309,12 @@ private void Handle(InvocationContext ctx, float x, float y, float z) { ctx.Console.Out.WriteLine($"Player at {center.X} {center.Y} {center.Z} in cell {cell.X} {cell.Y} {cell.Z}"); - mapData.QuerySalableTilesCenter(session.Player.Position, 2 * new Vector3(x, y, z), (salableTile) => { - Vector3 position = salableTile.Position; + mapData.QuerySellableTilesCenter(session.Player.Position, 2 * new Vector3(x, y, z), (sellableTile) => { + Vector3 position = sellableTile.Position; Vector3S toCell = FieldAccelerationStructure.PointToCell(position); - ctx.Console.Out.WriteLine($"SalableGroupId {salableTile.SalableGroup} found at {position.X} {position.Y} {position.Z} in cell {toCell.X} {toCell.Y} {toCell.Z}"); + ctx.Console.Out.WriteLine($"SellableGroupId {sellableTile.SellableGroup} found at {position.X} {position.Y} {position.Z} in cell {toCell.X} {toCell.Y} {toCell.Z}"); }); } }