From e87040c5c0d2d3cc2b741c6f45b7b8bbe23bf590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Fri, 10 May 2024 01:20:45 -0300 Subject: [PATCH] feat: npc patrol data (#93) * feat: npc patrol data * Simple start script Starts all servers using windows terminal * Update Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs Co-authored-by: Zin <62830952+Zintixx@users.noreply.github.com> --------- Co-authored-by: Zin <62830952+Zintixx@users.noreply.github.com> --- .../Storage/Metadata/MapEntityStorage.cs | 5 ++ Maple2.File.Ingest/Mapper/MapEntityMapper.cs | 34 +++++++++++- Maple2.Model/Metadata/MapEntity/MapEntity.cs | 1 + Maple2.Model/Metadata/MapEntity/PatrolData.cs | 23 ++++++++ Maple2.Model/Metadata/MapEntity/SpawnPoint.cs | 8 +-- Maple2.Model/Metadata/MapEntityMetadata.cs | 1 + .../Manager/Field/FieldManager.State.cs | 8 ++- .../Manager/Field/FieldManager.cs | 2 +- .../Model/Field/Actor/FieldNpc.cs | 52 +++++++++++++++++-- start.bat | 4 ++ 10 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 Maple2.Model/Metadata/MapEntity/PatrolData.cs create mode 100644 start.bat diff --git a/Maple2.Database/Storage/Metadata/MapEntityStorage.cs b/Maple2.Database/Storage/Metadata/MapEntityStorage.cs index 098beb09..065f7cb1 100644 --- a/Maple2.Database/Storage/Metadata/MapEntityStorage.cs +++ b/Maple2.Database/Storage/Metadata/MapEntityStorage.cs @@ -48,6 +48,7 @@ public MapEntityStorage(MetadataContext context) : base(context, CACHE_SIZE) { } var interacts = new Dictionary(); var triggerModels = new Dictionary(); var triggers = new List(); + var patrols = new List(); foreach (MapEntity entity in Context.MapEntity.Where(entity => entity.XBlock == xblock)) { switch (entity.Block) { case Breakable breakable: @@ -107,6 +108,9 @@ public MapEntityStorage(MetadataContext context) : base(context, CACHE_SIZE) { } float height = Math.Abs(mapBounding.Position2.Z - mapBounding.Position1.Z); bounding = new Prism(box, baseHeight, height); break; + case MS2PatrolData patrol: + patrols.Add(patrol); + break; } } @@ -128,6 +132,7 @@ public MapEntityStorage(MetadataContext context) : base(context, CACHE_SIZE) { } Interacts = interacts, TriggerModels = triggerModels, Trigger = new TriggerStorage(triggers), + Patrols = patrols }; Cache.AddReplace(xblock, mapEntity); } diff --git a/Maple2.File.Ingest/Mapper/MapEntityMapper.cs b/Maple2.File.Ingest/Mapper/MapEntityMapper.cs index d1d51fca..daa3014c 100644 --- a/Maple2.File.Ingest/Mapper/MapEntityMapper.cs +++ b/Maple2.File.Ingest/Mapper/MapEntityMapper.cs @@ -25,6 +25,17 @@ public MapEntityMapper(MetadataContext db, M2dReader exportedReader) { private IEnumerable ParseMap(string xblock, IEnumerable entities) { IMS2Bounding? otherBounding = null; + Dictionary ms2WayPoints = new(); + foreach (var wayPoint in entities) { + switch (wayPoint) { + case IMS2WayPoint ms2WayPoint: + ms2WayPoints.Add(ms2WayPoint.EntityId, ms2WayPoint); + break; + default: + break; + } + } + foreach (IMapEntity entity in entities) { switch (entity) { case IMS2InteractObject interactObject: @@ -81,8 +92,9 @@ private IEnumerable ParseMap(string xblock, IEnumerable e }; continue; default: + string? patrolData = npcSpawn.PatrolData != "00000000-0000-0000-0000-000000000000" ? npcSpawn.PatrolData.Replace("-", string.Empty) : null; yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) { - Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, (int) npcSpawn.NpcCount, npcIds, (int) npcSpawn.RegenCheckTime) + Block = new SpawnPointNPC(npcSpawn.SpawnPointID, npcSpawn.Position, npcSpawn.Rotation, npcSpawn.IsVisible, npcSpawn.IsSpawnOnFieldCreate, npcSpawn.SpawnRadius, (int) npcSpawn.NpcCount, npcIds, (int) npcSpawn.RegenCheckTime, patrolData) }; continue; } @@ -179,8 +191,28 @@ private IEnumerable ParseMap(string xblock, IEnumerable e } continue; } + case IMS2PatrolData patrolData: + List wayPoints = new(); + foreach (KeyValuePair wayPointDict in patrolData.WayPoints) { + IMS2WayPoint? wayPoint = ms2WayPoints.GetValueOrDefault(wayPointDict.Value.Replace("-", string.Empty)); + if (wayPoint is null) { + continue; + } + + patrolData.ApproachAnims.TryGetValue(wayPointDict.Key, out string? approachAnimation); + patrolData.ArriveAnims.TryGetValue(wayPointDict.Key, out string? arriveAnimation); + patrolData.ArriveAnimsTime.TryGetValue(wayPointDict.Key, out uint arriveAnimationTime); + wayPoints.Add(new MS2WayPoint(wayPoint.EntityId, wayPoint.IsVisible, wayPoint.Position, wayPoint.Rotation, approachAnimation ?? "", arriveAnimation ?? "", (int) arriveAnimationTime)); + } + + + yield return new MapEntity(xblock, new Guid(entity.EntityId), entity.EntityName) { + Block = new MS2PatrolData(patrolData.EntityId, patrolData.EntityName, patrolData.IsAirWayPoint, (int) patrolData.PatrolSpeed, patrolData.IsLoop, wayPoints) + }; + continue; } } + } private MapEntity? ParseTrigger(string xblock, IMS2TriggerObject trigger) { diff --git a/Maple2.Model/Metadata/MapEntity/MapEntity.cs b/Maple2.Model/Metadata/MapEntity/MapEntity.cs index b89515b2..3b83ff57 100644 --- a/Maple2.Model/Metadata/MapEntity/MapEntity.cs +++ b/Maple2.Model/Metadata/MapEntity/MapEntity.cs @@ -55,4 +55,5 @@ public MapEntity(string xBlock, Guid guid, string name) { [JsonDerivedType(typeof(Ms2TriggerSound), 558345729)] [JsonDerivedType(typeof(TriggerModel), 1436346081)] [JsonDerivedType(typeof(Ms2Bounding), 1539875768)] +[JsonDerivedType(typeof(MS2PatrolData), 250722994)] public abstract record MapBlock; diff --git a/Maple2.Model/Metadata/MapEntity/PatrolData.cs b/Maple2.Model/Metadata/MapEntity/PatrolData.cs new file mode 100644 index 00000000..2d373f68 --- /dev/null +++ b/Maple2.Model/Metadata/MapEntity/PatrolData.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Numerics; + +namespace Maple2.Model.Metadata; + +public record MS2PatrolData( + string Uuid, + string Name, + bool IsAirWayPoint, + int PatrolSpeed, + bool IsLoop, + List WayPoints +) : MapBlock; + +public record MS2WayPoint( + string Id, + bool IsVisible, + Vector3 Position, + Vector3 Rotation, + string ApproachAnimation, + string ArriveAnimation, + int ArriveAnimationTime +); \ No newline at end of file diff --git a/Maple2.Model/Metadata/MapEntity/SpawnPoint.cs b/Maple2.Model/Metadata/MapEntity/SpawnPoint.cs index 19e5ed2a..ba13bf11 100644 --- a/Maple2.Model/Metadata/MapEntity/SpawnPoint.cs +++ b/Maple2.Model/Metadata/MapEntity/SpawnPoint.cs @@ -1,4 +1,5 @@ -using System.Numerics; +using System; +using System.Numerics; namespace Maple2.Model.Metadata; @@ -21,7 +22,8 @@ public record SpawnPointNPC( float SpawnRadius, int NpcCount, int[] NpcIds, - int RegenCheckTime + int RegenCheckTime, + string? PatrolData ) : SpawnPoint(Id, Position, Rotation, Visible); public record EventSpawnPointNPC( @@ -36,7 +38,7 @@ public record EventSpawnPointNPC( int RegenCheckTime, int LifeTime, string SpawnAnimation -) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcCount, NpcIds, RegenCheckTime); +) : SpawnPointNPC(Id, Position, Rotation, Visible, SpawnOnFieldCreate, SpawnRadius, NpcCount, NpcIds, RegenCheckTime, String.Empty); public record EventSpawnPointItem( int Id, diff --git a/Maple2.Model/Metadata/MapEntityMetadata.cs b/Maple2.Model/Metadata/MapEntityMetadata.cs index 5a9c2e11..4da7b4f0 100644 --- a/Maple2.Model/Metadata/MapEntityMetadata.cs +++ b/Maple2.Model/Metadata/MapEntityMetadata.cs @@ -27,6 +27,7 @@ public class MapEntityMetadata { public required IReadOnlyDictionary Interacts { get; init; } public required IReadOnlyDictionary TriggerModels { get; init; } public required ITriggerStorage Trigger { get; init; } + public required IReadOnlyList Patrols { get; init; } } public interface ITriggerStorage : IReadOnlyDictionary { diff --git a/Maple2.Server.Game/Manager/Field/FieldManager.State.cs b/Maple2.Server.Game/Manager/Field/FieldManager.State.cs index dfecb75e..b5122184 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager.State.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager.State.cs @@ -75,7 +75,7 @@ public FieldPlayer SpawnPlayer(GameSession session, Player player, int portalId return fieldPlayer; } - public FieldNpc? SpawnNpc(NpcMetadata npc, Vector3 position, Vector3 rotation, FieldMobSpawn? owner = null) { + public FieldNpc? SpawnNpc(NpcMetadata npc, Vector3 position, Vector3 rotation, FieldMobSpawn? owner = null, SpawnPointNPC? spawnPointNpc = null) { Agent? agent = Navigation.AddAgent(npc, position); AnimationMetadata? animation = NpcMetadata.GetAnimation(npc.Model); @@ -83,7 +83,7 @@ public FieldPlayer SpawnPlayer(GameSession session, Player player, int portalId if (agent is not null) { spawnPosition = Navigation.FromPosition(agent.getPosition()); } - var fieldNpc = new FieldNpc(this, NextLocalId(), agent, new Npc(npc, animation)) { + var fieldNpc = new FieldNpc(this, NextLocalId(), agent, new Npc(npc, animation), spawnPointNpc?.PatrolData) { Owner = owner, Position = spawnPosition, Rotation = rotation, @@ -99,6 +99,10 @@ public FieldPlayer SpawnPlayer(GameSession session, Player player, int portalId return fieldNpc; } + private FieldNpc? SpawnNpc(NpcMetadata npc, SpawnPointNPC spawnPointNpc) { + return SpawnNpc(npc, spawnPointNpc.Position, spawnPointNpc.Rotation, null, spawnPointNpc); + } + public FieldPet? SpawnPet(Item pet, Vector3 position, Vector3 rotation, FieldMobSpawn? owner = null, FieldPlayer? player = null) { if (!NpcMetadata.TryGet(pet.Metadata.Property.PetId, out NpcMetadata? npc)) { return null; diff --git a/Maple2.Server.Game/Manager/Field/FieldManager.cs b/Maple2.Server.Game/Manager/Field/FieldManager.cs index 4c32afff..e7f2e292 100644 --- a/Maple2.Server.Game/Manager/Field/FieldManager.cs +++ b/Maple2.Server.Game/Manager/Field/FieldManager.cs @@ -128,7 +128,7 @@ private void Init() { continue; } - SpawnNpc(npc, spawnPointNpc.Position, spawnPointNpc.Rotation); + SpawnNpc(npc, spawnPointNpc); } } } diff --git a/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs b/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs index 2e81e05a..bf2fd44a 100644 --- a/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs +++ b/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs @@ -1,12 +1,10 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; using Maple2.Model.Enum; -using Maple2.Database.Storage; using Maple2.Model.Game; using Maple2.Model.Metadata; using Maple2.PathEngine; using Maple2.Server.Game.Manager.Field; -using Maple2.Server.Game.Manager.Items; using Maple2.Server.Game.Model.Routine; using Maple2.Server.Game.Model.Skill; using Maple2.Server.Game.Model.State; @@ -14,9 +12,7 @@ using Maple2.Tools; using Maple2.Tools.Collision; using Maple2.Server.Game.Session; -using Google.Protobuf.WellKnownTypes; using Maple2.Server.Game.Model.Field.Actor.ActorState; -using Maple2.Server.Core.Packets; namespace Maple2.Server.Game.Model; @@ -77,6 +73,7 @@ public short SequenceId { public readonly AgentNavigation? Navigation; public readonly AnimationSequence IdleSequence; public readonly AnimationSequence? JumpSequence; + public readonly AnimationSequence? WalkSequence; private readonly WeightedSet defaultRoutines; public readonly AiState AiState; private NpcRoutine CurrentRoutine { get; set; } @@ -86,10 +83,13 @@ public short SequenceId { public override Stats Stats { get; } public int TargetId = 0; + private readonly MS2PatrolData? patrolData; + private int currentWaypointIndex = 0; - public FieldNpc(FieldManager field, int objectId, Agent? agent, Npc npc) : base(field, objectId, npc) { + public FieldNpc(FieldManager field, int objectId, Agent? agent, Npc npc, string? patrolDataUUID = null) : base(field, objectId, npc) { IdleSequence = npc.Animations.GetValueOrDefault("Idle_A") ?? new AnimationSequence(-1, 1f, null); JumpSequence = npc.Animations.GetValueOrDefault("Jump_A") ?? npc.Animations.GetValueOrDefault("Jump_B"); + WalkSequence = npc.Animations.GetValueOrDefault("Walk_A"); defaultRoutines = new WeightedSet(); foreach (NpcAction action in Value.Metadata.Action.Actions) { defaultRoutines.Add(action.Name, action.Probability); @@ -97,6 +97,10 @@ public FieldNpc(FieldManager field, int objectId, Agent? agent, Npc npc) : base( if (agent is not null) { Navigation = Field.Navigation.ForAgent(this, agent); + + if (patrolDataUUID is not null) { + patrolData = field.Entities.Patrols.FirstOrDefault(x => x.Uuid == patrolDataUUID); + } } CurrentRoutine = new WaitRoutine(this, -1, 1f); Stats = new Stats(npc.Metadata.Stat); @@ -157,6 +161,44 @@ public override void Update(long tickCount) { } private NpcRoutine NextRoutine() { + if (patrolData?.WayPoints.Count > 0 && Navigation is not null) { + MS2WayPoint waypoint = patrolData.WayPoints[currentWaypointIndex]; + + if (!string.IsNullOrEmpty(waypoint.ArriveAnimation) && CurrentRoutine is not AnimateRoutine) { + if (Value.Animations.TryGetValue(waypoint.ArriveAnimation, out AnimationSequence? arriveSequence)) { + return new AnimateRoutine(this, arriveSequence); + } + } + + currentWaypointIndex++; + + if (currentWaypointIndex >= patrolData.WayPoints.Count) { + currentWaypointIndex = 0; + } + + waypoint = patrolData.WayPoints[currentWaypointIndex]; + + if (Navigation.PathTo(waypoint.Position)) { + if (Value.Animations.TryGetValue(waypoint.ApproachAnimation, out AnimationSequence? patrolSequence)) { + if (waypoint.ApproachAnimation.StartsWith("Walk_")) { + return MoveRoutine.Walk(this, patrolSequence.Id); + } else if (waypoint.ApproachAnimation.StartsWith("Run_")) { + return MoveRoutine.Run(this, patrolSequence.Id); + } + } + if (WalkSequence is not null) { + return MoveRoutine.Walk(this, WalkSequence.Id); + } + + // Log.Logger.Warning("No walk sequence found for npc {NpcId} in patrol {PatrolId}", Value.Metadata.Id, patrolData.Uuid); + return new WaitRoutine(this, IdleSequence.Id, 1f); + } else { + // Log.Logger.Warning("Failed to path to waypoint index({WaypointIndex}) coord {Coord} for npc {NpcId} in patrol {PatrolId}", currentWaypointIndex, waypoint.Position, Value.Metadata.Name, patrolData.Uuid); + return new WaitRoutine(this, IdleSequence.Id, 1f); + } + } + + string routineName = defaultRoutines.Get(); if (!Value.Animations.TryGetValue(routineName, out AnimationSequence? sequence)) { Logger.Error("Invalid routine: {Routine} for npc {NpcId}", routineName, Value.Metadata.Id); diff --git a/start.bat b/start.bat new file mode 100644 index 00000000..0a9afb7f --- /dev/null +++ b/start.bat @@ -0,0 +1,4 @@ +@echo off + +wt -d "Maple2.Server.World" dotnet run ; nt -d "Maple2.Server.Login" dotnet run ; nt -d "Maple2.Server.Web" dotnet run ; nt -d "Maple2.Server.Game" dotnet run +