From 70b56fb3077076ef9113ee290a7ec3fd1003a272 Mon Sep 17 00:00:00 2001
From: Zin <62830952+Zintixx@users.noreply.github.com>
Date: Wed, 9 Apr 2025 16:50:43 -0700
Subject: [PATCH 1/9] Aditional Buff & Skill BeginConditions
---
.../ActorStateComponent/AnimationState.cs | 352 ++++++++++++++++++
Maple2.File.Ingest/Maple2.File.Ingest.csproj | 2 +-
Maple2.File.Ingest/Mapper/AnimationMapper.cs | 14 +-
Maple2.File.Ingest/MapperExtensions.cs | 77 +++-
Maple2.Model/Enum/CompareType.cs | 5 +
Maple2.Model/Game/Npc/Npc.cs | 4 +-
Maple2.Model/Metadata/AnimationMetadata.cs | 4 +-
Maple2.Model/Metadata/BeginCondition.cs | 22 +-
Maple2.Server.Game/Model/Field/Actor/Actor.cs | 22 +-
.../ActorStateComponent/AnimationRecord.cs | 61 +++
.../ActorStateComponent/AnimationState.cs | 336 +++++++++--------
.../ActorStateComponent/MovementState.cs | 2 +-
.../MovementStateStates/MovementState.Walk.cs | 2 +-
.../MovementState.SkillCastTask.cs | 2 +-
.../Model/Field/Actor/FieldNpc.cs | 20 +-
.../Model/Field/Actor/FieldPlayer.cs | 2 +-
.../Field/Actor/Routine/AnimateRoutine.cs | 4 +-
.../Model/Field/Actor/Routine/JumpRoutine.cs | 4 +-
.../Model/Field/Actor/Routine/MoveRoutine.cs | 4 +-
.../Model/Field/Actor/Routine/NpcRoutine.cs | 2 +-
Maple2.Server.Game/Model/Skill/SkillQueue.cs | 4 +
.../PacketHandlers/SkillHandler.cs | 2 +-
Maple2.Server.Game/Util/SkillUtils.cs | 69 ++++
23 files changed, 818 insertions(+), 198 deletions(-)
create mode 100644 .github/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
create mode 100644 Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs
diff --git a/.github/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs b/.github/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
new file mode 100644
index 000000000..5a340463b
--- /dev/null
+++ b/.github/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
@@ -0,0 +1,352 @@
+using Maple2.Model.Metadata;
+using Maple2.Server.Core.Packets;
+using Maple2.Server.Game.Model.Enum;
+
+namespace Maple2.Server.Game.Model.ActorStateComponent;
+
+///
+/// Manages animation sequences for actors in the game.
+///
+public class AnimationState {
+ private readonly IActor actor;
+ private AnimationRecord? current;
+ private AnimationRecord? queued;
+
+ public readonly AnimationMetadata? RigMetadata;
+ public AnimationSequenceMetadata? PlayingSequence => current?.Sequence;
+ public short IdleSequenceId { get; init; }
+ public float SequenceSpeed => current?.Speed ?? 1.0f;
+
+ private bool isHandlingKeyframe;
+ private bool IsPlayerAnimation => actor is FieldPlayer;
+ public float MoveSpeed { get; set; } = 1;
+ public float AttackSpeed { get; set; } = 1;
+ private float lastSequenceTime;
+ private float sequenceEnd;
+ private LoopData sequenceLoop;
+ private long lastTick;
+ private long sequenceEndTick;
+ private long sequenceLoopEndTick;
+
+ private bool debugPrintAnimations;
+ public bool DebugPrintAnimations {
+ get { return debugPrintAnimations; }
+ set {
+ if (actor is FieldPlayer) {
+ debugPrintAnimations = value;
+ }
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the AnimationState class.
+ ///
+ /// The actor this animation state belongs to
+ /// The model name to load animations for
+ public AnimationState(IActor actor, string modelName) {
+ this.actor = actor;
+
+ RigMetadata = actor.NpcMetadata?.GetAnimation(modelName);
+ MoveSpeed = 1;
+ AttackSpeed = 1;
+ sequenceLoop = new LoopData(0, 0);
+
+ if (RigMetadata is null) {
+ IdleSequenceId = 0;
+ return;
+ }
+
+ string idleName = "Idle_A";
+ if (actor is FieldNpc npc) {
+ idleName = npc.Value.Metadata.Action.Actions.FirstOrDefault()?.Name ?? idleName;
+ IdleSequenceId = RigMetadata.Sequences.FirstOrDefault(sequence => sequence.Key.Contains(idleName)).Value.Id;
+ return;
+ }
+ IdleSequenceId = RigMetadata.Sequences.FirstOrDefault(sequence => sequence.Key == idleName).Value.Id;
+ }
+
+ ///
+ /// Resets the current animation sequence.
+ ///
+ private void ResetSequence() {
+ if (current?.Sequence != null && actor is FieldNpc npc) {
+ npc.SendControl = true;
+ }
+ current = null;
+ lastSequenceTime = 0;
+ sequenceLoop = new LoopData(0, 0);
+ lastTick = 0;
+ sequenceEnd = 0;
+ }
+
+ ///
+ /// Attempts to play an animation sequence with the specified name, speed, and type.
+ ///
+ /// The name of the animation sequence to play
+ /// The speed at which to play the animation
+ /// The type of animation (Move, Skill, or Misc)
+ /// True if the sequence was found and started (or queued), false otherwise
+ public bool TryPlaySequence(string name, float speed, AnimationType type) {
+ // Can't play animations without metadata
+ if (RigMetadata is null || !RigMetadata.Sequences.TryGetValue(name, out AnimationSequenceMetadata? sequence)) {
+ DebugPrint($"Attempt to play nonexistent sequence '{name}' at x{speed} speed, previous: '{PlayingSequence?.Name ?? "none"}' x{SequenceSpeed}");
+ ResetSequence();
+ return false;
+ }
+
+ // If we're currently processing a keyframe event, queue this sequence for later
+ if (isHandlingKeyframe) {
+ queued = new AnimationRecord(sequence, speed, type);
+ return true;
+ }
+
+ // Play the sequence immediately
+ PlaySequence(sequence, speed, type);
+ return true;
+ }
+
+ ///
+ /// Plays the specified animation sequence.
+ ///
+ /// The animation sequence to play
+ /// The speed at which to play the animation
+ /// The type of animation (Move, Skill, or Misc)
+ private void PlaySequence(AnimationSequenceMetadata sequenceMetadata, float speed, AnimationType type) {
+ // For NPCs, set SendControl flag when changing sequences
+ if (current?.Sequence != sequenceMetadata && actor is FieldNpc npc) {
+ npc.SendControl = true;
+ }
+
+ // Log the sequence change
+ DebugPrint($"Playing sequence '{sequenceMetadata.Name}' at x{speed} speed, previous: '{PlayingSequence?.Name ?? "none"}' x{SequenceSpeed}");
+
+ // Reset current sequence state
+ ResetSequence();
+
+ // Set the new sequence properties
+ current = new AnimationRecord(sequenceMetadata, speed, type);
+
+ // Start tracking from current tick
+ lastTick = actor.Field.FieldTick;
+ }
+
+ ///
+ /// Cancels the currently playing animation sequence.
+ ///
+ public void CancelSequence() {
+ // Log the cancellation if a sequence is playing
+ if (PlayingSequence is not null) {
+ DebugPrint($"Canceled playing sequence: '{PlayingSequence.Name}' x{SequenceSpeed}");
+ }
+
+ // If we're processing a keyframe event, queue the reset for later
+ if (isHandlingKeyframe) {
+ queued = null;
+ return;
+ }
+
+ // Reset the sequence state
+ ResetSequence();
+ }
+
+ ///
+ /// Updates the animation state based on the current tick count.
+ ///
+ /// The current server tick count
+ public void Update(long tickCount) {
+ // Skip update if no animation metadata is available
+ if (RigMetadata is null) {
+ return;
+ }
+
+ // Reset if no valid sequence is playing
+ if (PlayingSequence?.Keys.Count == 0) {
+ ResetSequence();
+ return;
+ }
+
+ // Calculate the current sequence time
+ float sequenceSpeedModifier = current?.Type switch {
+ AnimationType.Move => MoveSpeed,
+ AnimationType.Skill => AttackSpeed,
+ _ => 1,
+ };
+
+ long lastServerTick = lastTick == 0 ? tickCount : lastTick;
+ float speed = SequenceSpeed * sequenceSpeedModifier / 1000;
+ float delta = (float)(tickCount - lastServerTick) * speed;
+ float sequenceTime = lastSequenceTime + delta;
+
+ // Process keyframe events
+ if (PlayingSequence?.Keys != null) {
+ foreach (AnimationKey key in PlayingSequence.Keys) {
+ if (HasHitKeyframe(sequenceTime, key)) {
+ HitKeyframe(sequenceTime, key, speed);
+ }
+ }
+ }
+
+ // Handle sequence looping
+ if (current?.IsLooping == true && sequenceLoop.end != 0 && sequenceTime > sequenceLoop.end) {
+ if (!IsPlayerAnimation || tickCount <= sequenceLoopEndTick + Constant.ClientGraceTimeTick) {
+ if (current.LoopOnlyOnce) {
+ current.IsLooping = false;
+ current.LoopOnlyOnce = false;
+ }
+
+ sequenceTime -= sequenceLoop.end - sequenceLoop.start;
+ lastSequenceTime = sequenceTime - Math.Max(delta, sequenceTime - sequenceLoop.end + 0.001f);
+
+ // Play all keyframe events from loopstart to current
+ if (PlayingSequence?.Keys != null) {
+ foreach (AnimationKey key in PlayingSequence.Keys) {
+ if (HasHitKeyframe(sequenceTime, key)) {
+ HitKeyframe(sequenceTime, key, speed);
+ }
+ }
+ }
+ }
+ }
+
+ // Check for sequence end
+ if (sequenceEnd != 0 && sequenceTime > sequenceEnd) {
+ if (!IsPlayerAnimation || tickCount <= sequenceEndTick + Constant.ClientGraceTimeTick) {
+ ResetSequence();
+ }
+ }
+
+ // Update timing state
+ lastTick = tickCount;
+ lastSequenceTime = sequenceTime;
+ isHandlingKeyframe = false;
+
+ // Process queued actions
+ if (queued != null) {
+ PlaySequence(queued.Sequence!, queued.Speed, queued.Type);
+ queued = null;
+ }
+ }
+
+ ///
+ /// Sets whether the current sequence should loop.
+ ///
+ /// Whether the sequence should loop
+ /// Whether the sequence should only loop once
+ public void SetLoopSequence(bool shouldLoop, bool loopOnlyOnce) {
+ if (current is null) {
+ return;
+ }
+
+ current.IsLooping = shouldLoop;
+ current.LoopOnlyOnce = loopOnlyOnce;
+ }
+
+ ///
+ /// Determines if a keyframe has been hit in the current update.
+ ///
+ /// The current sequence time
+ /// The keyframe to check
+ /// True if the keyframe has been hit, false otherwise
+ private bool HasHitKeyframe(float sequenceTime, AnimationKey key) {
+ bool keyBeforeLoop = !current?.IsLooping ?? true || sequenceLoop.end == 0 || key.Time <= sequenceLoop.end + 0.001f;
+ bool hitKeySinceLastTick = key.Time > lastSequenceTime && key.Time <= sequenceTime;
+
+ return keyBeforeLoop && hitKeySinceLastTick;
+ }
+
+ ///
+ /// Gets the normalized time within a segment defined by two keyframes.
+ ///
+ /// The name of the first keyframe
+ /// The name of the second keyframe
+ /// A value between 0 and 1 representing the position within the segment, or -1 if not in the segment
+ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
+ if (PlayingSequence is null) {
+ return -1;
+ }
+
+ float keyframe1Time = -1;
+ float keyframe2Time = -1;
+
+ foreach (AnimationKey key in PlayingSequence.Keys) {
+ if (key.Name == keyframe1) {
+ keyframe1Time = key.Time;
+ }
+
+ if (key.Name == keyframe2) {
+ keyframe2Time = key.Time;
+ break;
+ }
+ }
+
+ // Segment doesn't exist or is malformed
+ if (keyframe1Time == -1 || keyframe2Time == -1 || keyframe1Time > keyframe2Time) {
+ return -1;
+ }
+
+ // Current time out of segment
+ if (lastSequenceTime < keyframe1Time || lastSequenceTime >= keyframe2Time) {
+ return -1;
+ }
+
+ if (keyframe1Time == keyframe2Time) {
+ // Can only be in the segment, and at the end, or not in the segment
+ return lastSequenceTime == keyframe1Time ? 1 : -1;
+ }
+
+ return (lastSequenceTime - keyframe1Time) / (keyframe2Time - keyframe1Time);
+ }
+
+ ///
+ /// Processes a keyframe event.
+ ///
+ /// The current sequence time
+ /// The keyframe that was hit
+ /// The current animation speed
+ private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
+ isHandlingKeyframe = true;
+
+ DebugPrint($"Sequence '{PlayingSequence!.Name}' keyframe event '{key.Name}'");
+
+ actor.KeyframeEvent(key.Name);
+
+ switch (key.Name) {
+ case "loopstart":
+ sequenceLoop = new LoopData(key.Time, 0);
+ break;
+ case "loopend":
+ sequenceLoop = new LoopData(sequenceLoop.start, key.Time);
+ sequenceLoopEndTick = actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
+ break;
+ case "end":
+ sequenceEnd = key.Time;
+ sequenceEndTick = actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
+ break;
+ default:
+ break;
+ }
+ }
+
+ ///
+ /// Prints a debug message if debug printing is enabled.
+ ///
+ /// The message to print
+ private void DebugPrint(string message) {
+ if (debugPrintAnimations && actor is FieldPlayer player) {
+ player.Session.Send(NoticePacket.Message(message));
+ }
+ }
+
+ ///
+ /// Represents a loop section in an animation sequence
+ ///
+ private struct LoopData {
+ public float start;
+ public float end;
+
+ public LoopData(float start, float end) {
+ this.start = start;
+ this.end = end;
+ }
+ }
+}
diff --git a/Maple2.File.Ingest/Maple2.File.Ingest.csproj b/Maple2.File.Ingest/Maple2.File.Ingest.csproj
index 40f8c88f0..346a1d178 100644
--- a/Maple2.File.Ingest/Maple2.File.Ingest.csproj
+++ b/Maple2.File.Ingest/Maple2.File.Ingest.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/Maple2.File.Ingest/Mapper/AnimationMapper.cs b/Maple2.File.Ingest/Mapper/AnimationMapper.cs
index 0689340fb..2160e4573 100644
--- a/Maple2.File.Ingest/Mapper/AnimationMapper.cs
+++ b/Maple2.File.Ingest/Mapper/AnimationMapper.cs
@@ -15,24 +15,22 @@ public AnimationMapper(M2dReader xmlReader) {
protected override IEnumerable Map() {
foreach (AnimationData data in parser.Parse()) {
foreach (KeyFrameMotion kfm in data.kfm) {
- IEnumerable<(string Name, AnimationSequence Sequence)> sequences = kfm.seq.Select(sequence => {
+ IEnumerable<(string Name, AnimationSequenceMetadata Sequence)> sequences = kfm.seq.Select(sequence => {
List keys = sequence.key.Select(key => new AnimationKey(key.name, (float) key.time)).ToList();
return (sequence.name,
- new AnimationSequence(
+ new AnimationSequenceMetadata(
Name: sequence.name,
Id: (short) sequence.id,
- Time: (float) (sequence.key.FirstOrDefault(key => key.name == "end")?.time ?? default), keys)
+ Time: (float) (sequence.key.FirstOrDefault(key => key.name == "end")?.time ?? 0), keys)
);
});
- var lookup = new Dictionary();
- foreach ((string name, AnimationSequence sequence) in sequences) {
- if (lookup.ContainsKey(name)) {
+ var lookup = new Dictionary();
+ foreach ((string name, AnimationSequenceMetadata sequence) in sequences) {
+ if (!lookup.TryAdd(name, sequence)) {
Console.WriteLine($"Ignore Duplicate: {name} for {kfm.name}");
- continue;
}
- lookup.Add(name, sequence);
}
yield return new AnimationMetadata(kfm.name, lookup);
diff --git a/Maple2.File.Ingest/MapperExtensions.cs b/Maple2.File.Ingest/MapperExtensions.cs
index 292d1aabe..5ac806e08 100644
--- a/Maple2.File.Ingest/MapperExtensions.cs
+++ b/Maple2.File.Ingest/MapperExtensions.cs
@@ -1,4 +1,6 @@
-using System.Diagnostics;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Reflection;
using Maple2.File.Ingest.Utils;
using Maple2.File.Parser.Xml;
using Maple2.File.Parser.Xml.Common;
@@ -317,17 +319,36 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
}
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
+ List dungeonGroupType = [];
+ foreach (Parser.Xml.Skill.BeginCondition.DungeonRoomGroupTypes type in beginCondition.requireDungeonRoomGroupTypes) {
+ if (Enum.TryParse(type.type, true, out DungeonGroupType groupType)) {
+ dungeonGroupType.Add(groupType);
+ }
+ }
return new BeginCondition(
Level: beginCondition.level,
Gender: (Gender) beginCondition.gender,
Mesos: beginCondition.money,
Stat: beginCondition.stat.ToDictionary(),
+ Maps: beginCondition.requireMapCodes.Select(mapCodes => mapCodes.code).ToArray(),
+ MapTypes: beginCondition.requireMapCategoryCodes.Select(mapType => (MapType) mapType.code).ToArray(),
+ Continents: beginCondition.requireMapContinentCodes.Select(continent => (Continent) continent.code).ToArray(),
+ ActiveSkill: beginCondition.requireSkillCodes.Select(skill => skill.code).ToArray(),
JobCode: beginCondition.job.Select(job => (JobCode) job.code).ToArray(),
Probability: beginCondition.probability,
CooldownTime: beginCondition.cooldownTime,
+ DurationWithoutMoving: (int) TimeSpan.FromSeconds(beginCondition.requireDurationWithoutMove).TotalMilliseconds,
+ DurationWithoutDamage: (int) TimeSpan.FromSeconds(beginCondition.requireDurationWithoutDamage).TotalMilliseconds,
OnlyShadowWorld: beginCondition.onlyShadowWorld || beginCondition.isShadowWorld,
OnlyFlyableMap: beginCondition.onlyFlyableMap,
+ OnlySurvival: beginCondition.allowMapleSurvival,
AllowDead: beginCondition.allowDeadState,
+ AllowOnBattleMount: beginCondition.allowBattleRidingState,
+ OnlyOnBattleMount: beginCondition.onlyBattleRidingState,
+ DungeonGroupType: beginCondition.requireDungeonRoomGroupTypes
+ .Where(type => Enum.TryParse(type.type, true, out DungeonGroupType _))
+ .Select(type => Enum.Parse(type.type, true))
+ .ToArray(),
Weapon: beginCondition.weapon.Select(weapon => new BeginConditionWeapon(
new ItemType(1, (byte) weapon.lh),
new ItemType(1, (byte) weapon.rh))).ToArray(),
@@ -337,7 +358,7 @@ public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCond
}
// We use this default to avoid writing useless checks
- private static readonly BeginConditionTarget DefaultBeginConditionTarget = new(Array.Empty(), null);
+ private static readonly BeginConditionTarget DefaultBeginConditionTarget = new([], null, [], [], [], new Dictionary(), [], []);
private static BeginConditionTarget? Convert(SubConditionTarget? target) {
if (target == null) {
return null;
@@ -345,13 +366,39 @@ public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCond
var result = new BeginConditionTarget(
Buff: ParseBuffs(target),
- Event: ParseEvent(target));
+ Event: ParseEvent(target),
+ Stat: ParseStat(target),
+ States: target.requireStates
+ .Select(state => Enum.GetValues()
+ .FirstOrDefault(enumValue =>
+ enumValue.GetType()
+ .GetField(enumValue.ToString())
+ ?.GetCustomAttribute()
+ ?.Description == state))
+ .Where(state => state != ActorState.None)
+ .ToArray(),
+ SubStates: target.requireSubStates
+ .Select(state => Enum.GetValues()
+ .FirstOrDefault(enumValue =>
+ enumValue.GetType()
+ .GetField(enumValue.ToString())
+ ?.GetCustomAttribute()
+ ?.Description == state))
+ .Where(state => state != ActorSubState.None)
+ .ToArray(),
+ Masteries: target.requireMasteryTypes
+ .Where(type => Enum.TryParse(type, true, out MasteryType _))
+ .Select(type => Enum.Parse(type, true))
+ .Zip(target.requireMasteryValues, (type, value) => (Type: type, Value: value))
+ .ToDictionary(pair => pair.Type, pair => pair.Value),
+ NpcIds: target.NpcIDs,
+ HasNotBuffIds: target.hasNotBuffID);
return DefaultBeginConditionTarget.Equals(result) ? null : result;
BeginConditionTarget.HasBuff[] ParseBuffs(SubConditionTarget data) {
if (data.hasBuffID.Length == 0 || data.hasBuffID[0] == 0) {
- return Array.Empty();
+ return [];
}
var hasBuff = new BeginConditionTarget.HasBuff[data.hasBuffID.Length];
@@ -383,6 +430,28 @@ BeginConditionTarget.HasBuff[] ParseBuffs(SubConditionTarget data) {
SkillIds: data.eventSkillID,
BuffIds: data.eventEffectID);
}
+
+ BeginConditionTarget.BeginConditionStat[] ParseStat(SubConditionTarget data) {
+ if (data.compareStat.Count == 0) {
+ return [];
+ }
+
+ var stats = new BeginConditionTarget.BeginConditionStat[data.compareStat.Count];
+ for (int i = 0; i < stats.Length; i++) {
+ foreach (BasicAttribute attribute in Enum.GetValues()) {
+ float value = data.compareStat[i][(byte) attribute];
+ if (value != default) {
+ stats[i] = new BeginConditionTarget.BeginConditionStat(
+ Attribute: attribute,
+ Value: value,
+ Compare: data.compareStat.Count > i ? Enum.Parse(data.compareStat[i].func) : CompareType.Equals,
+ ValueType: (CompareStatValueType) data.compareStat[i].type);
+ break;
+ }
+ }
+ }
+ return stats;
+ }
}
public static Dictionary> ToDictionary(this IEnumerable entries) {
diff --git a/Maple2.Model/Enum/CompareType.cs b/Maple2.Model/Enum/CompareType.cs
index cdfeeedea..4ae5d4ba3 100644
--- a/Maple2.Model/Enum/CompareType.cs
+++ b/Maple2.Model/Enum/CompareType.cs
@@ -7,3 +7,8 @@ public enum CompareType {
Greater = 3,
GreaterEquals = 4,
}
+
+public enum CompareStatValueType {
+ CurrentPercentage = 0,
+ TotalValue = 1,
+}
diff --git a/Maple2.Model/Game/Npc/Npc.cs b/Maple2.Model/Game/Npc/Npc.cs
index 061b33f09..9a9608cd7 100644
--- a/Maple2.Model/Game/Npc/Npc.cs
+++ b/Maple2.Model/Game/Npc/Npc.cs
@@ -5,7 +5,7 @@ namespace Maple2.Model.Game;
public class Npc {
public readonly NpcMetadata Metadata;
- public readonly IReadOnlyDictionary Animations;
+ public readonly IReadOnlyDictionary Animations;
public int Id => Metadata.Id;
@@ -13,6 +13,6 @@ public class Npc {
public Npc(NpcMetadata metadata, AnimationMetadata? animation) {
Metadata = metadata;
- Animations = animation?.Sequences ?? new Dictionary();
+ Animations = animation?.Sequences ?? new Dictionary();
}
}
diff --git a/Maple2.Model/Metadata/AnimationMetadata.cs b/Maple2.Model/Metadata/AnimationMetadata.cs
index 03e216943..b5a36a0d8 100644
--- a/Maple2.Model/Metadata/AnimationMetadata.cs
+++ b/Maple2.Model/Metadata/AnimationMetadata.cs
@@ -2,8 +2,8 @@
namespace Maple2.Model.Metadata;
-public record AnimationMetadata(string Model, IReadOnlyDictionary Sequences);
+public record AnimationMetadata(string Model, IReadOnlyDictionary Sequences);
-public record AnimationSequence(string Name, short Id, float Time, List? Keys);
+public record AnimationSequenceMetadata(string Name, short Id, float Time, List Keys);
public record AnimationKey(string Name, float Time);
diff --git a/Maple2.Model/Metadata/BeginCondition.cs b/Maple2.Model/Metadata/BeginCondition.cs
index e6a212a62..e8ae2967f 100644
--- a/Maple2.Model/Metadata/BeginCondition.cs
+++ b/Maple2.Model/Metadata/BeginCondition.cs
@@ -11,10 +11,20 @@ public record BeginCondition(
JobCode[] JobCode,
float Probability,
float CooldownTime,
+ int DurationWithoutDamage,
+ int DurationWithoutMoving,
bool OnlyShadowWorld,
bool OnlyFlyableMap,
+ bool OnlySurvival,
bool AllowDead,
+ bool AllowOnBattleMount,
+ bool OnlyOnBattleMount,
+ DungeonGroupType[] DungeonGroupType,
IReadOnlyDictionary Stat,
+ int[] Maps,
+ MapType[] MapTypes,
+ Continent[] Continents,
+ int[] ActiveSkill,
BeginConditionWeapon[]? Weapon,
BeginConditionTarget? Target,
BeginConditionTarget? Owner,
@@ -26,7 +36,14 @@ public record BeginConditionWeapon(
public record BeginConditionTarget(
BeginConditionTarget.HasBuff[] Buff,
- BeginConditionTarget.EventCondition? Event
+ BeginConditionTarget.EventCondition? Event,
+ BeginConditionTarget.BeginConditionStat[] Stat,
+ ActorState[] States,
+ ActorSubState[] SubStates,
+ IReadOnlyDictionary Masteries,
+ //string[] NpcTags // not used?
+ int[] NpcIds,
+ int[] HasNotBuffIds
) {
public record HasBuff(int Id, short Level, bool Owned, int Count, CompareType Compare);
@@ -35,4 +52,7 @@ public record HasBuff(int Id, short Level, bool Owned, int Count, CompareType Co
// SkillIds => 4,6,7,14,20
// BuffIds => 16,17,102
public record EventCondition(EventConditionType Type, bool IgnoreOwner, int[] SkillIds, int[] BuffIds);
+ public record BeginConditionStat(BasicAttribute Attribute, float Value, CompareType Compare, CompareStatValueType ValueType);
}
+
+
diff --git a/Maple2.Server.Game/Model/Field/Actor/Actor.cs b/Maple2.Server.Game/Model/Field/Actor/Actor.cs
index 310761e53..c178b005b 100644
--- a/Maple2.Server.Game/Model/Field/Actor/Actor.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/Actor.cs
@@ -48,6 +48,11 @@ public virtual Vector3 Rotation {
public virtual BuffManager Buffs { get; }
+ ///
+ /// Tick duration of actor in the same position.
+ ///
+ public (Vector3 Position, long LastTick, long Duration) PositionTick { get; set; }
+
protected Actor(FieldManager field, int objectId, T value, string modelName, NpcMetadataStorage npcMetadata) {
Field = field;
ObjectId = objectId;
@@ -58,16 +63,7 @@ protected Actor(FieldManager field, int objectId, T value, string modelName, Npc
AnimationState = new AnimationState(this, modelName);
SkillState = new SkillState(this);
Stats = new StatsManager(this);
- }
-
- protected Actor(FieldManager field, int objectId, T value, string modelName) {
- Field = field;
- ObjectId = objectId;
- Value = value;
- Buffs = new BuffManager(this);
- Transform = new Transform();
- AnimationState = new AnimationState(this, modelName);
- SkillState = new SkillState(this);
+ PositionTick = new ValueTuple(Vector3.Zero, 0, 0);
}
public void Dispose() {
@@ -206,6 +202,12 @@ public virtual void Update(long tickCount) {
return;
}
+ if (PositionTick.Position != Position) {
+ PositionTick = new ValueTuple(Position, tickCount, 0);
+ } else {
+ PositionTick = new ValueTuple(Position, PositionTick.LastTick, tickCount - PositionTick.LastTick);
+ }
+
AnimationState.Update(tickCount);
Buffs.Update(tickCount);
}
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs
new file mode 100644
index 000000000..a6c0a1bf8
--- /dev/null
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs
@@ -0,0 +1,61 @@
+using Maple2.Model.Metadata;
+using Maple2.Server.Game.Model.Enum;
+
+namespace Maple2.Server.Game.Model.ActorStateComponent;
+
+public class AnimationRecord {
+ public AnimationSequenceMetadata? Sequence { get; set; }
+ public float Speed { get; set; }
+ public AnimationType Type { get; set; }
+ public float LastTime { get; set; }
+ public float EndTime { get; set; }
+ public LoopData Loop { get; set; }
+ public long EndTick { get; set; }
+ public long LoopEndTick { get; set; }
+ public bool IsLooping { get; set; }
+ public bool LoopOnlyOnce { get; set; }
+ public SkillMetadata? Skill { get; set; }
+
+ public AnimationRecord() {
+ Speed = 1;
+ LastTime = 0;
+ Loop = new LoopData(0, 0);
+ IsLooping = false;
+ EndTime = 0;
+ Type = AnimationType.Misc;
+ }
+
+ public AnimationRecord(AnimationSequenceMetadata sequence, float speed, AnimationType type, SkillMetadata? skill = null) : this() {
+ Sequence = sequence;
+ Speed = speed;
+ Type = type;
+ Skill = skill;
+ }
+
+ public struct LoopData {
+ public float start;
+ public float end;
+
+ public LoopData(float start, float end) {
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ public struct TickPair {
+ public long server;
+ public long client;
+
+ // mobs
+ public TickPair(long server) {
+ this.server = server;
+ this.client = server;
+ }
+
+ // players
+ public TickPair(long server, long client) {
+ this.server = server;
+ this.client = client;
+ }
+ }
+}
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
index 4a21cec4e..ffca3a5af 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
@@ -1,60 +1,30 @@
-using Maple2.Model.Metadata;
+using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Model.Enum;
namespace Maple2.Server.Game.Model.ActorStateComponent;
+///
+/// Manages animation sequences for actors in the game.
+///
public class AnimationState {
private readonly IActor actor;
+ public AnimationRecord? Current;
+ private AnimationRecord? queued;
- private struct LoopData {
- public float start;
- public float end;
-
- public LoopData(float start, float end) {
- this.start = start;
- this.end = end;
- }
- }
-
- public struct TickPair {
- public long server;
- public long client;
-
- // mobs
- public TickPair(long server) {
- this.server = server;
- this.client = server;
- }
-
- // players
- public TickPair(long server, long client) {
- this.server = server;
- this.client = client;
- }
- }
-
- public AnimationMetadata? RigMetadata { get; init; }
- public AnimationSequence? PlayingSequence { get; private set; }
+ public readonly AnimationMetadata? RigMetadata;
+ public AnimationSequenceMetadata? PlayingSequence => Current?.Sequence;
public short IdleSequenceId { get; init; }
- private AnimationSequence? queuedSequence;
- private float queuedSequenceSpeed;
- private AnimationType queuedSequenceType;
- private bool queuedResetSequence = false;
- private bool reportingKeyframeEvent;
- private bool IsPlayerAnimation { get; set; }
- public float MoveSpeed { get; set; }
- public float AttackSpeed { get; set; }
- public float SequenceSpeed { get; private set; }
- private float lastSequenceTime { get; set; }
- private float sequenceEnd { get; set; }
- private LoopData sequenceLoop { get; set; }
- private long sequenceEndTick { get; set; }
- private long sequenceLoopEndTick { get; set; }
- private long lastTick { get; set; }
- private bool isLooping { get; set; }
- private bool loopOnlyOnce { get; set; }
- private AnimationType sequenceType { get; set; }
+ public float SequenceSpeed => Current?.Speed ?? 1.0f;
+
+ private bool isHandlingKeyframe;
+ private bool IsPlayerAnimation => actor is FieldPlayer;
+ public float MoveSpeed { get; set; } = 1f;
+ public float AttackSpeed { get; set; } = 1f;
+ private float lastSequenceTime;
+ private long lastTick;
+ private long sequenceEndTick;
+ private long sequenceLoopEndTick;
private bool debugPrintAnimations;
public bool DebugPrintAnimations {
@@ -66,17 +36,18 @@ public bool DebugPrintAnimations {
}
}
+ ///
+ /// Initializes a new instance of the AnimationState class.
+ ///
+ /// The actor this animation state belongs to
+ /// The model name to load animations for
public AnimationState(IActor actor, string modelName) {
this.actor = actor;
RigMetadata = actor.NpcMetadata?.GetAnimation(modelName);
- MoveSpeed = 1;
- AttackSpeed = 1;
- IsPlayerAnimation = actor is FieldPlayer;
if (RigMetadata is null) {
IdleSequenceId = 0;
-
return;
}
@@ -89,184 +60,231 @@ public AnimationState(IActor actor, string modelName) {
IdleSequenceId = RigMetadata.Sequences.FirstOrDefault(sequence => sequence.Key == idleName).Value.Id;
}
+ ///
+ /// Resets the current animation sequence.
+ ///
private void ResetSequence() {
- PlayingSequence = null;
- SequenceSpeed = 1;
+ if (Current?.Sequence != null && actor is FieldNpc npc) {
+ npc.SendControl = true;
+ }
+ Current = null;
lastSequenceTime = 0;
- sequenceLoop = new LoopData(0, 0);
lastTick = 0;
- isLooping = false;
- sequenceEnd = 0;
- sequenceType = AnimationType.Misc;
}
- public bool TryPlaySequence(string name, float speed, AnimationType type) {
- if (RigMetadata is null) {
- return false;
- }
-
- bool animationSequenceExists = RigMetadata.Sequences.TryGetValue(name, out AnimationSequence? sequence);
-
- if (reportingKeyframeEvent) {
- queuedSequence = sequence;
- queuedSequenceSpeed = speed;
- queuedSequenceType = type;
-
- return animationSequenceExists;
- }
-
- if (!animationSequenceExists) {
+ ///
+ /// Attempts to play an animation sequence with the specified name, speed, and type.
+ ///
+ /// The name of the animation sequence to play
+ /// The speed at which to play the animation
+ /// The type of animation (Move, Skill, or Misc)
+ /// Optional skill metadata associated with this animation
+ /// True if the sequence was found and started (or queued), false otherwise
+ public bool TryPlaySequence(string name, float speed, AnimationType type, SkillMetadata? skill = null) {
+ // Can't play animations without metadata
+ if (RigMetadata is null || !RigMetadata.Sequences.TryGetValue(name, out AnimationSequenceMetadata? sequence)) {
DebugPrint($"Attempt to play nonexistent sequence '{name}' at x{speed} speed, previous: '{PlayingSequence?.Name ?? "none"}' x{SequenceSpeed}");
-
- if (reportingKeyframeEvent) {
- queuedResetSequence = true;
- } else {
- ResetSequence();
- }
-
+ ResetSequence();
return false;
}
- PlaySequence(sequence!, speed, type);
+ // If we're currently processing a keyframe event, queue this sequence for later
+ if (isHandlingKeyframe) {
+ queued = new AnimationRecord(sequence, speed, type, skill);
+ return true;
+ }
+ // Play the sequence immediately
+ PlaySequence(sequence, speed, type, skill);
return true;
}
- public bool TryPlaySequence(string name, float speed, AnimationType type, out AnimationSequence? sequence) {
- if (!TryPlaySequence(name, speed, type)) {
+ ///
+ /// Attempts to play an animation sequence and returns the sequence metadata if successful.
+ ///
+ /// The name of the animation sequence to play
+ /// The speed at which to play the animation
+ /// The type of animation (Move, Skill, or Misc)
+ /// When this method returns, contains the animation sequence metadata if found; otherwise, null
+ /// Optional skill metadata associated with this animation
+ /// True if the sequence was found and started (or queued), false otherwise
+ public bool TryPlaySequence(string name, float speed, AnimationType type, out AnimationSequenceMetadata? sequence, SkillMetadata? skill = null) {
+ // Try to play the sequence
+ if (!TryPlaySequence(name, speed, type, skill)) {
sequence = null;
-
return false;
}
- if (queuedSequence is not null) {
- sequence = queuedSequence;
- } else {
- sequence = PlayingSequence;
+ // Return the appropriate sequence metadata
+ // If we're in a keyframe event, return the queued sequence metadata
+ // Otherwise, return the currently playing sequence metadata
+ sequence = queued?.Sequence ?? PlayingSequence;
+
+ // Double-check that we have a valid sequence
+ if (sequence == null) {
+ DebugPrint($"Warning: Failed to get sequence metadata for '{name}' after successful TryPlaySequence");
+ return false;
}
return true;
}
- private void PlaySequence(AnimationSequence sequence, float speed, AnimationType type) {
- if (PlayingSequence != sequence && actor is FieldNpc npc) {
+ ///
+ /// Plays the specified animation sequence.
+ ///
+ /// The animation sequence to play
+ /// The speed at which to play the animation
+ /// The type of animation (Move, Skill, or Misc)
+ /// Optional skill metadata associated with this animation
+ private void PlaySequence(AnimationSequenceMetadata sequenceMetadata, float speed, AnimationType type, SkillMetadata? skill = null) {
+ // For NPCs, set SendControl flag when changing sequences
+ if (Current?.Sequence != sequenceMetadata && actor is FieldNpc npc) {
npc.SendControl = true;
}
- ResetSequence();
+ // Log the sequence change
+ DebugPrint($"Playing sequence '{sequenceMetadata.Name}' at x{speed} speed, previous: '{PlayingSequence?.Name ?? "none"}' x{SequenceSpeed}");
- DebugPrint($"Playing sequence '{sequence!.Name}' at x{speed} speed, previous: '{PlayingSequence?.Name ?? "none"}' x{SequenceSpeed}");
+ // Reset current sequence state
+ ResetSequence();
- PlayingSequence = sequence;
- SequenceSpeed = speed;
- sequenceType = type;
+ // Set the new sequence properties
+ Current = new AnimationRecord(sequenceMetadata, speed, type, skill);
+ // Start tracking from current tick
lastTick = actor.Field.FieldTick;
}
+ ///
+ /// Cancels the currently playing animation sequence.
+ ///
public void CancelSequence() {
+ // Log the cancellation if a sequence is playing
if (PlayingSequence is not null) {
DebugPrint($"Canceled playing sequence: '{PlayingSequence.Name}' x{SequenceSpeed}");
}
- if (reportingKeyframeEvent) {
- queuedResetSequence = true;
-
+ // If we're processing a keyframe event, queue the reset for later
+ if (isHandlingKeyframe) {
+ queued = null;
return;
}
- if (PlayingSequence != null && actor is FieldNpc npc) {
- npc.SendControl = true;
- }
-
+ // Reset the sequence state
ResetSequence();
}
+ ///
+ /// Updates the animation state based on the current tick count.
+ ///
+ /// The current server tick count
public void Update(long tickCount) {
+ // Skip update if no animation metadata is available
if (RigMetadata is null) {
return;
}
- if (PlayingSequence?.Keys is null) {
-
+ // Reset if no valid sequence is playing
+ if (PlayingSequence?.Keys.Count == 0) {
ResetSequence();
-
return;
}
- float sequenceSpeedModifier = sequenceType switch {
+ // Calculate the current sequence time
+ float sequenceSpeedModifier = Current?.Type switch {
AnimationType.Move => MoveSpeed,
AnimationType.Skill => AttackSpeed,
- _ => 1
+ _ => 1,
};
long lastServerTick = lastTick == 0 ? tickCount : lastTick;
float speed = SequenceSpeed * sequenceSpeedModifier / 1000;
- float delta = (float) (tickCount - lastServerTick) * speed;
+ float delta = (float)(tickCount - lastServerTick) * speed;
float sequenceTime = lastSequenceTime + delta;
- foreach (AnimationKey key in PlayingSequence.Keys) {
- if (HasHitKeyframe(sequenceTime, key)) {
- HitKeyframe(sequenceTime, key, speed);
+ // Process keyframe events
+ if (PlayingSequence != null && PlayingSequence.Keys.Count > 0) {
+ foreach (AnimationKey key in PlayingSequence.Keys) {
+ if (HasHitKeyframe(sequenceTime, key)) {
+ HitKeyframe(sequenceTime, key, speed);
+ }
}
}
- // TODO: maybe make client grace period ping based instead?
- if (isLooping && sequenceLoop.end != 0 && sequenceTime > sequenceLoop.end) {
+ // Handle sequence looping
+ if (Current != null && Current.IsLooping && Current.Loop.end != 0 && sequenceTime > Current.Loop.end) {
if (!IsPlayerAnimation || tickCount <= sequenceLoopEndTick + Constant.ClientGraceTimeTick) {
- if (loopOnlyOnce) {
- isLooping = false;
- loopOnlyOnce = false;
+ if (Current.LoopOnlyOnce) {
+ Current.IsLooping = false;
+ Current.LoopOnlyOnce = false;
}
- sequenceTime -= sequenceLoop.end - sequenceLoop.start;
- lastSequenceTime = sequenceTime - Math.Max(delta, sequenceTime - sequenceLoop.end + 0.001f);
+ sequenceTime -= Current.Loop.end - Current.Loop.start;
+ lastSequenceTime = sequenceTime - Math.Max(delta, sequenceTime - Current.Loop.end + 0.001f);
- // play all keyframe events from loopstart to current
- foreach (AnimationKey key in PlayingSequence.Keys) {
- if (HasHitKeyframe(sequenceTime, key)) {
- HitKeyframe(sequenceTime, key, speed);
+ // Play all keyframe events from loopstart to current
+ if (PlayingSequence?.Keys != null) {
+ foreach (AnimationKey key in PlayingSequence.Keys) {
+ if (HasHitKeyframe(sequenceTime, key)) {
+ HitKeyframe(sequenceTime, key, speed);
+ }
}
}
}
}
- if (sequenceEnd != 0 && sequenceTime > sequenceEnd) {
+ // Check for sequence end
+ if (Current != null && Current.EndTime != 0 && sequenceTime > Current.EndTime) {
if (!IsPlayerAnimation || tickCount <= sequenceEndTick + Constant.ClientGraceTimeTick) {
ResetSequence();
}
}
+ // Update timing state
lastTick = tickCount;
lastSequenceTime = sequenceTime;
- reportingKeyframeEvent = false;
+ isHandlingKeyframe = false;
- if (queuedResetSequence) {
- if (PlayingSequence != null && actor is FieldNpc npc) {
- npc.SendControl = true;
- }
-
- ResetSequence();
- } else if (queuedSequence is not null) {
- PlaySequence(queuedSequence, queuedSequenceSpeed, queuedSequenceType);
+ // Process queued actions
+ if (queued != null) {
+ PlaySequence(queued.Sequence!, queued.Speed, queued.Type);
+ queued = null;
}
-
- queuedResetSequence = false;
- queuedSequence = null;
}
+ ///
+ /// Sets whether the current sequence should loop.
+ ///
+ /// Whether the sequence should loop
+ /// Whether the sequence should only loop once
public void SetLoopSequence(bool shouldLoop, bool loopOnlyOnce) {
- isLooping = shouldLoop;
- this.loopOnlyOnce = loopOnlyOnce;
+ if (Current is null) {
+ return;
+ }
+
+ Current.IsLooping = shouldLoop;
+ Current.LoopOnlyOnce = loopOnlyOnce;
}
+ ///
+ /// Determines if a keyframe has been hit in the current update.
+ ///
+ /// The current sequence time
+ /// The keyframe to check
+ /// True if the keyframe has been hit, false otherwise
private bool HasHitKeyframe(float sequenceTime, AnimationKey key) {
- bool keyBeforeLoop = !isLooping || sequenceLoop.end == 0 || key.Time <= sequenceLoop.end + 0.001f;
+ bool keyBeforeLoop = !Current?.IsLooping ?? true || Current?.Loop.end == 0 || key.Time <= Current?.Loop.end + 0.001f;
bool hitKeySinceLastTick = key.Time > lastSequenceTime && key.Time <= sequenceTime;
return keyBeforeLoop && hitKeySinceLastTick;
}
+ ///
+ /// Gets the normalized time within a segment defined by two keyframes.
+ ///
+ /// The name of the first keyframe
+ /// The name of the second keyframe
+ /// A value between 0 and 1 representing the position within the segment, or -1 if not in the segment
public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
if (PlayingSequence is null) {
return -1;
@@ -282,7 +300,6 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
if (key.Name == keyframe2) {
keyframe2Time = key.Time;
-
break;
}
}
@@ -305,34 +322,57 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
return (lastSequenceTime - keyframe1Time) / (keyframe2Time - keyframe1Time);
}
+ ///
+ /// Processes a keyframe event.
+ ///
+ /// The current sequence time
+ /// The keyframe that was hit
+ /// The current animation speed
private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
- reportingKeyframeEvent = true;
+ isHandlingKeyframe = true;
- DebugPrint($"Sequence '{PlayingSequence!.Name}' keyframe event '{key.Name}'");
+ if (PlayingSequence != null) {
+ DebugPrint($"Sequence '{PlayingSequence.Name}' keyframe event '{key.Name}'");
+ }
actor.KeyframeEvent(key.Name);
+ if (Current == null) return;
+
switch (key.Name) {
case "loopstart":
- sequenceLoop = new LoopData(key.Time, 0);
+ Current.Loop = new AnimationRecord.LoopData(key.Time, 0);
break;
case "loopend":
- sequenceLoop = new LoopData(sequenceLoop.start, key.Time);
- sequenceLoopEndTick = (long) ((sequenceTime - key.Time) / speed);
-
+ Current.Loop = new AnimationRecord.LoopData(Current.Loop.start, key.Time);
+ Current.LoopEndTick = actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
+ sequenceLoopEndTick = Current.LoopEndTick;
break;
case "end":
- sequenceEnd = key.Time;
- sequenceEndTick = (long) ((sequenceTime - key.Time) / speed);
+ Current.EndTime = key.Time;
+ Current.EndTick = actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
+ sequenceEndTick = Current.EndTick;
break;
default:
break;
}
}
+ ///
+ /// Prints a debug message if debug printing is enabled.
+ ///
+ /// The message to print
private void DebugPrint(string message) {
if (debugPrintAnimations && actor is FieldPlayer player) {
player.Session.Send(NoticePacket.Message(message));
}
}
+
+ ///
+ /// Checks if an animation is currently playing.
+ ///
+ /// True if an animation is playing, false otherwise
+ public bool IsAnimationPlaying() {
+ return Current != null && PlayingSequence != null;
+ }
}
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementState.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementState.cs
index 1115aa043..6fafa3dae 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementState.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementState.cs
@@ -14,7 +14,7 @@ public partial class MovementState {
public ActorState State { get; private set; } = ActorState.None;
public float Speed { get; private set; }
public Vector3 Velocity { get; private set; }
- private AnimationSequence? stateSequence;
+ private AnimationSequenceMetadata? stateSequence;
#region LastTickData
private float lastSpeed;
private Vector3 lastVelocity;
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Walk.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Walk.cs
index 4f619f6bf..09b1d671b 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Walk.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Walk.cs
@@ -22,7 +22,7 @@ private enum WalkType {
private (Vector3 start, Vector3 end) walkSegment;
private bool walkSegmentSet;
private bool walkLookWhenDone = false;
- private AnimationSequence? walkSequence = null;
+ private AnimationSequenceMetadata? walkSequence = null;
private float walkSpeed;
private NpcTask? walkTask = null;
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.SkillCastTask.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.SkillCastTask.cs
index 3ef9422b0..bbf960a74 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.SkillCastTask.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.SkillCastTask.cs
@@ -97,7 +97,7 @@ private void SkillCast(NpcSkillCastTask task, int id, short level, long uid, byt
return;
}
- if (!actor.AnimationState.TryPlaySequence(cast.Motion.MotionProperty.SequenceName, cast.Motion.MotionProperty.SequenceSpeed, AnimationType.Skill, out AnimationSequence? sequence)) {
+ if (!actor.AnimationState.TryPlaySequence(cast.Motion.MotionProperty.SequenceName, cast.Motion.MotionProperty.SequenceSpeed, AnimationType.Skill, out AnimationSequenceMetadata? sequence)) {
task.Cancel();
return;
diff --git a/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs b/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs
index 4fcdde04f..113498150 100644
--- a/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs
@@ -72,10 +72,10 @@ public short SequenceId {
);
public readonly AgentNavigation? Navigation;
- public readonly AnimationSequence IdleSequence;
- public readonly AnimationSequence? JumpSequence;
- public readonly AnimationSequence? WalkSequence;
- public readonly AnimationSequence? SpawnSequence;
+ public readonly AnimationSequenceMetadata IdleSequenceMetadata;
+ public readonly AnimationSequenceMetadata? JumpSequence;
+ public readonly AnimationSequenceMetadata? WalkSequence;
+ public readonly AnimationSequenceMetadata? SpawnSequence;
private readonly WeightedSet defaultRoutines;
public readonly AiState AiState;
public readonly MovementState MovementState;
@@ -95,7 +95,7 @@ public short SequenceId {
public readonly Dictionary AiExtraData = new();
public FieldNpc(FieldManager field, int objectId, DtCrowdAgent? agent, Npc npc, string aiPath, string spawnAnimation = "", string? patrolDataUUID = null) : base(field, objectId, npc, npc.Metadata.Model.Name, field.NpcMetadata) {
- IdleSequence = npc.Animations.GetValueOrDefault("Idle_A") ?? new AnimationSequence(string.Empty, -1, 1f, null);
+ IdleSequenceMetadata = npc.Animations.GetValueOrDefault("Idle_A") ?? new AnimationSequenceMetadata(string.Empty, -1, 1f, null);
JumpSequence = npc.Animations.GetValueOrDefault("Jump_A") ?? npc.Animations.GetValueOrDefault("Jump_B");
WalkSequence = npc.Animations.GetValueOrDefault("Walk_A");
SpawnSequence = npc.Animations.GetValueOrDefault(spawnAnimation);
@@ -227,7 +227,7 @@ public override void KeyframeEvent(string keyName) {
}
string routineName = defaultRoutines.Get();
- if (!Value.Animations.TryGetValue(routineName, out AnimationSequence? sequence)) {
+ if (!Value.Animations.TryGetValue(routineName, out AnimationSequenceMetadata? sequence)) {
Logger.Error("Invalid routine: {Routine} for npc {NpcId}", routineName, Value.Metadata.Id);
return MovementState.TryStandby(null, true);
@@ -243,7 +243,7 @@ public override void KeyframeEvent(string keyName) {
case { } when routineName.StartsWith("Run_"):
return MovementState.TryMoveTo(Navigation?.GetRandomPatrolPoint() ?? Position, false, sequence.Name);
case { }:
- if (!Value.Animations.TryGetValue(routineName, out AnimationSequence? animationSequence)) {
+ if (!Value.Animations.TryGetValue(routineName, out AnimationSequenceMetadata? animationSequence)) {
break;
}
return MovementState.TryEmote(animationSequence.Name, SpawnSequence is not null);
@@ -258,7 +258,7 @@ public override void KeyframeEvent(string keyName) {
MS2WayPoint currentWaypoint = Patrol!.WayPoints[currentWaypointIndex];
if (!string.IsNullOrEmpty(currentWaypoint.ArriveAnimation) && idleTask is not MovementState.NpcEmoteTask) {
- if (Value.Animations.TryGetValue(currentWaypoint.ArriveAnimation, out AnimationSequence? arriveSequence)) {
+ if (Value.Animations.TryGetValue(currentWaypoint.ArriveAnimation, out AnimationSequenceMetadata? arriveSequence)) {
return MovementState.TryEmote(arriveSequence.Name, false);
}
}
@@ -266,7 +266,7 @@ public override void KeyframeEvent(string keyName) {
NpcTask? approachTask = null;
if (Navigation!.PathTo(currentWaypoint.Position)) {
- if (Value.Animations.TryGetValue(currentWaypoint.ApproachAnimation, out AnimationSequence? patrolSequence)) {
+ if (Value.Animations.TryGetValue(currentWaypoint.ApproachAnimation, out AnimationSequenceMetadata? patrolSequence)) {
approachTask = MovementState.TryMoveTo(currentWaypoint.Position, false, sequence: patrolSequence.Name);
} else if (WalkSequence is not null) {
approachTask = MovementState.TryMoveTo(currentWaypoint.Position, false, WalkSequence.Name);
@@ -306,7 +306,7 @@ protected override void OnDeath() {
}
public virtual void Animate(string sequenceName, float duration = -1f) {
- if (!Value.Animations.TryGetValue(sequenceName, out AnimationSequence? sequence)) {
+ if (!Value.Animations.TryGetValue(sequenceName, out AnimationSequenceMetadata? sequence)) {
Logger.Error("Invalid sequence: {Sequence} for npc {NpcId}", sequenceName, Value.Metadata.Id);
return;
}
diff --git a/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs b/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs
index ecb7e6fae..3b25df4c3 100644
--- a/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs
@@ -87,7 +87,7 @@ public DeathState DeathState {
private readonly EventQueue scheduler;
- public FieldPlayer(GameSession session, Player player) : base(session.Field!, player.ObjectId, player, GetPlayerModel(player.Character.Gender)) {
+ public FieldPlayer(GameSession session, Player player) : base(session.Field, player.ObjectId, player, GetPlayerModel(player.Character.Gender), session.NpcMetadata) {
Session = session;
regenStats = new Dictionary>();
diff --git a/Maple2.Server.Game/Model/Field/Actor/Routine/AnimateRoutine.cs b/Maple2.Server.Game/Model/Field/Actor/Routine/AnimateRoutine.cs
index 4dc030d7c..42a78b68c 100644
--- a/Maple2.Server.Game/Model/Field/Actor/Routine/AnimateRoutine.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/Routine/AnimateRoutine.cs
@@ -6,13 +6,13 @@ public class AnimateRoutine : NpcRoutine {
private TimeSpan duration;
private readonly bool complete = true;
- public AnimateRoutine(FieldNpc npc, AnimationSequence sequence, float duration = -1f) : base(npc, sequence.Id) {
+ public AnimateRoutine(FieldNpc npc, AnimationSequenceMetadata sequenceMetadata, float duration = -1f) : base(npc, sequenceMetadata.Id) {
if (duration != -1f) {
this.duration = TimeSpan.FromMilliseconds(duration);
complete = false;
return;
}
- this.duration = TimeSpan.FromSeconds(sequence.Time);
+ this.duration = TimeSpan.FromSeconds(sequenceMetadata.Time);
}
public override Result Update(TimeSpan elapsed) {
diff --git a/Maple2.Server.Game/Model/Field/Actor/Routine/JumpRoutine.cs b/Maple2.Server.Game/Model/Field/Actor/Routine/JumpRoutine.cs
index 3f7e9bae6..9810b1caa 100644
--- a/Maple2.Server.Game/Model/Field/Actor/Routine/JumpRoutine.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/Routine/JumpRoutine.cs
@@ -40,7 +40,7 @@ private JumpRoutine(FieldNpc npc, short sequenceId, Vector3 endPosition, float d
this.duration = TimeSpan.FromSeconds(duration);
Npc.State = new StateJumpNpc(Npc.Position, endPosition, duration, scale);
- NextRoutine = () => new WaitRoutine(npc, npc.IdleSequence.Id, npc.IdleSequence.Time);
+ NextRoutine = () => new WaitRoutine(npc, npc.IdleSequenceMetadata.Id, npc.IdleSequenceMetadata.Time);
}
private JumpRoutine(FieldNpc npc, short sequenceId, float duration) : base(npc, sequenceId) {
@@ -48,7 +48,7 @@ private JumpRoutine(FieldNpc npc, short sequenceId, float duration) : base(npc,
this.duration = TimeSpan.FromSeconds(duration);
Npc.State = new StateJumpNpc(Npc.Position);
- NextRoutine = () => new WaitRoutine(npc, npc.IdleSequence.Id, npc.IdleSequence.Time);
+ NextRoutine = () => new WaitRoutine(npc, npc.IdleSequenceMetadata.Id, npc.IdleSequenceMetadata.Time);
}
public override Result Update(TimeSpan elapsed) {
diff --git a/Maple2.Server.Game/Model/Field/Actor/Routine/MoveRoutine.cs b/Maple2.Server.Game/Model/Field/Actor/Routine/MoveRoutine.cs
index 321fcb7fd..c174e8b6c 100644
--- a/Maple2.Server.Game/Model/Field/Actor/Routine/MoveRoutine.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/Routine/MoveRoutine.cs
@@ -10,7 +10,7 @@ public static NpcRoutine Walk(FieldNpc npc, short sequenceId) {
try {
return new MoveRoutine(npc, sequenceId, npc.Value.Metadata.Action.WalkSpeed);
} catch (ArgumentException) {
- return new WaitRoutine(npc, npc.IdleSequence.Id, npc.IdleSequence.Time);
+ return new WaitRoutine(npc, npc.IdleSequenceMetadata.Id, npc.IdleSequenceMetadata.Time);
}
}
@@ -18,7 +18,7 @@ public static NpcRoutine Run(FieldNpc npc, short sequenceId) {
try {
return new MoveRoutine(npc, sequenceId, npc.Value.Metadata.Action.RunSpeed);
} catch (ArgumentException) {
- return new WaitRoutine(npc, npc.IdleSequence.Id, npc.IdleSequence.Time);
+ return new WaitRoutine(npc, npc.IdleSequenceMetadata.Id, npc.IdleSequenceMetadata.Time);
}
}
diff --git a/Maple2.Server.Game/Model/Field/Actor/Routine/NpcRoutine.cs b/Maple2.Server.Game/Model/Field/Actor/Routine/NpcRoutine.cs
index 328de36c9..75c83973b 100644
--- a/Maple2.Server.Game/Model/Field/Actor/Routine/NpcRoutine.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/Routine/NpcRoutine.cs
@@ -31,7 +31,7 @@ public virtual void OnCompleted() {
Completed = true;
// Force Idle on completion
- Npc.SequenceId = Npc.IdleSequence.Id;
+ Npc.SequenceId = Npc.IdleSequenceMetadata.Id;
if (Npc.State.State != ActorState.Idle) {
Npc.State = new NpcState();
}
diff --git a/Maple2.Server.Game/Model/Skill/SkillQueue.cs b/Maple2.Server.Game/Model/Skill/SkillQueue.cs
index 19075ab8a..67ec12feb 100644
--- a/Maple2.Server.Game/Model/Skill/SkillQueue.cs
+++ b/Maple2.Server.Game/Model/Skill/SkillQueue.cs
@@ -40,6 +40,10 @@ public void Remove(long uid) {
}
}
+ public bool Contains(int skillId) {
+ return casts.Any(cast => cast?.SkillId == skillId);
+ }
+
public void Clear() {
for (int i = 0; i < MAX_PENDING; i++) {
casts[i] = null;
diff --git a/Maple2.Server.Game/PacketHandlers/SkillHandler.cs b/Maple2.Server.Game/PacketHandlers/SkillHandler.cs
index c0c216387..241f85ca9 100644
--- a/Maple2.Server.Game/PacketHandlers/SkillHandler.cs
+++ b/Maple2.Server.Game/PacketHandlers/SkillHandler.cs
@@ -158,7 +158,7 @@ private void HandleUse(GameSession session, IByteReader packet) {
SkillMetadataMotionProperty motion = metadata.Data.Motions.First().MotionProperty;
- session.Player.AnimationState.TryPlaySequence(motion.SequenceName, motion.SequenceSpeed, AnimationType.Skill);
+ session.Player.AnimationState.TryPlaySequence(motion.SequenceName, motion.SequenceSpeed, AnimationType.Skill, metadata);
long startTick = session.Field.FieldTick;
foreach (SkillEffectMetadata effect in metadata.Data.Skills) {
diff --git a/Maple2.Server.Game/Util/SkillUtils.cs b/Maple2.Server.Game/Util/SkillUtils.cs
index 6f9a36697..a38987416 100644
--- a/Maple2.Server.Game/Util/SkillUtils.cs
+++ b/Maple2.Server.Game/Util/SkillUtils.cs
@@ -3,6 +3,7 @@
using Maple2.Model.Enum;
using Maple2.Model.Game;
using Maple2.Model.Metadata;
+using Maple2.Server.Game.Manager.Field;
using Maple2.Server.Game.Model;
using Maple2.Tools.Collision;
@@ -81,6 +82,9 @@ public static bool Check(this BeginCondition condition, IActor caster, IActor ow
if (condition.OnlyFlyableMap && !caster.Field.Metadata.Property.CanFly) {
return false;
}
+ if (condition.OnlySurvival && !(caster.Field.Metadata.Property.Type is MapType.SurvivalTeam or MapType.SurvivalSolo)) {
+ return false;
+ }
if (player.Value.Character.Level < condition.Level) {
return false;
}
@@ -103,6 +107,28 @@ public static bool Check(this BeginCondition condition, IActor caster, IActor ow
return false;
}
}
+ if (condition.DurationWithoutMoving > 0) {
+ if (player.PositionTick.Duration < condition.DurationWithoutMoving) {
+ return false;
+ }
+ }
+ if (condition.Maps.Length > 0 && !condition.Maps.Contains(caster.Field.MapId)) {
+ return false;
+ }
+ if (condition.Maps.Length > 0 && !condition.MapTypes.Contains(caster.Field.Metadata.Property.Type)) {
+ return false;
+ }
+ if (condition.Maps.Length > 0 && !condition.Continents.Contains(caster.Field.Metadata.Property.Continent)) {
+ return false;
+ }
+ if (condition.DungeonGroupType.Length > 0 &&
+ (caster.Field is not DungeonFieldManager dungeonFieldManager ||
+ !condition.DungeonGroupType.Contains(dungeonFieldManager.DungeonMetadata.GroupType))) {
+ return false;
+ }
+ if (condition.ActiveSkill.Length > 0 && condition.ActiveSkill.All(id => owner.AnimationState.Current?.Skill?.Id != id)) {
+ return false;
+ }
}
return condition.Caster.Check(caster) && condition.Owner.Check(owner) && condition.Target.Check(target);
@@ -137,6 +163,49 @@ private static bool Check(this BeginConditionTarget? condition, IActor target) {
}
}
+ foreach ((BasicAttribute attribute, float value, CompareType compare, CompareStatValueType valueType) in condition.Stat) {
+ float targetValue = valueType switch {
+ CompareStatValueType.CurrentPercentage => (float) target.Stats.Values[attribute].Current / target.Stats.Values[attribute].Total,
+ CompareStatValueType.TotalValue => target.Stats.Values[attribute].Total,
+ _ => 0,
+ };
+
+ bool compareResult = compare switch {
+ CompareType.Equals => targetValue == value,
+ CompareType.Less => targetValue < value,
+ CompareType.LessEquals => targetValue <= value,
+ CompareType.Greater => targetValue > value,
+ CompareType.GreaterEquals => targetValue >= value,
+ _ => true,
+ };
+ if (!compareResult) {
+ return false;
+ }
+ }
+
+ if (condition.HasNotBuffIds.Length > 0 && condition.HasNotBuffIds.Any(id => target.Buffs.HasBuff(id))) {
+ return false;
+ }
+
+ // Verify if these conditions are only for player.
+ if (target is FieldPlayer player) {
+ if (condition.States.Length > 0 && !condition.States.Contains(player.State)) {
+ return false;
+ }
+
+ if (condition.SubStates.Length > 0 && !condition.SubStates.Contains(player.SubState)) {
+ return false;
+ }
+
+ if (condition.Masteries.Count > 0 && !condition.Masteries.All(mastery => player.Session.Mastery[mastery.Key] >= mastery.Value)) {
+ return false;
+ }
+ } else if (target is FieldNpc npc) {
+ if (condition.NpcIds.Length > 0 && !condition.NpcIds.Contains(npc.Value.Id)) {
+ return false;
+ }
+ }
+
return true;
}
From 98bca49248f36bbf7fc435baf50ef7389e2ca714 Mon Sep 17 00:00:00 2001
From: Zin <62830952+Zintixx@users.noreply.github.com>
Date: Wed, 9 Apr 2025 21:48:58 -0700
Subject: [PATCH 2/9] Additional Fixes on Animation
---
Maple2.Server.Game/Commands/DebugCommand.cs | 3 +-
.../AnimationManager.cs} | 54 +++++++++++++------
.../Field/FieldManager/FieldManager.State.cs | 1 +
Maple2.Server.Game/Model/Field/Actor/Actor.cs | 8 +--
.../ActorStateComponent/MovementState.cs | 16 +++---
.../MovementState.Emote.cs | 2 +-
.../MovementState.SkillCast.cs | 6 +--
.../MovementStateStates/MovementState.Walk.cs | 8 +--
.../MovementState.EmoteTask.cs | 4 +-
.../MovementState.SkillCastTask.cs | 2 +-
.../Model/Field/Actor/FieldActor.cs | 4 +-
.../Model/Field/Actor/FieldNpc.cs | 2 +-
.../Model/Field/Actor/FieldPlayer.cs | 14 ++---
.../Model/Field/Actor/IActor.cs | 2 +-
Maple2.Server.Game/Model/Field/Buff.cs | 3 ++
.../PacketHandlers/SkillHandler.cs | 12 ++---
.../Packets/NpcControlPacket.cs | 8 +--
Maple2.Server.Game/Session/GameSession.cs | 2 +
Maple2.Server.Game/Util/SkillUtils.cs | 2 +-
19 files changed, 89 insertions(+), 64 deletions(-)
rename Maple2.Server.Game/{Model/Field/Actor/ActorStateComponent/AnimationState.cs => Manager/AnimationManager.cs} (89%)
diff --git a/Maple2.Server.Game/Commands/DebugCommand.cs b/Maple2.Server.Game/Commands/DebugCommand.cs
index 8d68378ec..8673a1831 100644
--- a/Maple2.Server.Game/Commands/DebugCommand.cs
+++ b/Maple2.Server.Game/Commands/DebugCommand.cs
@@ -10,6 +10,7 @@
using System.Numerics;
using Maple2.Model.Common;
using System;
+using Maple2.Server.Game.Model;
namespace Maple2.Server.Game.Commands;
@@ -94,7 +95,7 @@ public DebugAnimationCommand(GameSession session) : base("anims", "Prints player
}
private void Handle(InvocationContext ctx, bool? enabled) {
- session.Player.AnimationState.DebugPrintAnimations = enabled ?? true;
+ session.Player.Animation.DebugPrintAnimations = enabled ?? true;
string message = enabled ?? true ? "Enabled" : "Disabled";
ctx.Console.Out.WriteLine($"{message} animation debug info printing");
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs b/Maple2.Server.Game/Manager/AnimationManager.cs
similarity index 89%
rename from Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
rename to Maple2.Server.Game/Manager/AnimationManager.cs
index ffca3a5af..fcf7cde08 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
+++ b/Maple2.Server.Game/Manager/AnimationManager.cs
@@ -1,14 +1,18 @@
-using Maple2.Model.Metadata;
+using Maple2.Model.Enum;
+using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
+using Maple2.Server.Game.Model;
+using Maple2.Server.Game.Model.ActorStateComponent;
using Maple2.Server.Game.Model.Enum;
+using Maple2.Server.Game.Session;
-namespace Maple2.Server.Game.Model.ActorStateComponent;
+namespace Maple2.Server.Game.Manager;
///
/// Manages animation sequences for actors in the game.
///
-public class AnimationState {
- private readonly IActor actor;
+public class AnimationManager {
+ private IActor Actor { get; set; }
public AnimationRecord? Current;
private AnimationRecord? queued;
@@ -18,7 +22,7 @@ public class AnimationState {
public float SequenceSpeed => Current?.Speed ?? 1.0f;
private bool isHandlingKeyframe;
- private bool IsPlayerAnimation => actor is FieldPlayer;
+ private bool IsPlayerAnimation => Actor is FieldPlayer;
public float MoveSpeed { get; set; } = 1f;
public float AttackSpeed { get; set; } = 1f;
private float lastSequenceTime;
@@ -30,7 +34,7 @@ public class AnimationState {
public bool DebugPrintAnimations {
get { return debugPrintAnimations; }
set {
- if (actor is FieldPlayer) {
+ if (Actor is FieldPlayer) {
debugPrintAnimations = value;
}
}
@@ -41,10 +45,13 @@ public bool DebugPrintAnimations {
///
/// The actor this animation state belongs to
/// The model name to load animations for
- public AnimationState(IActor actor, string modelName) {
- this.actor = actor;
+ public AnimationManager(IActor actor) {
+ Actor = actor;
- RigMetadata = actor.NpcMetadata?.GetAnimation(modelName);
+ RigMetadata = actor switch {
+ FieldNpc fieldNpc => actor.Field.NpcMetadata.GetAnimation(fieldNpc.Value.Metadata.Model.Name),
+ _ => null,
+ };
if (RigMetadata is null) {
IdleSequenceId = 0;
@@ -60,11 +67,26 @@ public AnimationState(IActor actor, string modelName) {
IdleSequenceId = RigMetadata.Sequences.FirstOrDefault(sequence => sequence.Key == idleName).Value.Id;
}
+ public AnimationManager(GameSession session) {
+ Actor = session.Player;
+ string model = session.Player.Value.Character.Gender == Gender.Male ? "male" : "female";
+ RigMetadata = session.NpcMetadata.GetAnimation(model);
+
+ if (RigMetadata is null) {
+ throw new Exception("Failed to initialize AnimationState, could not find metadata for player model " + model);
+ }
+ IdleSequenceId = RigMetadata!.Sequences.FirstOrDefault(sequence => sequence.Key == "Idle_A").Value.Id;
+ }
+
+ public void ResetActor(IActor actor) {
+ Actor = actor;
+ }
+
///
/// Resets the current animation sequence.
///
private void ResetSequence() {
- if (Current?.Sequence != null && actor is FieldNpc npc) {
+ if (Current?.Sequence != null && Actor is FieldNpc npc) {
npc.SendControl = true;
}
Current = null;
@@ -138,7 +160,7 @@ public bool TryPlaySequence(string name, float speed, AnimationType type, out An
/// Optional skill metadata associated with this animation
private void PlaySequence(AnimationSequenceMetadata sequenceMetadata, float speed, AnimationType type, SkillMetadata? skill = null) {
// For NPCs, set SendControl flag when changing sequences
- if (Current?.Sequence != sequenceMetadata && actor is FieldNpc npc) {
+ if (Current?.Sequence != sequenceMetadata && Actor is FieldNpc npc) {
npc.SendControl = true;
}
@@ -152,7 +174,7 @@ private void PlaySequence(AnimationSequenceMetadata sequenceMetadata, float spee
Current = new AnimationRecord(sequenceMetadata, speed, type, skill);
// Start tracking from current tick
- lastTick = actor.Field.FieldTick;
+ lastTick = Actor.Field.FieldTick;
}
///
@@ -335,7 +357,7 @@ private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
DebugPrint($"Sequence '{PlayingSequence.Name}' keyframe event '{key.Name}'");
}
- actor.KeyframeEvent(key.Name);
+ Actor.KeyframeEvent(key.Name);
if (Current == null) return;
@@ -345,12 +367,12 @@ private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
break;
case "loopend":
Current.Loop = new AnimationRecord.LoopData(Current.Loop.start, key.Time);
- Current.LoopEndTick = actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
+ Current.LoopEndTick = Actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
sequenceLoopEndTick = Current.LoopEndTick;
break;
case "end":
Current.EndTime = key.Time;
- Current.EndTick = actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
+ Current.EndTick = Actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
sequenceEndTick = Current.EndTick;
break;
default:
@@ -363,7 +385,7 @@ private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
///
/// The message to print
private void DebugPrint(string message) {
- if (debugPrintAnimations && actor is FieldPlayer player) {
+ if (debugPrintAnimations && Actor is FieldPlayer player) {
player.Session.Send(NoticePacket.Message(message));
}
}
diff --git a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs
index dd018e009..fb289a519 100644
--- a/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs
+++ b/Maple2.Server.Game/Manager/Field/FieldManager/FieldManager.State.cs
@@ -71,6 +71,7 @@ public FieldPlayer SpawnPlayer(GameSession session, Player player, int portalId
};
session.Stats.ResetActor(fieldPlayer);
session.Buffs.ResetActor(fieldPlayer);
+ session.Animation.ResetActor(fieldPlayer);
// Use Portal if needed.
if (fieldPlayer.Position == default && Entities.Portals.TryGetValue(portalId, out Portal? portal)) {
diff --git a/Maple2.Server.Game/Model/Field/Actor/Actor.cs b/Maple2.Server.Game/Model/Field/Actor/Actor.cs
index c178b005b..d8f39f6cf 100644
--- a/Maple2.Server.Game/Model/Field/Actor/Actor.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/Actor.cs
@@ -40,7 +40,7 @@ public virtual Vector3 Rotation {
set => Transform.RotationAnglesDegrees = value;
}
public Transform Transform { get; init; }
- public AnimationState AnimationState { get; init; }
+ public virtual AnimationManager Animation { get; }
public SkillState SkillState { get; init; }
public virtual bool IsDead { get; protected set; }
@@ -53,14 +53,14 @@ public virtual Vector3 Rotation {
///
public (Vector3 Position, long LastTick, long Duration) PositionTick { get; set; }
- protected Actor(FieldManager field, int objectId, T value, string modelName, NpcMetadataStorage npcMetadata) {
+ protected Actor(FieldManager field, int objectId, T value, NpcMetadataStorage npcMetadata) {
Field = field;
ObjectId = objectId;
Value = value;
Buffs = new BuffManager(this);
Transform = new Transform();
NpcMetadata = npcMetadata;
- AnimationState = new AnimationState(this, modelName);
+ Animation = new AnimationManager(this);
SkillState = new SkillState(this);
Stats = new StatsManager(this);
PositionTick = new ValueTuple(Vector3.Zero, 0, 0);
@@ -208,7 +208,7 @@ public virtual void Update(long tickCount) {
PositionTick = new ValueTuple(Position, PositionTick.LastTick, tickCount - PositionTick.LastTick);
}
- AnimationState.Update(tickCount);
+ Animation.Update(tickCount);
Buffs.Update(tickCount);
}
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementState.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementState.cs
index 6fafa3dae..f963fb432 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementState.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementState.cs
@@ -37,7 +37,7 @@ public partial class MovementState {
public MovementState(FieldNpc actor) {
this.actor = actor;
- hasIdleA = actor.AnimationState.RigMetadata?.Sequences?.ContainsKey("Idle_A") ?? false;
+ hasIdleA = actor.Animation.RigMetadata?.Sequences?.ContainsKey("Idle_A") ?? false;
aniSpeed = actor.Value.Metadata.Model.AniSpeed;
SetState(ActorState.Spawn);
@@ -141,10 +141,10 @@ private void Idle(string sequence = "") {
SetState(ActorState.Idle);
if (hasIdleA) {
- if (actor.AnimationState.TryPlaySequence(sequence, aniSpeed, AnimationType.Misc)) {
- stateSequence = actor.AnimationState.PlayingSequence;
- } else if (setAttackIdle && actor.AnimationState.TryPlaySequence("Idle_A", aniSpeed, AnimationType.Misc)) {
- stateSequence = actor.AnimationState.PlayingSequence;
+ if (actor.Animation.TryPlaySequence(sequence, aniSpeed, AnimationType.Misc)) {
+ stateSequence = actor.Animation.PlayingSequence;
+ } else if (setAttackIdle && actor.Animation.TryPlaySequence("Idle_A", aniSpeed, AnimationType.Misc)) {
+ stateSequence = actor.Animation.PlayingSequence;
}
}
}
@@ -182,7 +182,7 @@ public void KeyframeEvent(string keyName) {
}
public void Update(long tickCount) {
- if (actor.AnimationState.PlayingSequence != stateSequence) {
+ if (actor.Animation.PlayingSequence != stateSequence) {
Idle();
}
@@ -203,10 +203,10 @@ public void Update(long tickCount) {
StateWalkUpdate(tickCount, tickDelta);
break;
case ActorState.Spawn:
- if (actor.AnimationState.TryPlaySequence("Regen_A", aniSpeed, AnimationType.Misc)) {
+ if (actor.Animation.TryPlaySequence("Regen_A", aniSpeed, AnimationType.Misc)) {
SetState(ActorState.Regen);
- stateSequence = actor.AnimationState.PlayingSequence;
+ stateSequence = actor.Animation.PlayingSequence;
} else {
Idle();
}
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Emote.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Emote.cs
index be93d64a4..7cd3c5614 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Emote.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Emote.cs
@@ -16,7 +16,7 @@ public void StateEmoteEvent(string keyName) {
case "end":
if (emoteLimitTick != 0) {
if (emoteActionTask is NpcEmoteTask emoteTask) {
- actor.AnimationState.TryPlaySequence(emoteTask.Sequence, 1, AnimationType.Misc);
+ actor.Animation.TryPlaySequence(emoteTask.Sequence, 1, AnimationType.Misc);
}
return;
}
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.SkillCast.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.SkillCast.cs
index 6911216fe..46b7c0474 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.SkillCast.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.SkillCast.cs
@@ -23,8 +23,8 @@ public void StateSkillEvent(string keyName) {
actor.AppendDebugMessage("Skill Keyframe: " + keyName);
- if (actor.AnimationState.PlayingSequence is not null) {
- actor.AppendDebugMessage(actor.AnimationState.PlayingSequence.Name);
+ if (actor.Animation.PlayingSequence is not null) {
+ actor.AppendDebugMessage(actor.Animation.PlayingSequence.Name);
}
if (castSkill.Motion.AttackPoints.TryGetValue(keyName, out byte point)) {
@@ -104,7 +104,7 @@ private void StateSkillCastMoveUpdate(long tickCount, long tickDelta, float delt
castMoveLastTick = -1;
} else {
- castMoveTick = actor.AnimationState.GetSequenceSegmentTime(castMoveStartKeyframe, castMoveEndKeyframe);
+ castMoveTick = actor.Animation.GetSequenceSegmentTime(castMoveStartKeyframe, castMoveEndKeyframe);
if (castMoveFinished) {
castMoveTick = 1;
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Walk.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Walk.cs
index 09b1d671b..0bf75bf25 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Walk.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateStates/MovementState.Walk.cs
@@ -45,8 +45,8 @@ private void StartWalking(string sequence, NpcTask task) {
baseSpeed = isWalking ? actor.Value.Metadata.Action.WalkSpeed : actor.Value.Metadata.Action.RunSpeed;
- if (actor.AnimationState.PlayingSequence?.Name == sequence || actor.AnimationState.TryPlaySequence(sequence, aniSpeed * Speed, AnimationType.Misc)) {
- stateSequence = actor.AnimationState.PlayingSequence;
+ if (actor.Animation.PlayingSequence?.Name == sequence || actor.Animation.TryPlaySequence(sequence, aniSpeed * Speed, AnimationType.Misc)) {
+ stateSequence = actor.Animation.PlayingSequence;
walkSequence = stateSequence;
walkTask = task;
@@ -166,8 +166,8 @@ public void StateWalkEvent(string keyName) {
switch (keyName) {
case "end":
if (State == ActorState.Walk) {
- if (actor.AnimationState.TryPlaySequence(stateSequence!.Name, aniSpeed * Speed, AnimationType.Misc)) {
- stateSequence = actor.AnimationState.PlayingSequence;
+ if (actor.Animation.TryPlaySequence(stateSequence!.Name, aniSpeed * Speed, AnimationType.Misc)) {
+ stateSequence = actor.Animation.PlayingSequence;
}
return;
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.EmoteTask.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.EmoteTask.cs
index 6956d0046..b4c615949 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.EmoteTask.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.EmoteTask.cs
@@ -36,7 +36,7 @@ private void Emote(NpcTask task, string sequence, bool isIdle, float duration) {
return;
}
- if (!actor.AnimationState.TryPlaySequence(sequence, 1, AnimationType.Misc)) {
+ if (!actor.Animation.TryPlaySequence(sequence, 1, AnimationType.Misc)) {
task.Cancel();
return;
}
@@ -46,7 +46,7 @@ private void Emote(NpcTask task, string sequence, bool isIdle, float duration) {
}
emoteActionTask = task;
- stateSequence = actor.AnimationState.PlayingSequence;
+ stateSequence = actor.Animation.PlayingSequence;
SetState(isIdle ? ActorState.Idle : ActorState.Emotion);
}
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.SkillCastTask.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.SkillCastTask.cs
index bbf960a74..13ec26981 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.SkillCastTask.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/MovementStateTasks/MovementState.SkillCastTask.cs
@@ -97,7 +97,7 @@ private void SkillCast(NpcSkillCastTask task, int id, short level, long uid, byt
return;
}
- if (!actor.AnimationState.TryPlaySequence(cast.Motion.MotionProperty.SequenceName, cast.Motion.MotionProperty.SequenceSpeed, AnimationType.Skill, out AnimationSequenceMetadata? sequence)) {
+ if (!actor.Animation.TryPlaySequence(cast.Motion.MotionProperty.SequenceName, cast.Motion.MotionProperty.SequenceSpeed, AnimationType.Skill, out AnimationSequenceMetadata? sequence)) {
task.Cancel();
return;
diff --git a/Maple2.Server.Game/Model/Field/Actor/FieldActor.cs b/Maple2.Server.Game/Model/Field/Actor/FieldActor.cs
index 70b654680..4873897a0 100644
--- a/Maple2.Server.Game/Model/Field/Actor/FieldActor.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/FieldActor.cs
@@ -25,7 +25,7 @@ internal sealed class FieldActor : IActor {
public Transform Transform { get; init; }
public BuffManager Buffs { get; }
public StatsManager Stats { get; }
- public AnimationState AnimationState { get; init; }
+ public AnimationManager Animation { get; init; }
public SkillState SkillState { get; init; }
public FieldActor(FieldManager field, NpcMetadataStorage npcMetadata) {
@@ -34,7 +34,7 @@ public FieldActor(FieldManager field, NpcMetadataStorage npcMetadata) {
Buffs = new BuffManager(this);
Transform = new Transform();
NpcMetadata = npcMetadata;
- AnimationState = new AnimationState(this, string.Empty); // not meant to have animations
+ Animation = new AnimationManager(this); // not meant to have animations
SkillState = new SkillState(this);
}
diff --git a/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs b/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs
index 113498150..b9aa32796 100644
--- a/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/FieldNpc.cs
@@ -94,7 +94,7 @@ public short SequenceId {
public readonly Dictionary AiExtraData = new();
- public FieldNpc(FieldManager field, int objectId, DtCrowdAgent? agent, Npc npc, string aiPath, string spawnAnimation = "", string? patrolDataUUID = null) : base(field, objectId, npc, npc.Metadata.Model.Name, field.NpcMetadata) {
+ public FieldNpc(FieldManager field, int objectId, DtCrowdAgent? agent, Npc npc, string aiPath, string spawnAnimation = "", string? patrolDataUUID = null) : base(field, objectId, npc, field.NpcMetadata) {
IdleSequenceMetadata = npc.Animations.GetValueOrDefault("Idle_A") ?? new AnimationSequenceMetadata(string.Empty, -1, 1f, null);
JumpSequence = npc.Animations.GetValueOrDefault("Jump_A") ?? npc.Animations.GetValueOrDefault("Jump_B");
WalkSequence = npc.Animations.GetValueOrDefault("Walk_A");
diff --git a/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs b/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs
index 3b25df4c3..4a3913655 100644
--- a/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs
@@ -18,6 +18,7 @@ public class FieldPlayer : Actor {
public override StatsManager Stats => Session.Stats;
public override BuffManager Buffs => Session.Buffs;
+ public override AnimationManager Animation => Session.Animation;
public override IPrism Shape => new Prism(new Circle(new Vector2(Position.X, Position.Y), 10), Position.Z, 100);
private ActorState state;
public ActorState State {
@@ -87,7 +88,7 @@ public DeathState DeathState {
private readonly EventQueue scheduler;
- public FieldPlayer(GameSession session, Player player) : base(session.Field, player.ObjectId, player, GetPlayerModel(player.Character.Gender), session.NpcMetadata) {
+ public FieldPlayer(GameSession session, Player player) : base(session.Field, player.ObjectId, player, session.NpcMetadata) {
Session = session;
regenStats = new Dictionary>();
@@ -101,14 +102,6 @@ public FieldPlayer(GameSession session, Player player) : base(session.Field, pla
scheduler.Start();
}
- private static string GetPlayerModel(Gender gender) {
- return gender switch {
- Gender.Male => "male",
- Gender.Female => "female",
- _ => "male"
- };
- }
-
protected override void Dispose(bool disposing) {
scheduler.Stop();
}
@@ -192,6 +185,9 @@ public override void Update(long tickCount) {
}
Session.GameEvent.Update(tickCount);
+
+ //Console.WriteLine($"Playing sequence: {AnimationState.PlayingSequence?.Name ?? "null"}");
+ Console.WriteLine($"Current Animation: {Animation.Current?.Sequence?.Name ?? "null"} Skill: {Animation.Current?.Skill?.Id ?? 0}");
}
public void OnStateSync(StateSync stateSync) {
diff --git a/Maple2.Server.Game/Model/Field/Actor/IActor.cs b/Maple2.Server.Game/Model/Field/Actor/IActor.cs
index e7a0f0d1b..ea1a4c085 100644
--- a/Maple2.Server.Game/Model/Field/Actor/IActor.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/IActor.cs
@@ -15,7 +15,7 @@ public interface IActor : IFieldEntity {
public BuffManager Buffs { get; }
public StatsManager Stats { get; }
- public AnimationState AnimationState { get; init; }
+ public AnimationManager Animation { get; }
public SkillState SkillState { get; init; }
public bool IsDead { get; }
diff --git a/Maple2.Server.Game/Model/Field/Buff.cs b/Maple2.Server.Game/Model/Field/Buff.cs
index 35c064859..df80d7276 100644
--- a/Maple2.Server.Game/Model/Field/Buff.cs
+++ b/Maple2.Server.Game/Model/Field/Buff.cs
@@ -104,6 +104,9 @@ public virtual void Update(long tickCount) {
}
public bool UpdateEnabled(bool notifyField = true) {
+ if (Id == 11000081) {
+ Console.WriteLine($"Striker buff");
+ }
bool enabled = Metadata.Condition.Check(Caster, Owner, Owner);
if (Enabled != enabled) {
Enabled = enabled;
diff --git a/Maple2.Server.Game/PacketHandlers/SkillHandler.cs b/Maple2.Server.Game/PacketHandlers/SkillHandler.cs
index 241f85ca9..01cc16faf 100644
--- a/Maple2.Server.Game/PacketHandlers/SkillHandler.cs
+++ b/Maple2.Server.Game/PacketHandlers/SkillHandler.cs
@@ -158,7 +158,7 @@ private void HandleUse(GameSession session, IByteReader packet) {
SkillMetadataMotionProperty motion = metadata.Data.Motions.First().MotionProperty;
- session.Player.AnimationState.TryPlaySequence(motion.SequenceName, motion.SequenceSpeed, AnimationType.Skill, metadata);
+ session.Animation.TryPlaySequence(motion.SequenceName, motion.SequenceSpeed, AnimationType.Skill, metadata);
long startTick = session.Field.FieldTick;
foreach (SkillEffectMetadata effect in metadata.Data.Skills) {
@@ -335,7 +335,7 @@ private void HandleSync(GameSession session, IByteReader packet) {
return;
}
- if (session.Player.AnimationState.PlayingSequence is null) {
+ if (session.Player.Animation.PlayingSequence is null) {
Logger.Warning($"Last motion already expired on skill {skillUid}");
}
@@ -357,7 +357,7 @@ private void HandleSync(GameSession session, IByteReader packet) {
SkillMetadataMotionProperty motion = record.Metadata.Data.Motions[motionPoint].MotionProperty;
- session.Player.AnimationState.TryPlaySequence(motion.SequenceName, motion.SequenceSpeed, AnimationType.Skill);
+ session.Animation.TryPlaySequence(motion.SequenceName, motion.SequenceSpeed, AnimationType.Skill);
}
private void HandleTickSync(GameSession session, IByteReader packet) {
@@ -375,14 +375,14 @@ private void HandleTickSync(GameSession session, IByteReader packet) {
}
string skillSequence = record.Motion.MotionProperty.SequenceName;
- string playingSequence = session.Player.AnimationState.PlayingSequence?.Name ?? "";
+ string playingSequence = session.Player.Animation.PlayingSequence?.Name ?? "";
if (skillSequence != playingSequence) {
Logger.Warning($"Motion point on skill cast {skillUid} '{skillSequence}' doesn't match playing sequence '{playingSequence}'", skillUid);
return;
}
- session.Player.AnimationState.SetLoopSequence(true, true);
+ session.Player.Animation.SetLoopSequence(true, true);
}
private void HandleCancel(GameSession session, IByteReader packet) {
@@ -400,6 +400,6 @@ private void HandleCancel(GameSession session, IByteReader packet) {
session.Send(NoticePacket.Message($"Skill.Cancel: {skillUid}"));
}
- session.Player.AnimationState.CancelSequence();
+ session.Animation.CancelSequence();
}
}
diff --git a/Maple2.Server.Game/Packets/NpcControlPacket.cs b/Maple2.Server.Game/Packets/NpcControlPacket.cs
index 61d7778c2..faac7ac9f 100644
--- a/Maple2.Server.Game/Packets/NpcControlPacket.cs
+++ b/Maple2.Server.Game/Packets/NpcControlPacket.cs
@@ -46,25 +46,25 @@ private static void NpcBuffer(this PoolByteWriter buffer, FieldNpc npc, bool isT
buffer.Write(npc.Position);
buffer.WriteShort((short) (npc.Transform.RotationAnglesDegrees.Z * 10));
buffer.Write(npc.MovementState.Velocity);
- buffer.WriteShort((short) (npc.AnimationState.SequenceSpeed * 100));
+ buffer.WriteShort((short) (npc.Animation.SequenceSpeed * 100));
if (npc.Value.IsBoss) {
buffer.WriteInt(npc.BattleState.TargetId); // ObjectId of Player being targeted?
}
- short defaultSequenceId = npc.AnimationState.IdleSequenceId;
+ short defaultSequenceId = npc.Animation.IdleSequenceId;
if (isTalk) {
buffer.Write(ActorState.Talk);
buffer.WriteShort(-1);
} else {
buffer.Write(npc.MovementState.State);
- buffer.WriteShort(npc.AnimationState.PlayingSequence?.Id ?? defaultSequenceId);
+ buffer.WriteShort(npc.Animation.PlayingSequence?.Id ?? defaultSequenceId);
}
buffer.WriteShort(npc.SequenceCounter);
// Animation (-2 = Jump_A, -3 = Jump_B)
- bool isJumpSequence = (npc.AnimationState.PlayingSequence?.Id ?? -1) is ANI_JUMP_A or ANI_JUMP_B;
+ bool isJumpSequence = (npc.Animation.PlayingSequence?.Id ?? -1) is ANI_JUMP_A or ANI_JUMP_B;
if (isJumpSequence) {
bool isAbsolute = false;
diff --git a/Maple2.Server.Game/Session/GameSession.cs b/Maple2.Server.Game/Session/GameSession.cs
index b0d85c9e0..d6603ace8 100644
--- a/Maple2.Server.Game/Session/GameSession.cs
+++ b/Maple2.Server.Game/Session/GameSession.cs
@@ -100,6 +100,7 @@ public sealed partial class GameSession : Core.Network.Session {
public MarriageManager Marriage { get; set; } = null!;
public FishingManager Fishing { get; set; } = null!;
public DungeonManager Dungeon { get; set; } = null!;
+ public AnimationManager Animation { get; set; } = null!;
public GameSession(TcpClient tcpClient, GameServer server, IComponentContext context) : base(tcpClient) {
@@ -147,6 +148,7 @@ public bool EnterServer(long accountId, Guid machineId, MigrateInResponse migrat
db.Commit();
Player = new FieldPlayer(this, player);
+ Animation = new AnimationManager(this);
Currency = new CurrencyManager(this);
Mastery = new MasteryManager(this, Lua);
Stats = new StatsManager(Player, ServerTableMetadata.UserStatTable);
diff --git a/Maple2.Server.Game/Util/SkillUtils.cs b/Maple2.Server.Game/Util/SkillUtils.cs
index a38987416..672ec1f00 100644
--- a/Maple2.Server.Game/Util/SkillUtils.cs
+++ b/Maple2.Server.Game/Util/SkillUtils.cs
@@ -126,7 +126,7 @@ public static bool Check(this BeginCondition condition, IActor caster, IActor ow
!condition.DungeonGroupType.Contains(dungeonFieldManager.DungeonMetadata.GroupType))) {
return false;
}
- if (condition.ActiveSkill.Length > 0 && condition.ActiveSkill.All(id => owner.AnimationState.Current?.Skill?.Id != id)) {
+ if (condition.ActiveSkill.Length > 0 && condition.ActiveSkill.All(id => owner.Animation.Current?.Skill?.Id != id)) {
return false;
}
}
From 916e9523c8eec323f36a0b7adba7c680d66c4c25 Mon Sep 17 00:00:00 2001
From: Zin <62830952+Zintixx@users.noreply.github.com>
Date: Wed, 9 Apr 2025 22:16:43 -0700
Subject: [PATCH 3/9] Fix reset buff casters and owners
---
Maple2.Server.Game/Manager/Config/BuffManager.cs | 3 +++
Maple2.Server.Game/Model/Field/Actor/Actor.cs | 2 +-
.../Model/Field/Actor/FieldPlayer.cs | 5 +----
Maple2.Server.Game/Model/Field/Buff.cs | 16 +++++++++++-----
4 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/Maple2.Server.Game/Manager/Config/BuffManager.cs b/Maple2.Server.Game/Manager/Config/BuffManager.cs
index 7065bce43..4eebe79fd 100644
--- a/Maple2.Server.Game/Manager/Config/BuffManager.cs
+++ b/Maple2.Server.Game/Manager/Config/BuffManager.cs
@@ -49,6 +49,9 @@ public void Initialize() {
public void ResetActor(IActor actor) {
Actor = actor;
+ foreach ((int id, Buff buff) in Buffs) {
+ buff.ResetActor(actor);
+ }
}
public void LoadFieldBuffs() {
diff --git a/Maple2.Server.Game/Model/Field/Actor/Actor.cs b/Maple2.Server.Game/Model/Field/Actor/Actor.cs
index d8f39f6cf..1e7e37a24 100644
--- a/Maple2.Server.Game/Model/Field/Actor/Actor.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/Actor.cs
@@ -40,7 +40,7 @@ public virtual Vector3 Rotation {
set => Transform.RotationAnglesDegrees = value;
}
public Transform Transform { get; init; }
- public virtual AnimationManager Animation { get; }
+ public AnimationManager Animation { get; init; }
public SkillState SkillState { get; init; }
public virtual bool IsDead { get; protected set; }
diff --git a/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs b/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs
index 4a3913655..d04f29ec2 100644
--- a/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/FieldPlayer.cs
@@ -18,7 +18,6 @@ public class FieldPlayer : Actor {
public override StatsManager Stats => Session.Stats;
public override BuffManager Buffs => Session.Buffs;
- public override AnimationManager Animation => Session.Animation;
public override IPrism Shape => new Prism(new Circle(new Vector2(Position.X, Position.Y), 10), Position.Z, 100);
private ActorState state;
public ActorState State {
@@ -90,6 +89,7 @@ public DeathState DeathState {
public FieldPlayer(GameSession session, Player player) : base(session.Field, player.ObjectId, player, session.NpcMetadata) {
Session = session;
+ Animation = Session.Animation;
regenStats = new Dictionary>();
lastRegenTime = new Dictionary();
@@ -185,9 +185,6 @@ public override void Update(long tickCount) {
}
Session.GameEvent.Update(tickCount);
-
- //Console.WriteLine($"Playing sequence: {AnimationState.PlayingSequence?.Name ?? "null"}");
- Console.WriteLine($"Current Animation: {Animation.Current?.Sequence?.Name ?? "null"} Skill: {Animation.Current?.Skill?.Id ?? 0}");
}
public void OnStateSync(StateSync stateSync) {
diff --git a/Maple2.Server.Game/Model/Field/Buff.cs b/Maple2.Server.Game/Model/Field/Buff.cs
index df80d7276..44e8dae92 100644
--- a/Maple2.Server.Game/Model/Field/Buff.cs
+++ b/Maple2.Server.Game/Model/Field/Buff.cs
@@ -16,8 +16,8 @@ public class Buff : IUpdatable, IByteSerializable {
public readonly int ObjectId;
public long CastUid { get; set; }
- public readonly IActor Caster;
- public readonly IActor Owner;
+ public IActor Caster { get; private set; }
+ public IActor Owner { get; private set; }
public int Id => Metadata.Id;
public short Level => Metadata.Level;
@@ -55,6 +55,15 @@ public Buff(AdditionalEffectMetadata metadata, int objectId, IActor caster, IAct
canExpire = metadata.Property.KeepCondition != BuffKeepCondition.UnlimitedDuration && EndTick >= startTick;
}
+ public void ResetActor(IActor actor) {
+ if (actor.ObjectId == Caster.ObjectId) {
+ Caster = actor;
+ }
+ if (actor.ObjectId == Owner.ObjectId) {
+ Owner = actor;
+ }
+ }
+
public bool Stack(long startTick, int amount = 1, int durationMs = 0) {
Stacks = Math.Min(Stacks + amount, Metadata.Property.MaxCount);
StartTick = startTick;
@@ -104,9 +113,6 @@ public virtual void Update(long tickCount) {
}
public bool UpdateEnabled(bool notifyField = true) {
- if (Id == 11000081) {
- Console.WriteLine($"Striker buff");
- }
bool enabled = Metadata.Condition.Check(Caster, Owner, Owner);
if (Enabled != enabled) {
Enabled = enabled;
From afbc4698200537a61021debac207e4a4189ad339 Mon Sep 17 00:00:00 2001
From: Zin <62830952+Zintixx@users.noreply.github.com>
Date: Wed, 9 Apr 2025 22:19:14 -0700
Subject: [PATCH 4/9] remove file
---
.../ActorStateComponent/AnimationState.cs | 352 ------------------
1 file changed, 352 deletions(-)
delete mode 100644 .github/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
diff --git a/.github/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs b/.github/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
deleted file mode 100644
index 5a340463b..000000000
--- a/.github/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationState.cs
+++ /dev/null
@@ -1,352 +0,0 @@
-using Maple2.Model.Metadata;
-using Maple2.Server.Core.Packets;
-using Maple2.Server.Game.Model.Enum;
-
-namespace Maple2.Server.Game.Model.ActorStateComponent;
-
-///
-/// Manages animation sequences for actors in the game.
-///
-public class AnimationState {
- private readonly IActor actor;
- private AnimationRecord? current;
- private AnimationRecord? queued;
-
- public readonly AnimationMetadata? RigMetadata;
- public AnimationSequenceMetadata? PlayingSequence => current?.Sequence;
- public short IdleSequenceId { get; init; }
- public float SequenceSpeed => current?.Speed ?? 1.0f;
-
- private bool isHandlingKeyframe;
- private bool IsPlayerAnimation => actor is FieldPlayer;
- public float MoveSpeed { get; set; } = 1;
- public float AttackSpeed { get; set; } = 1;
- private float lastSequenceTime;
- private float sequenceEnd;
- private LoopData sequenceLoop;
- private long lastTick;
- private long sequenceEndTick;
- private long sequenceLoopEndTick;
-
- private bool debugPrintAnimations;
- public bool DebugPrintAnimations {
- get { return debugPrintAnimations; }
- set {
- if (actor is FieldPlayer) {
- debugPrintAnimations = value;
- }
- }
- }
-
- ///
- /// Initializes a new instance of the AnimationState class.
- ///
- /// The actor this animation state belongs to
- /// The model name to load animations for
- public AnimationState(IActor actor, string modelName) {
- this.actor = actor;
-
- RigMetadata = actor.NpcMetadata?.GetAnimation(modelName);
- MoveSpeed = 1;
- AttackSpeed = 1;
- sequenceLoop = new LoopData(0, 0);
-
- if (RigMetadata is null) {
- IdleSequenceId = 0;
- return;
- }
-
- string idleName = "Idle_A";
- if (actor is FieldNpc npc) {
- idleName = npc.Value.Metadata.Action.Actions.FirstOrDefault()?.Name ?? idleName;
- IdleSequenceId = RigMetadata.Sequences.FirstOrDefault(sequence => sequence.Key.Contains(idleName)).Value.Id;
- return;
- }
- IdleSequenceId = RigMetadata.Sequences.FirstOrDefault(sequence => sequence.Key == idleName).Value.Id;
- }
-
- ///
- /// Resets the current animation sequence.
- ///
- private void ResetSequence() {
- if (current?.Sequence != null && actor is FieldNpc npc) {
- npc.SendControl = true;
- }
- current = null;
- lastSequenceTime = 0;
- sequenceLoop = new LoopData(0, 0);
- lastTick = 0;
- sequenceEnd = 0;
- }
-
- ///
- /// Attempts to play an animation sequence with the specified name, speed, and type.
- ///
- /// The name of the animation sequence to play
- /// The speed at which to play the animation
- /// The type of animation (Move, Skill, or Misc)
- /// True if the sequence was found and started (or queued), false otherwise
- public bool TryPlaySequence(string name, float speed, AnimationType type) {
- // Can't play animations without metadata
- if (RigMetadata is null || !RigMetadata.Sequences.TryGetValue(name, out AnimationSequenceMetadata? sequence)) {
- DebugPrint($"Attempt to play nonexistent sequence '{name}' at x{speed} speed, previous: '{PlayingSequence?.Name ?? "none"}' x{SequenceSpeed}");
- ResetSequence();
- return false;
- }
-
- // If we're currently processing a keyframe event, queue this sequence for later
- if (isHandlingKeyframe) {
- queued = new AnimationRecord(sequence, speed, type);
- return true;
- }
-
- // Play the sequence immediately
- PlaySequence(sequence, speed, type);
- return true;
- }
-
- ///
- /// Plays the specified animation sequence.
- ///
- /// The animation sequence to play
- /// The speed at which to play the animation
- /// The type of animation (Move, Skill, or Misc)
- private void PlaySequence(AnimationSequenceMetadata sequenceMetadata, float speed, AnimationType type) {
- // For NPCs, set SendControl flag when changing sequences
- if (current?.Sequence != sequenceMetadata && actor is FieldNpc npc) {
- npc.SendControl = true;
- }
-
- // Log the sequence change
- DebugPrint($"Playing sequence '{sequenceMetadata.Name}' at x{speed} speed, previous: '{PlayingSequence?.Name ?? "none"}' x{SequenceSpeed}");
-
- // Reset current sequence state
- ResetSequence();
-
- // Set the new sequence properties
- current = new AnimationRecord(sequenceMetadata, speed, type);
-
- // Start tracking from current tick
- lastTick = actor.Field.FieldTick;
- }
-
- ///
- /// Cancels the currently playing animation sequence.
- ///
- public void CancelSequence() {
- // Log the cancellation if a sequence is playing
- if (PlayingSequence is not null) {
- DebugPrint($"Canceled playing sequence: '{PlayingSequence.Name}' x{SequenceSpeed}");
- }
-
- // If we're processing a keyframe event, queue the reset for later
- if (isHandlingKeyframe) {
- queued = null;
- return;
- }
-
- // Reset the sequence state
- ResetSequence();
- }
-
- ///
- /// Updates the animation state based on the current tick count.
- ///
- /// The current server tick count
- public void Update(long tickCount) {
- // Skip update if no animation metadata is available
- if (RigMetadata is null) {
- return;
- }
-
- // Reset if no valid sequence is playing
- if (PlayingSequence?.Keys.Count == 0) {
- ResetSequence();
- return;
- }
-
- // Calculate the current sequence time
- float sequenceSpeedModifier = current?.Type switch {
- AnimationType.Move => MoveSpeed,
- AnimationType.Skill => AttackSpeed,
- _ => 1,
- };
-
- long lastServerTick = lastTick == 0 ? tickCount : lastTick;
- float speed = SequenceSpeed * sequenceSpeedModifier / 1000;
- float delta = (float)(tickCount - lastServerTick) * speed;
- float sequenceTime = lastSequenceTime + delta;
-
- // Process keyframe events
- if (PlayingSequence?.Keys != null) {
- foreach (AnimationKey key in PlayingSequence.Keys) {
- if (HasHitKeyframe(sequenceTime, key)) {
- HitKeyframe(sequenceTime, key, speed);
- }
- }
- }
-
- // Handle sequence looping
- if (current?.IsLooping == true && sequenceLoop.end != 0 && sequenceTime > sequenceLoop.end) {
- if (!IsPlayerAnimation || tickCount <= sequenceLoopEndTick + Constant.ClientGraceTimeTick) {
- if (current.LoopOnlyOnce) {
- current.IsLooping = false;
- current.LoopOnlyOnce = false;
- }
-
- sequenceTime -= sequenceLoop.end - sequenceLoop.start;
- lastSequenceTime = sequenceTime - Math.Max(delta, sequenceTime - sequenceLoop.end + 0.001f);
-
- // Play all keyframe events from loopstart to current
- if (PlayingSequence?.Keys != null) {
- foreach (AnimationKey key in PlayingSequence.Keys) {
- if (HasHitKeyframe(sequenceTime, key)) {
- HitKeyframe(sequenceTime, key, speed);
- }
- }
- }
- }
- }
-
- // Check for sequence end
- if (sequenceEnd != 0 && sequenceTime > sequenceEnd) {
- if (!IsPlayerAnimation || tickCount <= sequenceEndTick + Constant.ClientGraceTimeTick) {
- ResetSequence();
- }
- }
-
- // Update timing state
- lastTick = tickCount;
- lastSequenceTime = sequenceTime;
- isHandlingKeyframe = false;
-
- // Process queued actions
- if (queued != null) {
- PlaySequence(queued.Sequence!, queued.Speed, queued.Type);
- queued = null;
- }
- }
-
- ///
- /// Sets whether the current sequence should loop.
- ///
- /// Whether the sequence should loop
- /// Whether the sequence should only loop once
- public void SetLoopSequence(bool shouldLoop, bool loopOnlyOnce) {
- if (current is null) {
- return;
- }
-
- current.IsLooping = shouldLoop;
- current.LoopOnlyOnce = loopOnlyOnce;
- }
-
- ///
- /// Determines if a keyframe has been hit in the current update.
- ///
- /// The current sequence time
- /// The keyframe to check
- /// True if the keyframe has been hit, false otherwise
- private bool HasHitKeyframe(float sequenceTime, AnimationKey key) {
- bool keyBeforeLoop = !current?.IsLooping ?? true || sequenceLoop.end == 0 || key.Time <= sequenceLoop.end + 0.001f;
- bool hitKeySinceLastTick = key.Time > lastSequenceTime && key.Time <= sequenceTime;
-
- return keyBeforeLoop && hitKeySinceLastTick;
- }
-
- ///
- /// Gets the normalized time within a segment defined by two keyframes.
- ///
- /// The name of the first keyframe
- /// The name of the second keyframe
- /// A value between 0 and 1 representing the position within the segment, or -1 if not in the segment
- public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
- if (PlayingSequence is null) {
- return -1;
- }
-
- float keyframe1Time = -1;
- float keyframe2Time = -1;
-
- foreach (AnimationKey key in PlayingSequence.Keys) {
- if (key.Name == keyframe1) {
- keyframe1Time = key.Time;
- }
-
- if (key.Name == keyframe2) {
- keyframe2Time = key.Time;
- break;
- }
- }
-
- // Segment doesn't exist or is malformed
- if (keyframe1Time == -1 || keyframe2Time == -1 || keyframe1Time > keyframe2Time) {
- return -1;
- }
-
- // Current time out of segment
- if (lastSequenceTime < keyframe1Time || lastSequenceTime >= keyframe2Time) {
- return -1;
- }
-
- if (keyframe1Time == keyframe2Time) {
- // Can only be in the segment, and at the end, or not in the segment
- return lastSequenceTime == keyframe1Time ? 1 : -1;
- }
-
- return (lastSequenceTime - keyframe1Time) / (keyframe2Time - keyframe1Time);
- }
-
- ///
- /// Processes a keyframe event.
- ///
- /// The current sequence time
- /// The keyframe that was hit
- /// The current animation speed
- private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
- isHandlingKeyframe = true;
-
- DebugPrint($"Sequence '{PlayingSequence!.Name}' keyframe event '{key.Name}'");
-
- actor.KeyframeEvent(key.Name);
-
- switch (key.Name) {
- case "loopstart":
- sequenceLoop = new LoopData(key.Time, 0);
- break;
- case "loopend":
- sequenceLoop = new LoopData(sequenceLoop.start, key.Time);
- sequenceLoopEndTick = actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
- break;
- case "end":
- sequenceEnd = key.Time;
- sequenceEndTick = actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
- break;
- default:
- break;
- }
- }
-
- ///
- /// Prints a debug message if debug printing is enabled.
- ///
- /// The message to print
- private void DebugPrint(string message) {
- if (debugPrintAnimations && actor is FieldPlayer player) {
- player.Session.Send(NoticePacket.Message(message));
- }
- }
-
- ///
- /// Represents a loop section in an animation sequence
- ///
- private struct LoopData {
- public float start;
- public float end;
-
- public LoopData(float start, float end) {
- this.start = start;
- this.end = end;
- }
- }
-}
From 822880a878f568306f58e7ec5c7c5adbebb25f40 Mon Sep 17 00:00:00 2001
From: Zin <62830952+Zintixx@users.noreply.github.com>
Date: Wed, 9 Apr 2025 22:19:54 -0700
Subject: [PATCH 5/9] update nuget for Maple2.File.Parser
---
Maple2.File.Ingest/Maple2.File.Ingest.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Maple2.File.Ingest/Maple2.File.Ingest.csproj b/Maple2.File.Ingest/Maple2.File.Ingest.csproj
index 346a1d178..57419acf2 100644
--- a/Maple2.File.Ingest/Maple2.File.Ingest.csproj
+++ b/Maple2.File.Ingest/Maple2.File.Ingest.csproj
@@ -19,7 +19,7 @@
-
+
From 1301c9a4ab454a4802bdeb2b9954ef3c1141b6e7 Mon Sep 17 00:00:00 2001
From: Zin <62830952+Zintixx@users.noreply.github.com>
Date: Wed, 9 Apr 2025 22:32:10 -0700
Subject: [PATCH 6/9] Remove added func
---
Maple2.Server.Game/Model/Skill/SkillQueue.cs | 4 ----
1 file changed, 4 deletions(-)
diff --git a/Maple2.Server.Game/Model/Skill/SkillQueue.cs b/Maple2.Server.Game/Model/Skill/SkillQueue.cs
index 67ec12feb..19075ab8a 100644
--- a/Maple2.Server.Game/Model/Skill/SkillQueue.cs
+++ b/Maple2.Server.Game/Model/Skill/SkillQueue.cs
@@ -40,10 +40,6 @@ public void Remove(long uid) {
}
}
- public bool Contains(int skillId) {
- return casts.Any(cast => cast?.SkillId == skillId);
- }
-
public void Clear() {
for (int i = 0; i < MAX_PENDING; i++) {
casts[i] = null;
From 1740392af00fd45835550062d8480322f6f280eb Mon Sep 17 00:00:00 2001
From: Zin <62830952+Zintixx@users.noreply.github.com>
Date: Wed, 9 Apr 2025 22:34:05 -0700
Subject: [PATCH 7/9] Rabbit comments
---
Maple2.File.Ingest/MapperExtensions.cs | 6 ------
.../Field/Actor/ActorStateComponent/AnimationRecord.cs | 4 ++--
Maple2.Server.Game/Util/SkillUtils.cs | 5 +++--
3 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/Maple2.File.Ingest/MapperExtensions.cs b/Maple2.File.Ingest/MapperExtensions.cs
index 5ac806e08..16ba0989a 100644
--- a/Maple2.File.Ingest/MapperExtensions.cs
+++ b/Maple2.File.Ingest/MapperExtensions.cs
@@ -319,12 +319,6 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
}
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
- List dungeonGroupType = [];
- foreach (Parser.Xml.Skill.BeginCondition.DungeonRoomGroupTypes type in beginCondition.requireDungeonRoomGroupTypes) {
- if (Enum.TryParse(type.type, true, out DungeonGroupType groupType)) {
- dungeonGroupType.Add(groupType);
- }
- }
return new BeginCondition(
Level: beginCondition.level,
Gender: (Gender) beginCondition.gender,
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs
index a6c0a1bf8..df6f19aef 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs
@@ -4,7 +4,7 @@
namespace Maple2.Server.Game.Model.ActorStateComponent;
public class AnimationRecord {
- public AnimationSequenceMetadata? Sequence { get; set; }
+ public AnimationSequenceMetadata? Sequence { get; private set; }
public float Speed { get; set; }
public AnimationType Type { get; set; }
public float LastTime { get; set; }
@@ -14,7 +14,7 @@ public class AnimationRecord {
public long LoopEndTick { get; set; }
public bool IsLooping { get; set; }
public bool LoopOnlyOnce { get; set; }
- public SkillMetadata? Skill { get; set; }
+ public SkillMetadata? Skill { get; private set; }
public AnimationRecord() {
Speed = 1;
diff --git a/Maple2.Server.Game/Util/SkillUtils.cs b/Maple2.Server.Game/Util/SkillUtils.cs
index 672ec1f00..535069240 100644
--- a/Maple2.Server.Game/Util/SkillUtils.cs
+++ b/Maple2.Server.Game/Util/SkillUtils.cs
@@ -115,10 +115,10 @@ public static bool Check(this BeginCondition condition, IActor caster, IActor ow
if (condition.Maps.Length > 0 && !condition.Maps.Contains(caster.Field.MapId)) {
return false;
}
- if (condition.Maps.Length > 0 && !condition.MapTypes.Contains(caster.Field.Metadata.Property.Type)) {
+ if (condition.MapTypes.Length > 0 && !condition.MapTypes.Contains(caster.Field.Metadata.Property.Type)) {
return false;
}
- if (condition.Maps.Length > 0 && !condition.Continents.Contains(caster.Field.Metadata.Property.Continent)) {
+ if (condition.Continents.Length > 0 && !condition.Continents.Contains(caster.Field.Metadata.Property.Continent)) {
return false;
}
if (condition.DungeonGroupType.Length > 0 &&
@@ -165,6 +165,7 @@ private static bool Check(this BeginConditionTarget? condition, IActor target) {
foreach ((BasicAttribute attribute, float value, CompareType compare, CompareStatValueType valueType) in condition.Stat) {
float targetValue = valueType switch {
+ CompareStatValueType.CurrentPercentage when target.Stats.Values[attribute].Total == 0 => 0,
CompareStatValueType.CurrentPercentage => (float) target.Stats.Values[attribute].Current / target.Stats.Values[attribute].Total,
CompareStatValueType.TotalValue => target.Stats.Values[attribute].Total,
_ => 0,
From 4a0892390026b6c0db88e4f4ecd1720875fd3bc6 Mon Sep 17 00:00:00 2001
From: Zin <62830952+Zintixx@users.noreply.github.com>
Date: Wed, 9 Apr 2025 22:47:45 -0700
Subject: [PATCH 8/9] formatting
---
Maple2.Server.Game/Manager/AnimationManager.cs | 6 +++---
.../Field/Actor/ActorStateComponent/AnimationRecord.cs | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Maple2.Server.Game/Manager/AnimationManager.cs b/Maple2.Server.Game/Manager/AnimationManager.cs
index fcf7cde08..5fa337f08 100644
--- a/Maple2.Server.Game/Manager/AnimationManager.cs
+++ b/Maple2.Server.Game/Manager/AnimationManager.cs
@@ -221,7 +221,7 @@ public void Update(long tickCount) {
long lastServerTick = lastTick == 0 ? tickCount : lastTick;
float speed = SequenceSpeed * sequenceSpeedModifier / 1000;
- float delta = (float)(tickCount - lastServerTick) * speed;
+ float delta = (float) (tickCount - lastServerTick) * speed;
float sequenceTime = lastSequenceTime + delta;
// Process keyframe events
@@ -367,12 +367,12 @@ private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
break;
case "loopend":
Current.Loop = new AnimationRecord.LoopData(Current.Loop.start, key.Time);
- Current.LoopEndTick = Actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
+ Current.LoopEndTick = Actor.Field.FieldTick + (long) ((key.Time - sequenceTime) / speed);
sequenceLoopEndTick = Current.LoopEndTick;
break;
case "end":
Current.EndTime = key.Time;
- Current.EndTick = Actor.Field.FieldTick + (long)((key.Time - sequenceTime) / speed);
+ Current.EndTick = Actor.Field.FieldTick + (long) ((key.Time - sequenceTime) / speed);
sequenceEndTick = Current.EndTick;
break;
default:
diff --git a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs
index df6f19aef..14158341e 100644
--- a/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs
+++ b/Maple2.Server.Game/Model/Field/Actor/ActorStateComponent/AnimationRecord.cs
@@ -1,4 +1,4 @@
-using Maple2.Model.Metadata;
+using Maple2.Model.Metadata;
using Maple2.Server.Game.Model.Enum;
namespace Maple2.Server.Game.Model.ActorStateComponent;
From d945d0adba3566f101f0d52beeb4e0a0e7972ab7 Mon Sep 17 00:00:00 2001
From: Zin <62830952+Zintixx@users.noreply.github.com>
Date: Wed, 9 Apr 2025 22:48:29 -0700
Subject: [PATCH 9/9] formatting