Skip to content

Commit

Permalink
feat: npc patrol data (#93)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
AngeloTadeucci and Zintixx authored May 10, 2024
1 parent 758ad19 commit e87040c
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 12 deletions.
5 changes: 5 additions & 0 deletions Maple2.Database/Storage/Metadata/MapEntityStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public MapEntityStorage(MetadataContext context) : base(context, CACHE_SIZE) { }
var interacts = new Dictionary<Guid, InteractObject>();
var triggerModels = new Dictionary<int, TriggerModel>();
var triggers = new List<Trigger>();
var patrols = new List<MS2PatrolData>();
foreach (MapEntity entity in Context.MapEntity.Where(entity => entity.XBlock == xblock)) {
switch (entity.Block) {
case Breakable breakable:
Expand Down Expand Up @@ -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;
}
}

Expand All @@ -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);
}
Expand Down
34 changes: 33 additions & 1 deletion Maple2.File.Ingest/Mapper/MapEntityMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ public MapEntityMapper(MetadataContext db, M2dReader exportedReader) {
private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> entities) {
IMS2Bounding? otherBounding = null;

Dictionary<string, IMS2WayPoint> 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:
Expand Down Expand Up @@ -81,8 +92,9 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> 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;
}
Expand Down Expand Up @@ -179,8 +191,28 @@ private IEnumerable<MapEntity> ParseMap(string xblock, IEnumerable<IMapEntity> e
}
continue;
}
case IMS2PatrolData patrolData:
List<MS2WayPoint> wayPoints = new();
foreach (KeyValuePair<string, string> 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) {
Expand Down
1 change: 1 addition & 0 deletions Maple2.Model/Metadata/MapEntity/MapEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
23 changes: 23 additions & 0 deletions Maple2.Model/Metadata/MapEntity/PatrolData.cs
Original file line number Diff line number Diff line change
@@ -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<MS2WayPoint> WayPoints
) : MapBlock;

public record MS2WayPoint(
string Id,
bool IsVisible,
Vector3 Position,
Vector3 Rotation,
string ApproachAnimation,
string ArriveAnimation,
int ArriveAnimationTime
);
8 changes: 5 additions & 3 deletions Maple2.Model/Metadata/MapEntity/SpawnPoint.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Numerics;
using System;
using System.Numerics;

namespace Maple2.Model.Metadata;

Expand All @@ -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(
Expand All @@ -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,
Expand Down
1 change: 1 addition & 0 deletions Maple2.Model/Metadata/MapEntityMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class MapEntityMetadata {
public required IReadOnlyDictionary<Guid, InteractObject> Interacts { get; init; }
public required IReadOnlyDictionary<int, TriggerModel> TriggerModels { get; init; }
public required ITriggerStorage Trigger { get; init; }
public required IReadOnlyList<MS2PatrolData> Patrols { get; init; }
}

public interface ITriggerStorage : IReadOnlyDictionary<int, Trigger> {
Expand Down
8 changes: 6 additions & 2 deletions Maple2.Server.Game/Manager/Field/FieldManager.State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,15 @@ 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);
Vector3 spawnPosition = position;
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,
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Maple2.Server.Game/Manager/Field/FieldManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ private void Init() {
continue;
}

SpawnNpc(npc, spawnPointNpc.Position, spawnPointNpc.Rotation);
SpawnNpc(npc, spawnPointNpc);
}
}
}
Expand Down
52 changes: 47 additions & 5 deletions Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
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;
using Maple2.Server.Game.Packets;
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;

Expand Down Expand Up @@ -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<string> defaultRoutines;
public readonly AiState AiState;
private NpcRoutine CurrentRoutine { get; set; }
Expand All @@ -86,17 +83,24 @@ 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<string>();
foreach (NpcAction action in Value.Metadata.Action.Actions) {
defaultRoutines.Add(action.Name, action.Probability);
}

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);
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions start.bat
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit e87040c

Please sign in to comment.