Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Maple2.Model/Metadata/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -921,6 +921,7 @@ public static class Constant {
public const string PaybackGuideUrl = "http://maplestory2.nexon.com/News/Events";
public const int DummyNpcMale = 2040998;
public const int DummyNpcFemale = 2040999;
public static int DummyNpc(Gender gender) => gender is Gender.Female ? DummyNpcFemale : DummyNpcMale;

#endregion

Expand Down
1 change: 1 addition & 0 deletions Maple2.Server.Game/Commands/DebugCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ public LogoutCommand(GameSession session) : base("logout", "Logs out.") {
}

private void Handle(InvocationContext ctx) {
ctx.Console.Out.WriteLine("Logging out...");
session.Disconnect();
}
}
Expand Down
6 changes: 2 additions & 4 deletions Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -552,9 +552,7 @@ public void MovePlayerAlongPath(string pathName) {
}

foreach (FieldPlayer player in Players.Values) {
int dummyNpcId = player.Value.Character.Gender is Gender.Male ? Constant.DummyNpcMale : Constant.DummyNpcFemale;

if (!NpcMetadata.TryGet(dummyNpcId, out NpcMetadata? npcMetadata)) {
if (!NpcMetadata.TryGet(Constant.DummyNpc(player.Value.Character.Gender), out NpcMetadata? npcMetadata)) {
continue;
}

Expand All @@ -566,7 +564,7 @@ public void MovePlayerAlongPath(string pathName) {
Broadcast(ProxyObjectPacket.AddNpc(dummyNpc));

dummyNpc.SetPatrolData(patrolData);
dummyNpc.MovementState.CleanupPatrolData();
dummyNpc.MovementState.CleanupPatrolData(player);
player.Session.Send(FollowNpcPacket.FollowNpc(dummyNpc.ObjectId));
}
}
Expand Down
3 changes: 2 additions & 1 deletion Maple2.Server.Game/Model/Enum/NpcTaskPriority.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
public enum NpcTaskPriority {
None,
Cleanup,
IdleAction, // wander, patrol, bore emote
IdleAction, // wander, patrol
Emote,
BattleStandby,
BattleWalk, // trace/runaway/move
BattleAction, // skill cast, jump
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,12 @@ public NpcTask TryMoveTargetDistance(IActor target, float distance, bool isBattl

public NpcTask TryStandby(IActor? target, bool isIdle, string sequence = "") {
NpcTaskPriority priority = isIdle ? NpcTaskPriority.IdleAction : NpcTaskPriority.BattleStandby;

return new NpcStandbyTask(actor.TaskState, this, sequence, priority, isIdle);
}

public NpcTask TryEmote(string sequenceName, bool isIdle, float duration = -1f) {
NpcTaskPriority priority = isIdle ? NpcTaskPriority.IdleAction : NpcTaskPriority.BattleStandby;
NpcTaskPriority priority = isIdle ? NpcTaskPriority.Emote : NpcTaskPriority.BattleStandby;
return new NpcEmoteTask(actor.TaskState, this, sequenceName, priority, isIdle, duration);
}

Expand Down Expand Up @@ -118,8 +119,8 @@ public NpcTask TryCastSkill(int id, short level, int faceTarget, Vector3 facePos
return new NpcSkillCastTask(actor.TaskState, this, id, level, faceTarget, facePos, uid);
}

public NpcTask CleanupPatrolData() {
return new NpcCleanupPatrolDataTask(actor.TaskState, this);
public NpcTask CleanupPatrolData(FieldPlayer player) {
return new NpcCleanupPatrolDataTask(player, actor.TaskState, this);
}

private void SetState(ActorState state) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,52 @@
using Maple2.Server.Game.Model.Enum;
using System.Numerics;
using Maple2.Model.Metadata;
using Maple2.Server.Game.Model.Enum;
using static Maple2.Server.Game.Model.ActorStateComponent.TaskState;

namespace Maple2.Server.Game.Model.ActorStateComponent;

public partial class MovementState {
public class NpcCleanupPatrolDataTask : NpcTask {
private readonly MovementState movement;
private readonly FieldPlayer player;
private readonly Vector3? lastPosition;
public override bool CancelOnInterrupt => false;

public NpcCleanupPatrolDataTask(TaskState taskState, MovementState movement) : base(taskState, NpcTaskPriority.Cleanup) {
public NpcCleanupPatrolDataTask(FieldPlayer player, TaskState taskState, MovementState movement) : base(taskState, NpcTaskPriority.Cleanup) {
this.movement = movement;
this.player = player;
MS2WayPoint? last = movement.actor.Patrol?.WayPoints.LastOrDefault();
if (last is null) {
return;
}
lastPosition = last.Position;
}

protected override void TaskResumed() {
if (movement.actor.Patrol is null) {
movement.actor.Field.RemoveNpc(movement.actor.ObjectId);
if (movement.actor.Patrol is not null) {
return;
}

movement.actor.Field.RemoveNpc(movement.actor.ObjectId);

if (lastPosition is null) {
return;
}

const float maxDistance = Constant.TalkableDistance * Constant.TalkableDistance;

// find nearest npc
FieldNpc? closestNpc = player.Field.Npcs.Values
.Where(npc => npc != movement.actor && Vector3.DistanceSquared(player.Position, npc.Position) < maxDistance)
.OrderBy(npc => Vector3.DistanceSquared(player.Position, npc.Position))
.FirstOrDefault();

if (closestNpc == null) {
return;
}

player.Transform.LookTo(Vector3.Normalize(closestNpc.Position - lastPosition.Value));
player.MoveToPosition(lastPosition.Value, player.Rotation);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ namespace Maple2.Server.Game.Model.ActorStateComponent;
public partial class MovementState {
public class NpcEmoteTask : NpcTask {
private readonly MovementState movement;
public string Sequence { get; init; } = string.Empty;
public bool IsIdle { get; init; }
public string Sequence { get; init; }
private bool IsIdle { get; init; }
public override bool CancelOnInterrupt => true;
public float Duration { get; init; }
private float Duration { get; init; }

public NpcEmoteTask(TaskState taskState, MovementState movement, string sequence, NpcTaskPriority priority, bool isIdle, float duration) : base(taskState, priority) {
this.movement = movement;
Expand All @@ -24,10 +24,15 @@ public NpcEmoteTask(TaskState taskState, MovementState movement, string sequence
protected override void TaskResumed() {
movement.Emote(this, Sequence, IsIdle, Duration);
}

protected override void TaskFinished(bool isCompleted) {
movement.emoteLimitTick = 0;
movement.Idle();
}

public override string ToString() {
return $"{GetType().Name} (Priority: {Priority}, Status: {Status}, Sequence: {Sequence})";
}
}

private void Emote(NpcTask task, string sequence, bool isIdle, float duration) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
using Maple2.Server.Game.Model.Enum;
using System.Diagnostics.CodeAnalysis;
using Maple2.Server.Game.Model.Enum;

namespace Maple2.Server.Game.Model.ActorStateComponent;

public class TaskState {
public FieldNpc Actor { get; init; }
private FieldNpc Actor { get; }

private PriorityQueue<NpcTask, NpcTaskPriority> taskQueue;
private NpcTask?[] runningTasks;
private bool isPendingStart = false;
private NpcTask? pendingTask = null;
private readonly PriorityQueue<NpcTask, NpcTaskPriority> taskQueue;
private readonly NpcTask?[] runningTasks;
private bool isPendingStart;
private NpcTask? pendingTask;

public TaskState(FieldNpc actor) {
Actor = actor;
Expand All @@ -22,25 +23,25 @@ public TaskState(FieldNpc actor) {
private NpcTaskStatus QueueTask(NpcTask task) {
NpcTask? queued = runningTasks[task.PriorityValue];

if (queued != null && !task.ShouldOverride(task)) {
if (queued != null && !task.ShouldOverride(queued)) {
return NpcTaskStatus.Cancelled;
}

if (taskQueue.TryPeek(out NpcTask? currentTask, out NpcTaskPriority priority)) {
bool cancelLowerPriority = currentTask.PriorityValue < task.PriorityValue && currentTask.CancelOnInterrupt;
bool cancelEqualPriority = currentTask.PriorityValue == task.PriorityValue;
queued?.Cancel();

if (cancelLowerPriority || cancelEqualPriority) {
if (taskQueue.TryPeek(out NpcTask? currentTask, out NpcTaskPriority priority) && currentTask.PriorityValue < task.PriorityValue) {
if (currentTask.CancelOnInterrupt) {
currentTask.Cancel();
} else if (currentTask.PriorityValue < task.PriorityValue) {
} else {
currentTask.Pause();
}
}

runningTasks[task.PriorityValue] = task;
taskQueue.Enqueue(task, task.Priority);

if (taskQueue.Peek() == task) {
NpcTask npcTask = taskQueue.Peek();
if (npcTask == task) {
isPendingStart = true;
pendingTask = task;

Expand All @@ -57,19 +58,28 @@ private void FinishTask(NpcTask task) {

runningTasks[task.PriorityValue] = null;

if (taskQueue.TryPeek(out NpcTask? currentTask, out NpcTaskPriority priority) && currentTask == task) {
taskQueue.Dequeue();
if (!taskQueue.TryPeek(out NpcTask? currentTask, out _) || currentTask != task) {
return;
}

if (taskQueue.TryPeek(out currentTask, out priority)) {
isPendingStart = true;
pendingTask = currentTask;
}
taskQueue.Dequeue();

if (!taskQueue.TryPeek(out currentTask, out _)) {
return;
}
isPendingStart = true;
pendingTask = currentTask;
}

public void Update(long tickCount) {
if (isPendingStart) {
if (taskQueue.TryPeek(out NpcTask? task, out NpcTaskPriority priority) && task == pendingTask) {
NpcTask? task;

while (taskQueue.TryPeek(out task, out _) && task.Status == NpcTaskStatus.Cancelled) {
taskQueue.Dequeue();
}

if (taskQueue.TryPeek(out task, out _) && task == pendingTask) {
task.Resume();
}
}
Expand All @@ -79,22 +89,15 @@ public void Update(long tickCount) {
}

public abstract class NpcTask {
protected TaskState queue { get; private set; }

public NpcTaskPriority Priority { get; private init; }
public int PriorityValue { get => (int) Priority; }
public NpcTaskStatus Status {
get => status;
private set {
status = value;
}
}
public bool IsDone { get => Status == NpcTaskStatus.Cancelled || Status == NpcTaskStatus.Complete; }
private TaskState Queue { get; }
public NpcTaskPriority Priority { get; }
public int PriorityValue => (int) Priority;
public NpcTaskStatus Status { get; private set; }
public bool IsDone => Status is NpcTaskStatus.Cancelled or NpcTaskStatus.Complete;
public virtual bool CancelOnInterrupt { get; }
private NpcTaskStatus status;

public NpcTask(TaskState queue, NpcTaskPriority priority) {
this.queue = queue;
protected NpcTask(TaskState queue, NpcTaskPriority priority) {
this.Queue = queue;
Priority = priority;

Status = queue.QueueTask(this);
Expand Down Expand Up @@ -131,7 +134,7 @@ public void Finish(bool isCompleted) {

Status = isCompleted ? NpcTaskStatus.Complete : NpcTaskStatus.Cancelled;

queue.FinishTask(this);
Queue.FinishTask(this);
TaskFinished(isCompleted);
}

Expand All @@ -144,5 +147,9 @@ public void Cancel() {
public void Completed() {
Finish(true);
}

public override string ToString() {
return $"{GetType().Name} (Priority: {Priority}, Status: {Status})";
}
}
}
9 changes: 5 additions & 4 deletions Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ public short SequenceId {
public MS2PatrolData? Patrol { get; private set; }
private int currentWaypointIndex;

private bool hasBeenBattling = false;
private bool hasBeenBattling;
private NpcTask? idleTask;
private long idleTaskLimitTick = 0;
private long idleTaskLimitTick;

public readonly Dictionary<string, int> AiExtraData = new();

Expand Down Expand Up @@ -170,7 +170,7 @@ public override void Update(long tickCount) {
if (tickCount >= nextDebugPacket && playersListeningToDebugNow && debugMessages.Count > 0) {
sentDebugPacket = true;

Field.BroadcastAiMessage(CinematicPacket.BalloonTalk(false, ObjectId, String.Join("", debugMessages.ToArray()), 2500, 0));
Field.BroadcastAiMessage(CinematicPacket.BalloonTalk(false, ObjectId, string.Join("", debugMessages.ToArray()), 2500, 0));
}

if (sentDebugPacket || tickCount >= nextDebugPacket) {
Expand Down Expand Up @@ -206,6 +206,8 @@ private void DoIdleBehavior(long tickCount) {

if (idleTask is MovementState.NpcStandbyTask && idleTaskLimitTick == 0) {
idleTaskLimitTick = tickCount + 1000;
} else if (idleTask is not MovementState.NpcStandbyTask && idleTaskLimitTick != 0) {
idleTaskLimitTick = 0;
}

bool hitLimit = idleTaskLimitTick != 0 && tickCount >= idleTaskLimitTick;
Expand Down Expand Up @@ -351,7 +353,6 @@ public void DropLoot(FieldPlayer firstPlayer) {
}
}


public override SkillRecord? CastSkill(int id, short level, long uid = 0, byte motionPoint = 0) {
if (!Field.SkillMetadata.TryGet(id, level, out SkillMetadata? metadata) || metadata.Data.Motions.Length <= motionPoint) {
Logger.Error("Invalid skill use: {SkillId},{Level},{motionPoint}", id, level, motionPoint);
Expand Down
5 changes: 0 additions & 5 deletions Maple2.Server.Game/Session/GameSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -723,11 +723,6 @@ protected override void Dispose(bool disposing) {
State = SessionState.Disconnected;
Complete();
} finally {
#if !DEBUG
if (Player.Value.Character.ReturnMapId != 0) {
Player.Value.Character.MapId = Player.Value.Character.ReturnMapId;
}
#endif
Guild.Dispose();
Buddy.Dispose();
Party.Dispose();
Expand Down
5 changes: 5 additions & 0 deletions Maple2.Tools/VectorMath/Transform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ public void LookTo(Vector3 direction, bool snapToGroundPlane = true) {
}

public void LookTo(Vector3 direction, Vector3 up, bool snapToGroundPlane = true) {
direction = Vector3.Normalize(direction);
up = Vector3.Normalize(up);

if (snapToGroundPlane) {
direction = Vector3.Normalize(direction - Vector3.Dot(direction, up) * up); // plane projection formula

Expand All @@ -147,6 +150,8 @@ public void LookTo(Vector3 direction, Vector3 up, bool snapToGroundPlane = true)
}

Vector3 right = Vector3.Cross(direction, up);
up = Vector3.Cross(right, direction);

float scale = Scale;

RightAxis = scale * right;
Expand Down