Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Maple2.Database/Context/MetadataContext.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Extensions;
using Maple2.Database.Extensions;
using Maple2.Database.Model.Metadata;
using Maple2.Model.Game.Field;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -59,6 +59,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<FunctionCubeMetadata>(ConfigureFunctionCubeMetadata);
}

/// <summary>
/// Configures the Entity Framework Core mapping for the AdditionalEffectMetadata entity, including table name, composite primary key, and JSON conversion for complex properties.
/// </summary>
private static void ConfigureAdditionalEffectMetadata(EntityTypeBuilder<AdditionalEffectMetadata> builder) {
builder.ToTable("additional-effect");
builder.HasKey(effect => new { effect.Id, effect.Level });
Expand Down
16 changes: 15 additions & 1 deletion Maple2.File.Ingest/Mapper/AdditionalEffectMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.File.IO;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.AdditionalEffect;
using Maple2.Model.Enum;
Expand All@@ -16,6 +16,10 @@ public AdditionalEffectMapper(M2dReader xmlReader) {
parser = new AdditionalEffectParser(xmlReader);
}

/// <summary>
/// Maps parsed additional effect data into strongly typed <see cref="AdditionalEffectMetadata"/> objects.
/// </summary>
/// <returns>An enumerable of <see cref="AdditionalEffectMetadata"/> representing all parsed additional effects.</returns>
protected override IEnumerable<AdditionalEffectMetadata> Map() {
foreach ((int id, IList<AdditionalEffectData> datas) in parser.Parse()) {
foreach (AdditionalEffectData data in datas) {
Expand DownExpand Up@@ -231,6 +235,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
NotKill: dotDamage.notKill);
}

/// <summary>
/// Converts a <see cref="DotBuffProperty"/> to a <see cref="AdditionalEffectMetadataDot.DotBuff"/> if the buff ID is positive; otherwise returns null.
/// </summary>
/// <param name="dotBuff">The DOT buff property to convert.</param>
/// <returns>A <see cref="AdditionalEffectMetadataDot.DotBuff"/> instance if valid; otherwise, null.</returns>
private static AdditionalEffectMetadataDot.DotBuff? Convert(DotBuffProperty? dotBuff) {
if (dotBuff is not { buffID: > 0 }) {
return null;
Expand All@@ -239,6 +248,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
return new AdditionalEffectMetadataDot.DotBuff(Target: (SkillTargetType) dotBuff.target, Id: dotBuff.buffID, Level: dotBuff.buffLevel);
}

/// <summary>
/// Converts a <see cref="ShieldProperty"/> to an <see cref="AdditionalEffectMetadataShield"/> if shield values are positive; otherwise returns null.
/// </summary>
/// <param name="shield">The shield property to convert.</param>
/// <returns>An <see cref="AdditionalEffectMetadataShield"/> if applicable; otherwise, null.</returns>
private static AdditionalEffectMetadataShield? Convert(ShieldProperty shield) {
if (shield is { hpValue: <= 0, hpByTargetMaxHP: <= 0 }) {
return null;
Expand Down
11 changes: 10 additions & 1 deletion Maple2.File.Ingest/Mapper/SkillMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.Skill;
Expand All@@ -14,6 +14,10 @@ public SkillMapper(M2dReader xmlReader) {
parser = new SkillParser(xmlReader);
}

/// <summary>
/// Maps parsed skill XML data into structured <see cref="StoredSkillMetadata"/> objects, transforming raw skill definitions into strongly typed metadata for further processing.
/// </summary>
/// <returns>An enumerable sequence of <see cref="StoredSkillMetadata"/> representing all valid skills parsed from the source data.</returns>
protected override IEnumerable<StoredSkillMetadata> Map() {
foreach ((int id, string name, SkillData data) in parser.Parse()) {
if (data.basic == null) continue; // Old_JobChange_01
Expand DownExpand Up@@ -133,6 +137,11 @@ protected override IEnumerable<StoredSkillMetadata> Map() {
}
}

/// <summary>
/// Converts a <see cref="RegionSkill"/> object into a <see cref="SkillMetadataRange"/>, mapping region type strings to <see cref="SkillRegion"/> values and transferring relevant range properties.
/// </summary>
/// <param name="region">The region skill data to convert.</param>
/// <returns>A <see cref="SkillMetadataRange"/> representing the region's metadata.</returns>
private static SkillMetadataRange Convert(RegionSkill region) {
return new SkillMetadataRange(
Type: region.rangeType switch {
Expand Down
12 changes: 11 additions & 1 deletion Maple2.File.Ingest/MapperExtensions.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Maple2.File.Ingest.Utils;
Expand DownExpand Up@@ -227,6 +227,11 @@ public static byte OptionIndex(this SpecialAttribute attribute) {
};
}

/// <summary>
/// Converts a <see cref="TriggerSkill"/> instance into a <see cref="SkillEffectMetadata"/>, mapping its properties to either a splash or condition effect and assembling the associated skills.
/// </summary>
/// <param name="trigger">The trigger skill data to convert.</param>
/// <returns>A <see cref="SkillEffectMetadata"/> representing the trigger's effect, including splash or condition details and linked skills.</returns>
public static SkillEffectMetadata Convert(this TriggerSkill trigger) {
SkillEffectMetadataCondition? condition = null;
SkillEffectMetadataSplash? splash = null;
Expand DownExpand Up@@ -316,6 +321,11 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
UseMove: autoTargeting.autoTargetUseMove);
}

/// <summary>
/// Converts a parsed XML skill begin condition into a strongly typed <see cref="BeginCondition"/> metadata object for use in Maple2 game logic.
/// </summary>
/// <param name="beginCondition">The XML-parsed skill begin condition to convert.</param>
/// <returns>A <see cref="BeginCondition"/> instance containing mapped level, gender, mesos, stats, map and skill requirements, job codes, probability, cooldowns, durations, state flags, dungeon group types, weapon requirements, and subconditions for target, owner, and caster.</returns>
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
return new BeginCondition(
Level: beginCondition.level,
Expand Down
10 changes: 9 additions & 1 deletion Maple2.Server.Core/Packets/RequestPacket.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.PacketLib.Tools;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;

namespace Maple2.Server.Core.Packets;
Expand All@@ -8,10 +8,18 @@ public static ByteWriter Login() {
return Packet.Of(SendOp.RequestLogin);
}

/// <summary>
/// Creates a packet for requesting a session key from the server.
/// </summary>
/// <returns>A <see cref="ByteWriter"/> containing the key request packet.</returns>
public static ByteWriter Key() {
return Packet.Of(SendOp.RequestKey);
}

/// <summary>
/// Creates a heartbeat request packet containing the current system tick count.
/// </summary>
/// <returns>A ByteWriter representing the heartbeat request packet.</returns>
public static ByteWriter Heartbeat() {
var pWriter = Packet.Of(SendOp.RequestHeartbeat);
pWriter.WriteInt(Environment.TickCount);
Expand Down
13 changes: 12 additions & 1 deletion Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Maple2.Database.Storage;
Expand DownExpand Up@@ -40,6 +40,17 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

/// <summary>
/// Processes the "buff" command to add or remove a specified buff on one or more players in the current field.
/// </summary>
/// <param name="ctx">The command invocation context.</param>
/// <param name="buffId">The ID of the buff to add or remove.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="stack">The number of buff stacks to apply.</param>
/// <param name="duration">The duration of the buff in seconds, or -1 for default duration.</param>
/// <param name="all">If true, applies the operation to all players in the field.</param>
/// <param name="target">The name of the target player to affect, or empty to target the command issuer.</param>
/// <param name="remove">If true, removes the buff instead of adding it.</param>
private void Handle(InvocationContext ctx, int buffId, int level, int stack, int duration, bool all, string target, bool remove) {
try {
if (!skillStorage.TryGetEffect(buffId, (short) level, out AdditionalEffectMetadata? _)) {
Expand Down
8 changes: 7 additions & 1 deletion Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
Expand DownExpand Up@@ -141,6 +141,12 @@ private void Handle(InvocationContext ctx, string name) {
}
}

/// <summary>
/// Instantly kills the specified NPC by applying damage equal to its current health and broadcasts the resulting updates to the field.
/// </summary>
/// <param name="session">The game session performing the kill action.</param>
/// <param name="npc">The NPC to be killed.</param>
/// <param name="skill">The skill metadata used to generate the damage record.</param>
private static void Kill(GameSession session, FieldNpc npc, SkillMetadata skill) {
var damageRecord = new DamageRecord(skill, skill.Data.Motions[0].Attacks[0]) {
CasterId = session.Player.ObjectId,
Expand Down
14 changes: 11 additions & 3 deletions Maple2.Server.Game/Manager/AnimationManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Model.Enum;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Model;
Expand DownExpand Up@@ -199,7 +199,10 @@ public void CancelSequence() {
/// <summary>
/// Updates the animation state based on the current tick count.
/// </summary>
/// <param name="tickCount">The current server tick count</param>
/// <summary>
/// Updates the animation state for the actor based on the current server tick, processing keyframe events, handling looping, and resetting sequences as needed.
/// </summary>
/// <param name="tickCount">The current server tick count.</param>
public void Update(long tickCount) {
// Skip update if no animation metadata is available
if (RigMetadata is null) {
Expand DownExpand Up@@ -349,7 +352,12 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
/// </summary>
/// <param name="sequenceTime">The current sequence time</param>
/// <param name="key">The keyframe that was hit</param>
/// <param name="speed">The current animation speed</param>
/// <summary>
/// Handles a keyframe event during an animation sequence, triggering actor callbacks and updating loop or end timing based on the keyframe type.
/// </summary>
/// <param name="sequenceTime">The current normalized time within the animation sequence.</param>
/// <param name="key">The animation keyframe being processed.</param>
/// <param name="speed">The current animation speed.</param>
private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
isHandlingKeyframe = true;

Expand Down
50 changes: 49 additions & 1 deletion Maple2.Server.Game/Manager/Config/BuffManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Game;
Expand DownExpand Up@@ -61,6 +61,9 @@ public void Clear() {
}
}

/// <summary>
/// Applies entrance buffs and refreshes premium club buffs for the actor when entering a field.
/// </summary>
public void LoadFieldBuffs() {
// Lapenshards
// Game Events
Expand All@@ -71,6 +74,18 @@ public void LoadFieldBuffs() {
}
}

/// <summary>
/// Adds a buff to the specified owner, handling stacking, duration, cooldowns, group conflicts, and effect application.
/// </summary>
/// <param name="caster">The actor applying the buff.</param>
/// <param name="owner">The actor receiving the buff.</param>
/// <param name="id">The buff's skill or effect ID.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="startTick">The tick count when the buff starts.</param>
/// <param name="stacks">The number of stacks to apply (clamped to the buff's maximum).</param>
/// <param name="durationMs">The duration of the buff in milliseconds. If negative, uses the default duration.</param>
/// <param name="notifyField">Whether to broadcast the buff addition to the field.</param>
/// <param name="type">The event condition type triggering the buff application.</param>
public void AddBuff(IActor caster, IActor owner, int id, short level, long startTick, int stacks = 0, int durationMs = -1, bool notifyField = true, EventConditionType type = EventConditionType.Activate) {
if (!owner.Field.SkillMetadata.TryGetEffect(id, level, out AdditionalEffectMetadata? additionalEffect)) {
logger.Error("Invalid buff: {SkillId},{Level}", id, level);
Expand DownExpand Up@@ -310,6 +325,11 @@ public float TotalCompulsionRate(CompulsionEventType type, int skillId = 0) {
nestedCompulsionDic.Values.Where(compulsion => compulsion.SkillIds.Contains(skillId)).Sum(compulsion => compulsion.Rate);
}

/// <summary>
/// Returns the resistance value for the specified attribute, or 0 if not present.
/// </summary>
/// <param name="attribute">The attribute for which to retrieve resistance.</param>
/// <returns>The resistance value for the given attribute, or 0 if none is set.</returns>
public float GetResistance(BasicAttribute attribute) {
if (Resistances.TryGetValue(attribute, out float value)) {
return value;
Expand All@@ -318,6 +338,13 @@ public float GetResistance(BasicAttribute attribute) {
return 0;
}

/// <summary>
/// Calculates the total invoke value and rate for a specified invoke effect type, filtered by skill ID or skill group.
/// </summary>
/// <param name="invokeType">The type of invoke effect to aggregate.</param>
/// <param name="skillId">The skill ID to match against invoke effects.</param>
/// <param name="skillGroup">Optional skill group IDs to match against invoke effects.</param>
/// <returns>A tuple containing the total invoke value (as an integer) and the total invoke rate (as a float).</returns>
public (int, float) GetInvokeValues(InvokeEffectType invokeType, int skillId, params int[] skillGroup) {
if (!Invokes.TryGetValue(invokeType, out var nestedInvokeDic))
return (0, 0f);
Expand All@@ -335,6 +362,9 @@ public float GetResistance(BasicAttribute attribute) {
return ((int) value, rate);
}

/// <summary>
/// Sets the shield health for a buff based on its metadata, using either a fixed value or a percentage of the actor's maximum health.
/// </summary>
private void SetShield(Buff buff) {
if (buff.Metadata.Shield == null) {
return;
Expand DownExpand Up@@ -369,6 +399,9 @@ private void SetMount(Buff buff) {
}
}

/// <summary>
/// Applies update effects from the buff, including canceling specified buffs and resetting skill cooldowns for the actor.
/// </summary>
private void SetUpdates(Buff buff) {
if (buff.Metadata.Update.Cancel != null) {
CancelBuffs(buff, buff.Metadata.Update.Cancel);
Expand All@@ -381,6 +414,15 @@ private void SetUpdates(Buff buff) {
}
}
}
/// <summary>
/// Triggers an event for all enabled buffs, causing the owner to apply each buff's effects for the specified event type.
/// </summary>
/// <param name="caster">The actor who initiated the event.</param>
/// <param name="owner">The actor who owns the buffs.</param>
/// <param name="target">The target actor affected by the event.</param>
/// <param name="type">The event condition type that determines which effects to apply.</param>
/// <param name="skillId">Optional skill ID associated with the event.</param>
/// <param name="buffId">Optional buff ID associated with the event.</param>
public void TriggerEvent(IActor caster, IActor owner, IActor target, EventConditionType type, int skillId = 0, int buffId = 0) {
foreach (Buff buff in EnumerateBuffs()) {
if (!buff.Enabled) {
Expand DownExpand Up@@ -520,6 +562,12 @@ public void Remove(params (int id, int casterId)[] buffIds) {
}
}

/// <summary>
/// Removes all buffs with the specified ID and caster ID from the actor, updates resistances, handles related effects, and refreshes stats if necessary.
/// </summary>
/// <param name="id">The buff ID to remove.</param>
/// <param name="casterId">The object ID of the caster whose buffs should be removed.</param>
/// <returns>True if the removal process completes.</returns>
public bool Remove(int id, int casterId) {
//TODO: Check if buff is removable/should be removed
bool refreshStats = false;
Expand Down
5 changes: 4 additions & 1 deletion Maple2.Server.Game/Manager/Config/SkillManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Storage;
using Maple2.Database.Storage;
using Maple2.Model.Enum;
using Maple2.Model.Game;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -30,6 +30,9 @@ public void LoadSkillBook() {
session.Send(SkillBookPacket.Load(SkillBook));
}

/// <summary>
/// Applies all passive skill effects that target the player, updating active buffs based on the player's learned passive skills.
/// </summary>
public void UpdatePassiveBuffs(bool notifyField = true) {
// TODO: Only remove buffs that have been unlearned.
/*foreach (Buff buff in session.Player.Buffs.Buffs.Values) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
Expand DownExpand Up@@ -437,6 +437,10 @@ public void AddSkill(IActor caster, SkillEffectMetadata effect, Vector3[] points
}
}

/// <summary>
/// Adds splash skill effects from a skill record, calculating effect positions based on cube magic paths if applicable.
/// </summary>
/// <param name="record">The skill record containing caster, position, rotation, and attack metadata.</param>
public void AddSkill(SkillRecord record) {
SkillMetadataAttack attack = record.Attack;
if (!TableMetadata.MagicPathTable.Entries.TryGetValue(attack.CubeMagicPathId, out IReadOnlyList<MagicPath>? cubeMagicPaths)) {
Expand DownExpand Up@@ -474,6 +478,14 @@ public void AddSkill(SkillRecord record) {
}
}

/// <summary>
/// Returns a filtered collection of actors within the specified prisms, based on the target type and limit.
/// </summary>
/// <param name="prisms">The prisms used to filter actors by location or area.</param>
/// <param name="targetType">The type of targets to select (e.g., friendly players, hostile mobs, or hungry mobs).</param>
/// <param name="limit">The maximum number of actors to return.</param>
/// <param name="ignore">An optional collection of actors to exclude from the results.</param>
/// <returns>An enumerable of actors matching the criteria, or an empty collection if the target type is unhandled.</returns>
public IEnumerable<IActor> GetTargets(Prism[] prisms, ApplyTargetType targetType, int limit, ICollection<IActor>? ignore = null) {
switch (targetType) {
case ApplyTargetType.Friendly:
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Maple2.Database/Context/MetadataContext.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Extensions;
using Maple2.Database.Extensions;
using Maple2.Database.Model.Metadata;
using Maple2.Model.Game.Field;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -59,6 +59,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<FunctionCubeMetadata>(ConfigureFunctionCubeMetadata);
}

/// <summary>
/// Configures the Entity Framework Core mapping for the AdditionalEffectMetadata entity, including table name, composite primary key, and JSON conversion for complex properties.
/// </summary>
private static void ConfigureAdditionalEffectMetadata(EntityTypeBuilder<AdditionalEffectMetadata> builder) {
builder.ToTable("additional-effect");
builder.HasKey(effect => new { effect.Id, effect.Level });
Expand Down
16 changes: 15 additions & 1 deletion Maple2.File.Ingest/Mapper/AdditionalEffectMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.File.IO;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.AdditionalEffect;
using Maple2.Model.Enum;
Expand All@@ -16,6 +16,10 @@ public AdditionalEffectMapper(M2dReader xmlReader) {
parser = new AdditionalEffectParser(xmlReader);
}

/// <summary>
/// Maps parsed additional effect data into strongly typed <see cref="AdditionalEffectMetadata"/> objects.
/// </summary>
/// <returns>An enumerable of <see cref="AdditionalEffectMetadata"/> representing all parsed additional effects.</returns>
protected override IEnumerable<AdditionalEffectMetadata> Map() {
foreach ((int id, IList<AdditionalEffectData> datas) in parser.Parse()) {
foreach (AdditionalEffectData data in datas) {
Expand DownExpand Up@@ -231,6 +235,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
NotKill: dotDamage.notKill);
}

/// <summary>
/// Converts a <see cref="DotBuffProperty"/> to a <see cref="AdditionalEffectMetadataDot.DotBuff"/> if the buff ID is positive; otherwise returns null.
/// </summary>
/// <param name="dotBuff">The DOT buff property to convert.</param>
/// <returns>A <see cref="AdditionalEffectMetadataDot.DotBuff"/> instance if valid; otherwise, null.</returns>
private static AdditionalEffectMetadataDot.DotBuff? Convert(DotBuffProperty? dotBuff) {
if (dotBuff is not { buffID: > 0 }) {
return null;
Expand All@@ -239,6 +248,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
return new AdditionalEffectMetadataDot.DotBuff(Target: (SkillTargetType) dotBuff.target, Id: dotBuff.buffID, Level: dotBuff.buffLevel);
}

/// <summary>
/// Converts a <see cref="ShieldProperty"/> to an <see cref="AdditionalEffectMetadataShield"/> if shield values are positive; otherwise returns null.
/// </summary>
/// <param name="shield">The shield property to convert.</param>
/// <returns>An <see cref="AdditionalEffectMetadataShield"/> if applicable; otherwise, null.</returns>
private static AdditionalEffectMetadataShield? Convert(ShieldProperty shield) {
if (shield is { hpValue: <= 0, hpByTargetMaxHP: <= 0 }) {
return null;
Expand Down
11 changes: 10 additions & 1 deletion Maple2.File.Ingest/Mapper/SkillMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.Skill;
Expand All@@ -14,6 +14,10 @@ public SkillMapper(M2dReader xmlReader) {
parser = new SkillParser(xmlReader);
}

/// <summary>
/// Maps parsed skill XML data into structured <see cref="StoredSkillMetadata"/> objects, transforming raw skill definitions into strongly typed metadata for further processing.
/// </summary>
/// <returns>An enumerable sequence of <see cref="StoredSkillMetadata"/> representing all valid skills parsed from the source data.</returns>
protected override IEnumerable<StoredSkillMetadata> Map() {
foreach ((int id, string name, SkillData data) in parser.Parse()) {
if (data.basic == null) continue; // Old_JobChange_01
Expand DownExpand Up@@ -133,6 +137,11 @@ protected override IEnumerable<StoredSkillMetadata> Map() {
}
}

/// <summary>
/// Converts a <see cref="RegionSkill"/> object into a <see cref="SkillMetadataRange"/>, mapping region type strings to <see cref="SkillRegion"/> values and transferring relevant range properties.
/// </summary>
/// <param name="region">The region skill data to convert.</param>
/// <returns>A <see cref="SkillMetadataRange"/> representing the region's metadata.</returns>
private static SkillMetadataRange Convert(RegionSkill region) {
return new SkillMetadataRange(
Type: region.rangeType switch {
Expand Down
12 changes: 11 additions & 1 deletion Maple2.File.Ingest/MapperExtensions.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Maple2.File.Ingest.Utils;
Expand DownExpand Up@@ -227,6 +227,11 @@ public static byte OptionIndex(this SpecialAttribute attribute) {
};
}

/// <summary>
/// Converts a <see cref="TriggerSkill"/> instance into a <see cref="SkillEffectMetadata"/>, mapping its properties to either a splash or condition effect and assembling the associated skills.
/// </summary>
/// <param name="trigger">The trigger skill data to convert.</param>
/// <returns>A <see cref="SkillEffectMetadata"/> representing the trigger's effect, including splash or condition details and linked skills.</returns>
public static SkillEffectMetadata Convert(this TriggerSkill trigger) {
SkillEffectMetadataCondition? condition = null;
SkillEffectMetadataSplash? splash = null;
Expand DownExpand Up@@ -316,6 +321,11 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
UseMove: autoTargeting.autoTargetUseMove);
}

/// <summary>
/// Converts a parsed XML skill begin condition into a strongly typed <see cref="BeginCondition"/> metadata object for use in Maple2 game logic.
/// </summary>
/// <param name="beginCondition">The XML-parsed skill begin condition to convert.</param>
/// <returns>A <see cref="BeginCondition"/> instance containing mapped level, gender, mesos, stats, map and skill requirements, job codes, probability, cooldowns, durations, state flags, dungeon group types, weapon requirements, and subconditions for target, owner, and caster.</returns>
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
return new BeginCondition(
Level: beginCondition.level,
Expand Down
10 changes: 9 additions & 1 deletion Maple2.Server.Core/Packets/RequestPacket.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.PacketLib.Tools;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;

namespace Maple2.Server.Core.Packets;
Expand All@@ -8,10 +8,18 @@ public static ByteWriter Login() {
return Packet.Of(SendOp.RequestLogin);
}

/// <summary>
/// Creates a packet for requesting a session key from the server.
/// </summary>
/// <returns>A <see cref="ByteWriter"/> containing the key request packet.</returns>
public static ByteWriter Key() {
return Packet.Of(SendOp.RequestKey);
}

/// <summary>
/// Creates a heartbeat request packet containing the current system tick count.
/// </summary>
/// <returns>A ByteWriter representing the heartbeat request packet.</returns>
public static ByteWriter Heartbeat() {
var pWriter = Packet.Of(SendOp.RequestHeartbeat);
pWriter.WriteInt(Environment.TickCount);
Expand Down
13 changes: 12 additions & 1 deletion Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Maple2.Database.Storage;
Expand DownExpand Up@@ -40,6 +40,17 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

/// <summary>
/// Processes the "buff" command to add or remove a specified buff on one or more players in the current field.
/// </summary>
/// <param name="ctx">The command invocation context.</param>
/// <param name="buffId">The ID of the buff to add or remove.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="stack">The number of buff stacks to apply.</param>
/// <param name="duration">The duration of the buff in seconds, or -1 for default duration.</param>
/// <param name="all">If true, applies the operation to all players in the field.</param>
/// <param name="target">The name of the target player to affect, or empty to target the command issuer.</param>
/// <param name="remove">If true, removes the buff instead of adding it.</param>
private void Handle(InvocationContext ctx, int buffId, int level, int stack, int duration, bool all, string target, bool remove) {
try {
if (!skillStorage.TryGetEffect(buffId, (short) level, out AdditionalEffectMetadata? _)) {
Expand Down
8 changes: 7 additions & 1 deletion Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
Expand DownExpand Up@@ -141,6 +141,12 @@ private void Handle(InvocationContext ctx, string name) {
}
}

/// <summary>
/// Instantly kills the specified NPC by applying damage equal to its current health and broadcasts the resulting updates to the field.
/// </summary>
/// <param name="session">The game session performing the kill action.</param>
/// <param name="npc">The NPC to be killed.</param>
/// <param name="skill">The skill metadata used to generate the damage record.</param>
private static void Kill(GameSession session, FieldNpc npc, SkillMetadata skill) {
var damageRecord = new DamageRecord(skill, skill.Data.Motions[0].Attacks[0]) {
CasterId = session.Player.ObjectId,
Expand Down
14 changes: 11 additions & 3 deletions Maple2.Server.Game/Manager/AnimationManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Model.Enum;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Model;
Expand DownExpand Up@@ -199,7 +199,10 @@ public void CancelSequence() {
/// <summary>
/// Updates the animation state based on the current tick count.
/// </summary>
/// <param name="tickCount">The current server tick count</param>
/// <summary>
/// Updates the animation state for the actor based on the current server tick, processing keyframe events, handling looping, and resetting sequences as needed.
/// </summary>
/// <param name="tickCount">The current server tick count.</param>
public void Update(long tickCount) {
// Skip update if no animation metadata is available
if (RigMetadata is null) {
Expand DownExpand Up@@ -349,7 +352,12 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
/// </summary>
/// <param name="sequenceTime">The current sequence time</param>
/// <param name="key">The keyframe that was hit</param>
/// <param name="speed">The current animation speed</param>
/// <summary>
/// Handles a keyframe event during an animation sequence, triggering actor callbacks and updating loop or end timing based on the keyframe type.
/// </summary>
/// <param name="sequenceTime">The current normalized time within the animation sequence.</param>
/// <param name="key">The animation keyframe being processed.</param>
/// <param name="speed">The current animation speed.</param>
private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
isHandlingKeyframe = true;

Expand Down
50 changes: 49 additions & 1 deletion Maple2.Server.Game/Manager/Config/BuffManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Game;
Expand DownExpand Up@@ -61,6 +61,9 @@ public void Clear() {
}
}

/// <summary>
/// Applies entrance buffs and refreshes premium club buffs for the actor when entering a field.
/// </summary>
public void LoadFieldBuffs() {
// Lapenshards
// Game Events
Expand All@@ -71,6 +74,18 @@ public void LoadFieldBuffs() {
}
}

/// <summary>
/// Adds a buff to the specified owner, handling stacking, duration, cooldowns, group conflicts, and effect application.
/// </summary>
/// <param name="caster">The actor applying the buff.</param>
/// <param name="owner">The actor receiving the buff.</param>
/// <param name="id">The buff's skill or effect ID.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="startTick">The tick count when the buff starts.</param>
/// <param name="stacks">The number of stacks to apply (clamped to the buff's maximum).</param>
/// <param name="durationMs">The duration of the buff in milliseconds. If negative, uses the default duration.</param>
/// <param name="notifyField">Whether to broadcast the buff addition to the field.</param>
/// <param name="type">The event condition type triggering the buff application.</param>
public void AddBuff(IActor caster, IActor owner, int id, short level, long startTick, int stacks = 0, int durationMs = -1, bool notifyField = true, EventConditionType type = EventConditionType.Activate) {
if (!owner.Field.SkillMetadata.TryGetEffect(id, level, out AdditionalEffectMetadata? additionalEffect)) {
logger.Error("Invalid buff: {SkillId},{Level}", id, level);
Expand DownExpand Up@@ -310,6 +325,11 @@ public float TotalCompulsionRate(CompulsionEventType type, int skillId = 0) {
nestedCompulsionDic.Values.Where(compulsion => compulsion.SkillIds.Contains(skillId)).Sum(compulsion => compulsion.Rate);
}

/// <summary>
/// Returns the resistance value for the specified attribute, or 0 if not present.
/// </summary>
/// <param name="attribute">The attribute for which to retrieve resistance.</param>
/// <returns>The resistance value for the given attribute, or 0 if none is set.</returns>
public float GetResistance(BasicAttribute attribute) {
if (Resistances.TryGetValue(attribute, out float value)) {
return value;
Expand All@@ -318,6 +338,13 @@ public float GetResistance(BasicAttribute attribute) {
return 0;
}

/// <summary>
/// Calculates the total invoke value and rate for a specified invoke effect type, filtered by skill ID or skill group.
/// </summary>
/// <param name="invokeType">The type of invoke effect to aggregate.</param>
/// <param name="skillId">The skill ID to match against invoke effects.</param>
/// <param name="skillGroup">Optional skill group IDs to match against invoke effects.</param>
/// <returns>A tuple containing the total invoke value (as an integer) and the total invoke rate (as a float).</returns>
public (int, float) GetInvokeValues(InvokeEffectType invokeType, int skillId, params int[] skillGroup) {
if (!Invokes.TryGetValue(invokeType, out var nestedInvokeDic))
return (0, 0f);
Expand All@@ -335,6 +362,9 @@ public float GetResistance(BasicAttribute attribute) {
return ((int) value, rate);
}

/// <summary>
/// Sets the shield health for a buff based on its metadata, using either a fixed value or a percentage of the actor's maximum health.
/// </summary>
private void SetShield(Buff buff) {
if (buff.Metadata.Shield == null) {
return;
Expand DownExpand Up@@ -369,6 +399,9 @@ private void SetMount(Buff buff) {
}
}

/// <summary>
/// Applies update effects from the buff, including canceling specified buffs and resetting skill cooldowns for the actor.
/// </summary>
private void SetUpdates(Buff buff) {
if (buff.Metadata.Update.Cancel != null) {
CancelBuffs(buff, buff.Metadata.Update.Cancel);
Expand All@@ -381,6 +414,15 @@ private void SetUpdates(Buff buff) {
}
}
}
/// <summary>
/// Triggers an event for all enabled buffs, causing the owner to apply each buff's effects for the specified event type.
/// </summary>
/// <param name="caster">The actor who initiated the event.</param>
/// <param name="owner">The actor who owns the buffs.</param>
/// <param name="target">The target actor affected by the event.</param>
/// <param name="type">The event condition type that determines which effects to apply.</param>
/// <param name="skillId">Optional skill ID associated with the event.</param>
/// <param name="buffId">Optional buff ID associated with the event.</param>
public void TriggerEvent(IActor caster, IActor owner, IActor target, EventConditionType type, int skillId = 0, int buffId = 0) {
foreach (Buff buff in EnumerateBuffs()) {
if (!buff.Enabled) {
Expand DownExpand Up@@ -520,6 +562,12 @@ public void Remove(params (int id, int casterId)[] buffIds) {
}
}

/// <summary>
/// Removes all buffs with the specified ID and caster ID from the actor, updates resistances, handles related effects, and refreshes stats if necessary.
/// </summary>
/// <param name="id">The buff ID to remove.</param>
/// <param name="casterId">The object ID of the caster whose buffs should be removed.</param>
/// <returns>True if the removal process completes.</returns>
public bool Remove(int id, int casterId) {
//TODO: Check if buff is removable/should be removed
bool refreshStats = false;
Expand Down
5 changes: 4 additions & 1 deletion Maple2.Server.Game/Manager/Config/SkillManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Storage;
using Maple2.Database.Storage;
using Maple2.Model.Enum;
using Maple2.Model.Game;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -30,6 +30,9 @@ public void LoadSkillBook() {
session.Send(SkillBookPacket.Load(SkillBook));
}

/// <summary>
/// Applies all passive skill effects that target the player, updating active buffs based on the player's learned passive skills.
/// </summary>
public void UpdatePassiveBuffs(bool notifyField = true) {
// TODO: Only remove buffs that have been unlearned.
/*foreach (Buff buff in session.Player.Buffs.Buffs.Values) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
Expand DownExpand Up@@ -437,6 +437,10 @@ public void AddSkill(IActor caster, SkillEffectMetadata effect, Vector3[] points
}
}

/// <summary>
/// Adds splash skill effects from a skill record, calculating effect positions based on cube magic paths if applicable.
/// </summary>
/// <param name="record">The skill record containing caster, position, rotation, and attack metadata.</param>
public void AddSkill(SkillRecord record) {
SkillMetadataAttack attack = record.Attack;
if (!TableMetadata.MagicPathTable.Entries.TryGetValue(attack.CubeMagicPathId, out IReadOnlyList<MagicPath>? cubeMagicPaths)) {
Expand DownExpand Up@@ -474,6 +478,14 @@ public void AddSkill(SkillRecord record) {
}
}

/// <summary>
/// Returns a filtered collection of actors within the specified prisms, based on the target type and limit.
/// </summary>
/// <param name="prisms">The prisms used to filter actors by location or area.</param>
/// <param name="targetType">The type of targets to select (e.g., friendly players, hostile mobs, or hungry mobs).</param>
/// <param name="limit">The maximum number of actors to return.</param>
/// <param name="ignore">An optional collection of actors to exclude from the results.</param>
/// <returns>An enumerable of actors matching the criteria, or an empty collection if the target type is unhandled.</returns>
public IEnumerable<IActor> GetTargets(Prism[] prisms, ApplyTargetType targetType, int limit, ICollection<IActor>? ignore = null) {
switch (targetType) {
case ApplyTargetType.Friendly:
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Maple2.Database/Context/MetadataContext.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Extensions;
using Maple2.Database.Extensions;
using Maple2.Database.Model.Metadata;
using Maple2.Model.Game.Field;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -59,6 +59,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<FunctionCubeMetadata>(ConfigureFunctionCubeMetadata);
}

/// <summary>
/// Configures the Entity Framework Core mapping for the AdditionalEffectMetadata entity, including table name, composite primary key, and JSON conversion for complex properties.
/// </summary>
private static void ConfigureAdditionalEffectMetadata(EntityTypeBuilder<AdditionalEffectMetadata> builder) {
builder.ToTable("additional-effect");
builder.HasKey(effect => new { effect.Id, effect.Level });
Expand Down
16 changes: 15 additions & 1 deletion Maple2.File.Ingest/Mapper/AdditionalEffectMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.File.IO;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.AdditionalEffect;
using Maple2.Model.Enum;
Expand All@@ -16,6 +16,10 @@ public AdditionalEffectMapper(M2dReader xmlReader) {
parser = new AdditionalEffectParser(xmlReader);
}

/// <summary>
/// Maps parsed additional effect data into strongly typed <see cref="AdditionalEffectMetadata"/> objects.
/// </summary>
/// <returns>An enumerable of <see cref="AdditionalEffectMetadata"/> representing all parsed additional effects.</returns>
protected override IEnumerable<AdditionalEffectMetadata> Map() {
foreach ((int id, IList<AdditionalEffectData> datas) in parser.Parse()) {
foreach (AdditionalEffectData data in datas) {
Expand DownExpand Up@@ -231,6 +235,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
NotKill: dotDamage.notKill);
}

/// <summary>
/// Converts a <see cref="DotBuffProperty"/> to a <see cref="AdditionalEffectMetadataDot.DotBuff"/> if the buff ID is positive; otherwise returns null.
/// </summary>
/// <param name="dotBuff">The DOT buff property to convert.</param>
/// <returns>A <see cref="AdditionalEffectMetadataDot.DotBuff"/> instance if valid; otherwise, null.</returns>
private static AdditionalEffectMetadataDot.DotBuff? Convert(DotBuffProperty? dotBuff) {
if (dotBuff is not { buffID: > 0 }) {
return null;
Expand All@@ -239,6 +248,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
return new AdditionalEffectMetadataDot.DotBuff(Target: (SkillTargetType) dotBuff.target, Id: dotBuff.buffID, Level: dotBuff.buffLevel);
}

/// <summary>
/// Converts a <see cref="ShieldProperty"/> to an <see cref="AdditionalEffectMetadataShield"/> if shield values are positive; otherwise returns null.
/// </summary>
/// <param name="shield">The shield property to convert.</param>
/// <returns>An <see cref="AdditionalEffectMetadataShield"/> if applicable; otherwise, null.</returns>
private static AdditionalEffectMetadataShield? Convert(ShieldProperty shield) {
if (shield is { hpValue: <= 0, hpByTargetMaxHP: <= 0 }) {
return null;
Expand Down
11 changes: 10 additions & 1 deletion Maple2.File.Ingest/Mapper/SkillMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.Skill;
Expand All@@ -14,6 +14,10 @@ public SkillMapper(M2dReader xmlReader) {
parser = new SkillParser(xmlReader);
}

/// <summary>
/// Maps parsed skill XML data into structured <see cref="StoredSkillMetadata"/> objects, transforming raw skill definitions into strongly typed metadata for further processing.
/// </summary>
/// <returns>An enumerable sequence of <see cref="StoredSkillMetadata"/> representing all valid skills parsed from the source data.</returns>
protected override IEnumerable<StoredSkillMetadata> Map() {
foreach ((int id, string name, SkillData data) in parser.Parse()) {
if (data.basic == null) continue; // Old_JobChange_01
Expand DownExpand Up@@ -133,6 +137,11 @@ protected override IEnumerable<StoredSkillMetadata> Map() {
}
}

/// <summary>
/// Converts a <see cref="RegionSkill"/> object into a <see cref="SkillMetadataRange"/>, mapping region type strings to <see cref="SkillRegion"/> values and transferring relevant range properties.
/// </summary>
/// <param name="region">The region skill data to convert.</param>
/// <returns>A <see cref="SkillMetadataRange"/> representing the region's metadata.</returns>
private static SkillMetadataRange Convert(RegionSkill region) {
return new SkillMetadataRange(
Type: region.rangeType switch {
Expand Down
12 changes: 11 additions & 1 deletion Maple2.File.Ingest/MapperExtensions.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Maple2.File.Ingest.Utils;
Expand DownExpand Up@@ -227,6 +227,11 @@ public static byte OptionIndex(this SpecialAttribute attribute) {
};
}

/// <summary>
/// Converts a <see cref="TriggerSkill"/> instance into a <see cref="SkillEffectMetadata"/>, mapping its properties to either a splash or condition effect and assembling the associated skills.
/// </summary>
/// <param name="trigger">The trigger skill data to convert.</param>
/// <returns>A <see cref="SkillEffectMetadata"/> representing the trigger's effect, including splash or condition details and linked skills.</returns>
public static SkillEffectMetadata Convert(this TriggerSkill trigger) {
SkillEffectMetadataCondition? condition = null;
SkillEffectMetadataSplash? splash = null;
Expand DownExpand Up@@ -316,6 +321,11 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
UseMove: autoTargeting.autoTargetUseMove);
}

/// <summary>
/// Converts a parsed XML skill begin condition into a strongly typed <see cref="BeginCondition"/> metadata object for use in Maple2 game logic.
/// </summary>
/// <param name="beginCondition">The XML-parsed skill begin condition to convert.</param>
/// <returns>A <see cref="BeginCondition"/> instance containing mapped level, gender, mesos, stats, map and skill requirements, job codes, probability, cooldowns, durations, state flags, dungeon group types, weapon requirements, and subconditions for target, owner, and caster.</returns>
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
return new BeginCondition(
Level: beginCondition.level,
Expand Down
10 changes: 9 additions & 1 deletion Maple2.Server.Core/Packets/RequestPacket.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.PacketLib.Tools;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;

namespace Maple2.Server.Core.Packets;
Expand All@@ -8,10 +8,18 @@ public static ByteWriter Login() {
return Packet.Of(SendOp.RequestLogin);
}

/// <summary>
/// Creates a packet for requesting a session key from the server.
/// </summary>
/// <returns>A <see cref="ByteWriter"/> containing the key request packet.</returns>
public static ByteWriter Key() {
return Packet.Of(SendOp.RequestKey);
}

/// <summary>
/// Creates a heartbeat request packet containing the current system tick count.
/// </summary>
/// <returns>A ByteWriter representing the heartbeat request packet.</returns>
public static ByteWriter Heartbeat() {
var pWriter = Packet.Of(SendOp.RequestHeartbeat);
pWriter.WriteInt(Environment.TickCount);
Expand Down
13 changes: 12 additions & 1 deletion Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Maple2.Database.Storage;
Expand DownExpand Up@@ -40,6 +40,17 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

/// <summary>
/// Processes the "buff" command to add or remove a specified buff on one or more players in the current field.
/// </summary>
/// <param name="ctx">The command invocation context.</param>
/// <param name="buffId">The ID of the buff to add or remove.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="stack">The number of buff stacks to apply.</param>
/// <param name="duration">The duration of the buff in seconds, or -1 for default duration.</param>
/// <param name="all">If true, applies the operation to all players in the field.</param>
/// <param name="target">The name of the target player to affect, or empty to target the command issuer.</param>
/// <param name="remove">If true, removes the buff instead of adding it.</param>
private void Handle(InvocationContext ctx, int buffId, int level, int stack, int duration, bool all, string target, bool remove) {
try {
if (!skillStorage.TryGetEffect(buffId, (short) level, out AdditionalEffectMetadata? _)) {
Expand Down
8 changes: 7 additions & 1 deletion Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
Expand DownExpand Up@@ -141,6 +141,12 @@ private void Handle(InvocationContext ctx, string name) {
}
}

/// <summary>
/// Instantly kills the specified NPC by applying damage equal to its current health and broadcasts the resulting updates to the field.
/// </summary>
/// <param name="session">The game session performing the kill action.</param>
/// <param name="npc">The NPC to be killed.</param>
/// <param name="skill">The skill metadata used to generate the damage record.</param>
private static void Kill(GameSession session, FieldNpc npc, SkillMetadata skill) {
var damageRecord = new DamageRecord(skill, skill.Data.Motions[0].Attacks[0]) {
CasterId = session.Player.ObjectId,
Expand Down
14 changes: 11 additions & 3 deletions Maple2.Server.Game/Manager/AnimationManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Model.Enum;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Model;
Expand DownExpand Up@@ -199,7 +199,10 @@ public void CancelSequence() {
/// <summary>
/// Updates the animation state based on the current tick count.
/// </summary>
/// <param name="tickCount">The current server tick count</param>
/// <summary>
/// Updates the animation state for the actor based on the current server tick, processing keyframe events, handling looping, and resetting sequences as needed.
/// </summary>
/// <param name="tickCount">The current server tick count.</param>
public void Update(long tickCount) {
// Skip update if no animation metadata is available
if (RigMetadata is null) {
Expand DownExpand Up@@ -349,7 +352,12 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
/// </summary>
/// <param name="sequenceTime">The current sequence time</param>
/// <param name="key">The keyframe that was hit</param>
/// <param name="speed">The current animation speed</param>
/// <summary>
/// Handles a keyframe event during an animation sequence, triggering actor callbacks and updating loop or end timing based on the keyframe type.
/// </summary>
/// <param name="sequenceTime">The current normalized time within the animation sequence.</param>
/// <param name="key">The animation keyframe being processed.</param>
/// <param name="speed">The current animation speed.</param>
private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
isHandlingKeyframe = true;

Expand Down
50 changes: 49 additions & 1 deletion Maple2.Server.Game/Manager/Config/BuffManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Game;
Expand DownExpand Up@@ -61,6 +61,9 @@ public void Clear() {
}
}

/// <summary>
/// Applies entrance buffs and refreshes premium club buffs for the actor when entering a field.
/// </summary>
public void LoadFieldBuffs() {
// Lapenshards
// Game Events
Expand All@@ -71,6 +74,18 @@ public void LoadFieldBuffs() {
}
}

/// <summary>
/// Adds a buff to the specified owner, handling stacking, duration, cooldowns, group conflicts, and effect application.
/// </summary>
/// <param name="caster">The actor applying the buff.</param>
/// <param name="owner">The actor receiving the buff.</param>
/// <param name="id">The buff's skill or effect ID.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="startTick">The tick count when the buff starts.</param>
/// <param name="stacks">The number of stacks to apply (clamped to the buff's maximum).</param>
/// <param name="durationMs">The duration of the buff in milliseconds. If negative, uses the default duration.</param>
/// <param name="notifyField">Whether to broadcast the buff addition to the field.</param>
/// <param name="type">The event condition type triggering the buff application.</param>
public void AddBuff(IActor caster, IActor owner, int id, short level, long startTick, int stacks = 0, int durationMs = -1, bool notifyField = true, EventConditionType type = EventConditionType.Activate) {
if (!owner.Field.SkillMetadata.TryGetEffect(id, level, out AdditionalEffectMetadata? additionalEffect)) {
logger.Error("Invalid buff: {SkillId},{Level}", id, level);
Expand DownExpand Up@@ -310,6 +325,11 @@ public float TotalCompulsionRate(CompulsionEventType type, int skillId = 0) {
nestedCompulsionDic.Values.Where(compulsion => compulsion.SkillIds.Contains(skillId)).Sum(compulsion => compulsion.Rate);
}

/// <summary>
/// Returns the resistance value for the specified attribute, or 0 if not present.
/// </summary>
/// <param name="attribute">The attribute for which to retrieve resistance.</param>
/// <returns>The resistance value for the given attribute, or 0 if none is set.</returns>
public float GetResistance(BasicAttribute attribute) {
if (Resistances.TryGetValue(attribute, out float value)) {
return value;
Expand All@@ -318,6 +338,13 @@ public float GetResistance(BasicAttribute attribute) {
return 0;
}

/// <summary>
/// Calculates the total invoke value and rate for a specified invoke effect type, filtered by skill ID or skill group.
/// </summary>
/// <param name="invokeType">The type of invoke effect to aggregate.</param>
/// <param name="skillId">The skill ID to match against invoke effects.</param>
/// <param name="skillGroup">Optional skill group IDs to match against invoke effects.</param>
/// <returns>A tuple containing the total invoke value (as an integer) and the total invoke rate (as a float).</returns>
public (int, float) GetInvokeValues(InvokeEffectType invokeType, int skillId, params int[] skillGroup) {
if (!Invokes.TryGetValue(invokeType, out var nestedInvokeDic))
return (0, 0f);
Expand All@@ -335,6 +362,9 @@ public float GetResistance(BasicAttribute attribute) {
return ((int) value, rate);
}

/// <summary>
/// Sets the shield health for a buff based on its metadata, using either a fixed value or a percentage of the actor's maximum health.
/// </summary>
private void SetShield(Buff buff) {
if (buff.Metadata.Shield == null) {
return;
Expand DownExpand Up@@ -369,6 +399,9 @@ private void SetMount(Buff buff) {
}
}

/// <summary>
/// Applies update effects from the buff, including canceling specified buffs and resetting skill cooldowns for the actor.
/// </summary>
private void SetUpdates(Buff buff) {
if (buff.Metadata.Update.Cancel != null) {
CancelBuffs(buff, buff.Metadata.Update.Cancel);
Expand All@@ -381,6 +414,15 @@ private void SetUpdates(Buff buff) {
}
}
}
/// <summary>
/// Triggers an event for all enabled buffs, causing the owner to apply each buff's effects for the specified event type.
/// </summary>
/// <param name="caster">The actor who initiated the event.</param>
/// <param name="owner">The actor who owns the buffs.</param>
/// <param name="target">The target actor affected by the event.</param>
/// <param name="type">The event condition type that determines which effects to apply.</param>
/// <param name="skillId">Optional skill ID associated with the event.</param>
/// <param name="buffId">Optional buff ID associated with the event.</param>
public void TriggerEvent(IActor caster, IActor owner, IActor target, EventConditionType type, int skillId = 0, int buffId = 0) {
foreach (Buff buff in EnumerateBuffs()) {
if (!buff.Enabled) {
Expand DownExpand Up@@ -520,6 +562,12 @@ public void Remove(params (int id, int casterId)[] buffIds) {
}
}

/// <summary>
/// Removes all buffs with the specified ID and caster ID from the actor, updates resistances, handles related effects, and refreshes stats if necessary.
/// </summary>
/// <param name="id">The buff ID to remove.</param>
/// <param name="casterId">The object ID of the caster whose buffs should be removed.</param>
/// <returns>True if the removal process completes.</returns>
public bool Remove(int id, int casterId) {
//TODO: Check if buff is removable/should be removed
bool refreshStats = false;
Expand Down
5 changes: 4 additions & 1 deletion Maple2.Server.Game/Manager/Config/SkillManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Storage;
using Maple2.Database.Storage;
using Maple2.Model.Enum;
using Maple2.Model.Game;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -30,6 +30,9 @@ public void LoadSkillBook() {
session.Send(SkillBookPacket.Load(SkillBook));
}

/// <summary>
/// Applies all passive skill effects that target the player, updating active buffs based on the player's learned passive skills.
/// </summary>
public void UpdatePassiveBuffs(bool notifyField = true) {
// TODO: Only remove buffs that have been unlearned.
/*foreach (Buff buff in session.Player.Buffs.Buffs.Values) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
Expand DownExpand Up@@ -437,6 +437,10 @@ public void AddSkill(IActor caster, SkillEffectMetadata effect, Vector3[] points
}
}

/// <summary>
/// Adds splash skill effects from a skill record, calculating effect positions based on cube magic paths if applicable.
/// </summary>
/// <param name="record">The skill record containing caster, position, rotation, and attack metadata.</param>
public void AddSkill(SkillRecord record) {
SkillMetadataAttack attack = record.Attack;
if (!TableMetadata.MagicPathTable.Entries.TryGetValue(attack.CubeMagicPathId, out IReadOnlyList<MagicPath>? cubeMagicPaths)) {
Expand DownExpand Up@@ -474,6 +478,14 @@ public void AddSkill(SkillRecord record) {
}
}

/// <summary>
/// Returns a filtered collection of actors within the specified prisms, based on the target type and limit.
/// </summary>
/// <param name="prisms">The prisms used to filter actors by location or area.</param>
/// <param name="targetType">The type of targets to select (e.g., friendly players, hostile mobs, or hungry mobs).</param>
/// <param name="limit">The maximum number of actors to return.</param>
/// <param name="ignore">An optional collection of actors to exclude from the results.</param>
/// <returns>An enumerable of actors matching the criteria, or an empty collection if the target type is unhandled.</returns>
public IEnumerable<IActor> GetTargets(Prism[] prisms, ApplyTargetType targetType, int limit, ICollection<IActor>? ignore = null) {
switch (targetType) {
case ApplyTargetType.Friendly:
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Maple2.Database/Context/MetadataContext.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Extensions;
using Maple2.Database.Extensions;
using Maple2.Database.Model.Metadata;
using Maple2.Model.Game.Field;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -59,6 +59,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<FunctionCubeMetadata>(ConfigureFunctionCubeMetadata);
}

/// <summary>
/// Configures the Entity Framework Core mapping for the AdditionalEffectMetadata entity, including table name, composite primary key, and JSON conversion for complex properties.
/// </summary>
private static void ConfigureAdditionalEffectMetadata(EntityTypeBuilder<AdditionalEffectMetadata> builder) {
builder.ToTable("additional-effect");
builder.HasKey(effect => new { effect.Id, effect.Level });
Expand Down
16 changes: 15 additions & 1 deletion Maple2.File.Ingest/Mapper/AdditionalEffectMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.File.IO;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.AdditionalEffect;
using Maple2.Model.Enum;
Expand All@@ -16,6 +16,10 @@ public AdditionalEffectMapper(M2dReader xmlReader) {
parser = new AdditionalEffectParser(xmlReader);
}

/// <summary>
/// Maps parsed additional effect data into strongly typed <see cref="AdditionalEffectMetadata"/> objects.
/// </summary>
/// <returns>An enumerable of <see cref="AdditionalEffectMetadata"/> representing all parsed additional effects.</returns>
protected override IEnumerable<AdditionalEffectMetadata> Map() {
foreach ((int id, IList<AdditionalEffectData> datas) in parser.Parse()) {
foreach (AdditionalEffectData data in datas) {
Expand DownExpand Up@@ -231,6 +235,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
NotKill: dotDamage.notKill);
}

/// <summary>
/// Converts a <see cref="DotBuffProperty"/> to a <see cref="AdditionalEffectMetadataDot.DotBuff"/> if the buff ID is positive; otherwise returns null.
/// </summary>
/// <param name="dotBuff">The DOT buff property to convert.</param>
/// <returns>A <see cref="AdditionalEffectMetadataDot.DotBuff"/> instance if valid; otherwise, null.</returns>
private static AdditionalEffectMetadataDot.DotBuff? Convert(DotBuffProperty? dotBuff) {
if (dotBuff is not { buffID: > 0 }) {
return null;
Expand All@@ -239,6 +248,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
return new AdditionalEffectMetadataDot.DotBuff(Target: (SkillTargetType) dotBuff.target, Id: dotBuff.buffID, Level: dotBuff.buffLevel);
}

/// <summary>
/// Converts a <see cref="ShieldProperty"/> to an <see cref="AdditionalEffectMetadataShield"/> if shield values are positive; otherwise returns null.
/// </summary>
/// <param name="shield">The shield property to convert.</param>
/// <returns>An <see cref="AdditionalEffectMetadataShield"/> if applicable; otherwise, null.</returns>
private static AdditionalEffectMetadataShield? Convert(ShieldProperty shield) {
if (shield is { hpValue: <= 0, hpByTargetMaxHP: <= 0 }) {
return null;
Expand Down
11 changes: 10 additions & 1 deletion Maple2.File.Ingest/Mapper/SkillMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.Skill;
Expand All@@ -14,6 +14,10 @@ public SkillMapper(M2dReader xmlReader) {
parser = new SkillParser(xmlReader);
}

/// <summary>
/// Maps parsed skill XML data into structured <see cref="StoredSkillMetadata"/> objects, transforming raw skill definitions into strongly typed metadata for further processing.
/// </summary>
/// <returns>An enumerable sequence of <see cref="StoredSkillMetadata"/> representing all valid skills parsed from the source data.</returns>
protected override IEnumerable<StoredSkillMetadata> Map() {
foreach ((int id, string name, SkillData data) in parser.Parse()) {
if (data.basic == null) continue; // Old_JobChange_01
Expand DownExpand Up@@ -133,6 +137,11 @@ protected override IEnumerable<StoredSkillMetadata> Map() {
}
}

/// <summary>
/// Converts a <see cref="RegionSkill"/> object into a <see cref="SkillMetadataRange"/>, mapping region type strings to <see cref="SkillRegion"/> values and transferring relevant range properties.
/// </summary>
/// <param name="region">The region skill data to convert.</param>
/// <returns>A <see cref="SkillMetadataRange"/> representing the region's metadata.</returns>
private static SkillMetadataRange Convert(RegionSkill region) {
return new SkillMetadataRange(
Type: region.rangeType switch {
Expand Down
12 changes: 11 additions & 1 deletion Maple2.File.Ingest/MapperExtensions.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Maple2.File.Ingest.Utils;
Expand DownExpand Up@@ -227,6 +227,11 @@ public static byte OptionIndex(this SpecialAttribute attribute) {
};
}

/// <summary>
/// Converts a <see cref="TriggerSkill"/> instance into a <see cref="SkillEffectMetadata"/>, mapping its properties to either a splash or condition effect and assembling the associated skills.
/// </summary>
/// <param name="trigger">The trigger skill data to convert.</param>
/// <returns>A <see cref="SkillEffectMetadata"/> representing the trigger's effect, including splash or condition details and linked skills.</returns>
public static SkillEffectMetadata Convert(this TriggerSkill trigger) {
SkillEffectMetadataCondition? condition = null;
SkillEffectMetadataSplash? splash = null;
Expand DownExpand Up@@ -316,6 +321,11 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
UseMove: autoTargeting.autoTargetUseMove);
}

/// <summary>
/// Converts a parsed XML skill begin condition into a strongly typed <see cref="BeginCondition"/> metadata object for use in Maple2 game logic.
/// </summary>
/// <param name="beginCondition">The XML-parsed skill begin condition to convert.</param>
/// <returns>A <see cref="BeginCondition"/> instance containing mapped level, gender, mesos, stats, map and skill requirements, job codes, probability, cooldowns, durations, state flags, dungeon group types, weapon requirements, and subconditions for target, owner, and caster.</returns>
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
return new BeginCondition(
Level: beginCondition.level,
Expand Down
10 changes: 9 additions & 1 deletion Maple2.Server.Core/Packets/RequestPacket.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.PacketLib.Tools;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;

namespace Maple2.Server.Core.Packets;
Expand All@@ -8,10 +8,18 @@ public static ByteWriter Login() {
return Packet.Of(SendOp.RequestLogin);
}

/// <summary>
/// Creates a packet for requesting a session key from the server.
/// </summary>
/// <returns>A <see cref="ByteWriter"/> containing the key request packet.</returns>
public static ByteWriter Key() {
return Packet.Of(SendOp.RequestKey);
}

/// <summary>
/// Creates a heartbeat request packet containing the current system tick count.
/// </summary>
/// <returns>A ByteWriter representing the heartbeat request packet.</returns>
public static ByteWriter Heartbeat() {
var pWriter = Packet.Of(SendOp.RequestHeartbeat);
pWriter.WriteInt(Environment.TickCount);
Expand Down
13 changes: 12 additions & 1 deletion Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Maple2.Database.Storage;
Expand DownExpand Up@@ -40,6 +40,17 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

/// <summary>
/// Processes the "buff" command to add or remove a specified buff on one or more players in the current field.
/// </summary>
/// <param name="ctx">The command invocation context.</param>
/// <param name="buffId">The ID of the buff to add or remove.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="stack">The number of buff stacks to apply.</param>
/// <param name="duration">The duration of the buff in seconds, or -1 for default duration.</param>
/// <param name="all">If true, applies the operation to all players in the field.</param>
/// <param name="target">The name of the target player to affect, or empty to target the command issuer.</param>
/// <param name="remove">If true, removes the buff instead of adding it.</param>
private void Handle(InvocationContext ctx, int buffId, int level, int stack, int duration, bool all, string target, bool remove) {
try {
if (!skillStorage.TryGetEffect(buffId, (short) level, out AdditionalEffectMetadata? _)) {
Expand Down
8 changes: 7 additions & 1 deletion Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
Expand DownExpand Up@@ -141,6 +141,12 @@ private void Handle(InvocationContext ctx, string name) {
}
}

/// <summary>
/// Instantly kills the specified NPC by applying damage equal to its current health and broadcasts the resulting updates to the field.
/// </summary>
/// <param name="session">The game session performing the kill action.</param>
/// <param name="npc">The NPC to be killed.</param>
/// <param name="skill">The skill metadata used to generate the damage record.</param>
private static void Kill(GameSession session, FieldNpc npc, SkillMetadata skill) {
var damageRecord = new DamageRecord(skill, skill.Data.Motions[0].Attacks[0]) {
CasterId = session.Player.ObjectId,
Expand Down
14 changes: 11 additions & 3 deletions Maple2.Server.Game/Manager/AnimationManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Model.Enum;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Model;
Expand DownExpand Up@@ -199,7 +199,10 @@ public void CancelSequence() {
/// <summary>
/// Updates the animation state based on the current tick count.
/// </summary>
/// <param name="tickCount">The current server tick count</param>
/// <summary>
/// Updates the animation state for the actor based on the current server tick, processing keyframe events, handling looping, and resetting sequences as needed.
/// </summary>
/// <param name="tickCount">The current server tick count.</param>
public void Update(long tickCount) {
// Skip update if no animation metadata is available
if (RigMetadata is null) {
Expand DownExpand Up@@ -349,7 +352,12 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
/// </summary>
/// <param name="sequenceTime">The current sequence time</param>
/// <param name="key">The keyframe that was hit</param>
/// <param name="speed">The current animation speed</param>
/// <summary>
/// Handles a keyframe event during an animation sequence, triggering actor callbacks and updating loop or end timing based on the keyframe type.
/// </summary>
/// <param name="sequenceTime">The current normalized time within the animation sequence.</param>
/// <param name="key">The animation keyframe being processed.</param>
/// <param name="speed">The current animation speed.</param>
private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
isHandlingKeyframe = true;

Expand Down
50 changes: 49 additions & 1 deletion Maple2.Server.Game/Manager/Config/BuffManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Game;
Expand DownExpand Up@@ -61,6 +61,9 @@ public void Clear() {
}
}

/// <summary>
/// Applies entrance buffs and refreshes premium club buffs for the actor when entering a field.
/// </summary>
public void LoadFieldBuffs() {
// Lapenshards
// Game Events
Expand All@@ -71,6 +74,18 @@ public void LoadFieldBuffs() {
}
}

/// <summary>
/// Adds a buff to the specified owner, handling stacking, duration, cooldowns, group conflicts, and effect application.
/// </summary>
/// <param name="caster">The actor applying the buff.</param>
/// <param name="owner">The actor receiving the buff.</param>
/// <param name="id">The buff's skill or effect ID.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="startTick">The tick count when the buff starts.</param>
/// <param name="stacks">The number of stacks to apply (clamped to the buff's maximum).</param>
/// <param name="durationMs">The duration of the buff in milliseconds. If negative, uses the default duration.</param>
/// <param name="notifyField">Whether to broadcast the buff addition to the field.</param>
/// <param name="type">The event condition type triggering the buff application.</param>
public void AddBuff(IActor caster, IActor owner, int id, short level, long startTick, int stacks = 0, int durationMs = -1, bool notifyField = true, EventConditionType type = EventConditionType.Activate) {
if (!owner.Field.SkillMetadata.TryGetEffect(id, level, out AdditionalEffectMetadata? additionalEffect)) {
logger.Error("Invalid buff: {SkillId},{Level}", id, level);
Expand DownExpand Up@@ -310,6 +325,11 @@ public float TotalCompulsionRate(CompulsionEventType type, int skillId = 0) {
nestedCompulsionDic.Values.Where(compulsion => compulsion.SkillIds.Contains(skillId)).Sum(compulsion => compulsion.Rate);
}

/// <summary>
/// Returns the resistance value for the specified attribute, or 0 if not present.
/// </summary>
/// <param name="attribute">The attribute for which to retrieve resistance.</param>
/// <returns>The resistance value for the given attribute, or 0 if none is set.</returns>
public float GetResistance(BasicAttribute attribute) {
if (Resistances.TryGetValue(attribute, out float value)) {
return value;
Expand All@@ -318,6 +338,13 @@ public float GetResistance(BasicAttribute attribute) {
return 0;
}

/// <summary>
/// Calculates the total invoke value and rate for a specified invoke effect type, filtered by skill ID or skill group.
/// </summary>
/// <param name="invokeType">The type of invoke effect to aggregate.</param>
/// <param name="skillId">The skill ID to match against invoke effects.</param>
/// <param name="skillGroup">Optional skill group IDs to match against invoke effects.</param>
/// <returns>A tuple containing the total invoke value (as an integer) and the total invoke rate (as a float).</returns>
public (int, float) GetInvokeValues(InvokeEffectType invokeType, int skillId, params int[] skillGroup) {
if (!Invokes.TryGetValue(invokeType, out var nestedInvokeDic))
return (0, 0f);
Expand All@@ -335,6 +362,9 @@ public float GetResistance(BasicAttribute attribute) {
return ((int) value, rate);
}

/// <summary>
/// Sets the shield health for a buff based on its metadata, using either a fixed value or a percentage of the actor's maximum health.
/// </summary>
private void SetShield(Buff buff) {
if (buff.Metadata.Shield == null) {
return;
Expand DownExpand Up@@ -369,6 +399,9 @@ private void SetMount(Buff buff) {
}
}

/// <summary>
/// Applies update effects from the buff, including canceling specified buffs and resetting skill cooldowns for the actor.
/// </summary>
private void SetUpdates(Buff buff) {
if (buff.Metadata.Update.Cancel != null) {
CancelBuffs(buff, buff.Metadata.Update.Cancel);
Expand All@@ -381,6 +414,15 @@ private void SetUpdates(Buff buff) {
}
}
}
/// <summary>
/// Triggers an event for all enabled buffs, causing the owner to apply each buff's effects for the specified event type.
/// </summary>
/// <param name="caster">The actor who initiated the event.</param>
/// <param name="owner">The actor who owns the buffs.</param>
/// <param name="target">The target actor affected by the event.</param>
/// <param name="type">The event condition type that determines which effects to apply.</param>
/// <param name="skillId">Optional skill ID associated with the event.</param>
/// <param name="buffId">Optional buff ID associated with the event.</param>
public void TriggerEvent(IActor caster, IActor owner, IActor target, EventConditionType type, int skillId = 0, int buffId = 0) {
foreach (Buff buff in EnumerateBuffs()) {
if (!buff.Enabled) {
Expand DownExpand Up@@ -520,6 +562,12 @@ public void Remove(params (int id, int casterId)[] buffIds) {
}
}

/// <summary>
/// Removes all buffs with the specified ID and caster ID from the actor, updates resistances, handles related effects, and refreshes stats if necessary.
/// </summary>
/// <param name="id">The buff ID to remove.</param>
/// <param name="casterId">The object ID of the caster whose buffs should be removed.</param>
/// <returns>True if the removal process completes.</returns>
public bool Remove(int id, int casterId) {
//TODO: Check if buff is removable/should be removed
bool refreshStats = false;
Expand Down
5 changes: 4 additions & 1 deletion Maple2.Server.Game/Manager/Config/SkillManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Storage;
using Maple2.Database.Storage;
using Maple2.Model.Enum;
using Maple2.Model.Game;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -30,6 +30,9 @@ public void LoadSkillBook() {
session.Send(SkillBookPacket.Load(SkillBook));
}

/// <summary>
/// Applies all passive skill effects that target the player, updating active buffs based on the player's learned passive skills.
/// </summary>
public void UpdatePassiveBuffs(bool notifyField = true) {
// TODO: Only remove buffs that have been unlearned.
/*foreach (Buff buff in session.Player.Buffs.Buffs.Values) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
Expand DownExpand Up@@ -437,6 +437,10 @@ public void AddSkill(IActor caster, SkillEffectMetadata effect, Vector3[] points
}
}

/// <summary>
/// Adds splash skill effects from a skill record, calculating effect positions based on cube magic paths if applicable.
/// </summary>
/// <param name="record">The skill record containing caster, position, rotation, and attack metadata.</param>
public void AddSkill(SkillRecord record) {
SkillMetadataAttack attack = record.Attack;
if (!TableMetadata.MagicPathTable.Entries.TryGetValue(attack.CubeMagicPathId, out IReadOnlyList<MagicPath>? cubeMagicPaths)) {
Expand DownExpand Up@@ -474,6 +478,14 @@ public void AddSkill(SkillRecord record) {
}
}

/// <summary>
/// Returns a filtered collection of actors within the specified prisms, based on the target type and limit.
/// </summary>
/// <param name="prisms">The prisms used to filter actors by location or area.</param>
/// <param name="targetType">The type of targets to select (e.g., friendly players, hostile mobs, or hungry mobs).</param>
/// <param name="limit">The maximum number of actors to return.</param>
/// <param name="ignore">An optional collection of actors to exclude from the results.</param>
/// <returns>An enumerable of actors matching the criteria, or an empty collection if the target type is unhandled.</returns>
public IEnumerable<IActor> GetTargets(Prism[] prisms, ApplyTargetType targetType, int limit, ICollection<IActor>? ignore = null) {
switch (targetType) {
case ApplyTargetType.Friendly:
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Maple2.Database/Context/MetadataContext.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Extensions;
using Maple2.Database.Extensions;
using Maple2.Database.Model.Metadata;
using Maple2.Model.Game.Field;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -59,6 +59,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<FunctionCubeMetadata>(ConfigureFunctionCubeMetadata);
}

/// <summary>
/// Configures the Entity Framework Core mapping for the AdditionalEffectMetadata entity, including table name, composite primary key, and JSON conversion for complex properties.
/// </summary>
private static void ConfigureAdditionalEffectMetadata(EntityTypeBuilder<AdditionalEffectMetadata> builder) {
builder.ToTable("additional-effect");
builder.HasKey(effect => new { effect.Id, effect.Level });
Expand Down
16 changes: 15 additions & 1 deletion Maple2.File.Ingest/Mapper/AdditionalEffectMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.File.IO;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.AdditionalEffect;
using Maple2.Model.Enum;
Expand All@@ -16,6 +16,10 @@ public AdditionalEffectMapper(M2dReader xmlReader) {
parser = new AdditionalEffectParser(xmlReader);
}

/// <summary>
/// Maps parsed additional effect data into strongly typed <see cref="AdditionalEffectMetadata"/> objects.
/// </summary>
/// <returns>An enumerable of <see cref="AdditionalEffectMetadata"/> representing all parsed additional effects.</returns>
protected override IEnumerable<AdditionalEffectMetadata> Map() {
foreach ((int id, IList<AdditionalEffectData> datas) in parser.Parse()) {
foreach (AdditionalEffectData data in datas) {
Expand DownExpand Up@@ -231,6 +235,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
NotKill: dotDamage.notKill);
}

/// <summary>
/// Converts a <see cref="DotBuffProperty"/> to a <see cref="AdditionalEffectMetadataDot.DotBuff"/> if the buff ID is positive; otherwise returns null.
/// </summary>
/// <param name="dotBuff">The DOT buff property to convert.</param>
/// <returns>A <see cref="AdditionalEffectMetadataDot.DotBuff"/> instance if valid; otherwise, null.</returns>
private static AdditionalEffectMetadataDot.DotBuff? Convert(DotBuffProperty? dotBuff) {
if (dotBuff is not { buffID: > 0 }) {
return null;
Expand All@@ -239,6 +248,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
return new AdditionalEffectMetadataDot.DotBuff(Target: (SkillTargetType) dotBuff.target, Id: dotBuff.buffID, Level: dotBuff.buffLevel);
}

/// <summary>
/// Converts a <see cref="ShieldProperty"/> to an <see cref="AdditionalEffectMetadataShield"/> if shield values are positive; otherwise returns null.
/// </summary>
/// <param name="shield">The shield property to convert.</param>
/// <returns>An <see cref="AdditionalEffectMetadataShield"/> if applicable; otherwise, null.</returns>
private static AdditionalEffectMetadataShield? Convert(ShieldProperty shield) {
if (shield is { hpValue: <= 0, hpByTargetMaxHP: <= 0 }) {
return null;
Expand Down
11 changes: 10 additions & 1 deletion Maple2.File.Ingest/Mapper/SkillMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.Skill;
Expand All@@ -14,6 +14,10 @@ public SkillMapper(M2dReader xmlReader) {
parser = new SkillParser(xmlReader);
}

/// <summary>
/// Maps parsed skill XML data into structured <see cref="StoredSkillMetadata"/> objects, transforming raw skill definitions into strongly typed metadata for further processing.
/// </summary>
/// <returns>An enumerable sequence of <see cref="StoredSkillMetadata"/> representing all valid skills parsed from the source data.</returns>
protected override IEnumerable<StoredSkillMetadata> Map() {
foreach ((int id, string name, SkillData data) in parser.Parse()) {
if (data.basic == null) continue; // Old_JobChange_01
Expand DownExpand Up@@ -133,6 +137,11 @@ protected override IEnumerable<StoredSkillMetadata> Map() {
}
}

/// <summary>
/// Converts a <see cref="RegionSkill"/> object into a <see cref="SkillMetadataRange"/>, mapping region type strings to <see cref="SkillRegion"/> values and transferring relevant range properties.
/// </summary>
/// <param name="region">The region skill data to convert.</param>
/// <returns>A <see cref="SkillMetadataRange"/> representing the region's metadata.</returns>
private static SkillMetadataRange Convert(RegionSkill region) {
return new SkillMetadataRange(
Type: region.rangeType switch {
Expand Down
12 changes: 11 additions & 1 deletion Maple2.File.Ingest/MapperExtensions.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Maple2.File.Ingest.Utils;
Expand DownExpand Up@@ -227,6 +227,11 @@ public static byte OptionIndex(this SpecialAttribute attribute) {
};
}

/// <summary>
/// Converts a <see cref="TriggerSkill"/> instance into a <see cref="SkillEffectMetadata"/>, mapping its properties to either a splash or condition effect and assembling the associated skills.
/// </summary>
/// <param name="trigger">The trigger skill data to convert.</param>
/// <returns>A <see cref="SkillEffectMetadata"/> representing the trigger's effect, including splash or condition details and linked skills.</returns>
public static SkillEffectMetadata Convert(this TriggerSkill trigger) {
SkillEffectMetadataCondition? condition = null;
SkillEffectMetadataSplash? splash = null;
Expand DownExpand Up@@ -316,6 +321,11 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
UseMove: autoTargeting.autoTargetUseMove);
}

/// <summary>
/// Converts a parsed XML skill begin condition into a strongly typed <see cref="BeginCondition"/> metadata object for use in Maple2 game logic.
/// </summary>
/// <param name="beginCondition">The XML-parsed skill begin condition to convert.</param>
/// <returns>A <see cref="BeginCondition"/> instance containing mapped level, gender, mesos, stats, map and skill requirements, job codes, probability, cooldowns, durations, state flags, dungeon group types, weapon requirements, and subconditions for target, owner, and caster.</returns>
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
return new BeginCondition(
Level: beginCondition.level,
Expand Down
10 changes: 9 additions & 1 deletion Maple2.Server.Core/Packets/RequestPacket.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.PacketLib.Tools;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;

namespace Maple2.Server.Core.Packets;
Expand All@@ -8,10 +8,18 @@ public static ByteWriter Login() {
return Packet.Of(SendOp.RequestLogin);
}

/// <summary>
/// Creates a packet for requesting a session key from the server.
/// </summary>
/// <returns>A <see cref="ByteWriter"/> containing the key request packet.</returns>
public static ByteWriter Key() {
return Packet.Of(SendOp.RequestKey);
}

/// <summary>
/// Creates a heartbeat request packet containing the current system tick count.
/// </summary>
/// <returns>A ByteWriter representing the heartbeat request packet.</returns>
public static ByteWriter Heartbeat() {
var pWriter = Packet.Of(SendOp.RequestHeartbeat);
pWriter.WriteInt(Environment.TickCount);
Expand Down
13 changes: 12 additions & 1 deletion Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Maple2.Database.Storage;
Expand DownExpand Up@@ -40,6 +40,17 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

/// <summary>
/// Processes the "buff" command to add or remove a specified buff on one or more players in the current field.
/// </summary>
/// <param name="ctx">The command invocation context.</param>
/// <param name="buffId">The ID of the buff to add or remove.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="stack">The number of buff stacks to apply.</param>
/// <param name="duration">The duration of the buff in seconds, or -1 for default duration.</param>
/// <param name="all">If true, applies the operation to all players in the field.</param>
/// <param name="target">The name of the target player to affect, or empty to target the command issuer.</param>
/// <param name="remove">If true, removes the buff instead of adding it.</param>
private void Handle(InvocationContext ctx, int buffId, int level, int stack, int duration, bool all, string target, bool remove) {
try {
if (!skillStorage.TryGetEffect(buffId, (short) level, out AdditionalEffectMetadata? _)) {
Expand Down
8 changes: 7 additions & 1 deletion Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
Expand DownExpand Up@@ -141,6 +141,12 @@ private void Handle(InvocationContext ctx, string name) {
}
}

/// <summary>
/// Instantly kills the specified NPC by applying damage equal to its current health and broadcasts the resulting updates to the field.
/// </summary>
/// <param name="session">The game session performing the kill action.</param>
/// <param name="npc">The NPC to be killed.</param>
/// <param name="skill">The skill metadata used to generate the damage record.</param>
private static void Kill(GameSession session, FieldNpc npc, SkillMetadata skill) {
var damageRecord = new DamageRecord(skill, skill.Data.Motions[0].Attacks[0]) {
CasterId = session.Player.ObjectId,
Expand Down
14 changes: 11 additions & 3 deletions Maple2.Server.Game/Manager/AnimationManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Model.Enum;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Model;
Expand DownExpand Up@@ -199,7 +199,10 @@ public void CancelSequence() {
/// <summary>
/// Updates the animation state based on the current tick count.
/// </summary>
/// <param name="tickCount">The current server tick count</param>
/// <summary>
/// Updates the animation state for the actor based on the current server tick, processing keyframe events, handling looping, and resetting sequences as needed.
/// </summary>
/// <param name="tickCount">The current server tick count.</param>
public void Update(long tickCount) {
// Skip update if no animation metadata is available
if (RigMetadata is null) {
Expand DownExpand Up@@ -349,7 +352,12 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
/// </summary>
/// <param name="sequenceTime">The current sequence time</param>
/// <param name="key">The keyframe that was hit</param>
/// <param name="speed">The current animation speed</param>
/// <summary>
/// Handles a keyframe event during an animation sequence, triggering actor callbacks and updating loop or end timing based on the keyframe type.
/// </summary>
/// <param name="sequenceTime">The current normalized time within the animation sequence.</param>
/// <param name="key">The animation keyframe being processed.</param>
/// <param name="speed">The current animation speed.</param>
private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
isHandlingKeyframe = true;

Expand Down
50 changes: 49 additions & 1 deletion Maple2.Server.Game/Manager/Config/BuffManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Game;
Expand DownExpand Up@@ -61,6 +61,9 @@ public void Clear() {
}
}

/// <summary>
/// Applies entrance buffs and refreshes premium club buffs for the actor when entering a field.
/// </summary>
public void LoadFieldBuffs() {
// Lapenshards
// Game Events
Expand All@@ -71,6 +74,18 @@ public void LoadFieldBuffs() {
}
}

/// <summary>
/// Adds a buff to the specified owner, handling stacking, duration, cooldowns, group conflicts, and effect application.
/// </summary>
/// <param name="caster">The actor applying the buff.</param>
/// <param name="owner">The actor receiving the buff.</param>
/// <param name="id">The buff's skill or effect ID.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="startTick">The tick count when the buff starts.</param>
/// <param name="stacks">The number of stacks to apply (clamped to the buff's maximum).</param>
/// <param name="durationMs">The duration of the buff in milliseconds. If negative, uses the default duration.</param>
/// <param name="notifyField">Whether to broadcast the buff addition to the field.</param>
/// <param name="type">The event condition type triggering the buff application.</param>
public void AddBuff(IActor caster, IActor owner, int id, short level, long startTick, int stacks = 0, int durationMs = -1, bool notifyField = true, EventConditionType type = EventConditionType.Activate) {
if (!owner.Field.SkillMetadata.TryGetEffect(id, level, out AdditionalEffectMetadata? additionalEffect)) {
logger.Error("Invalid buff: {SkillId},{Level}", id, level);
Expand DownExpand Up@@ -310,6 +325,11 @@ public float TotalCompulsionRate(CompulsionEventType type, int skillId = 0) {
nestedCompulsionDic.Values.Where(compulsion => compulsion.SkillIds.Contains(skillId)).Sum(compulsion => compulsion.Rate);
}

/// <summary>
/// Returns the resistance value for the specified attribute, or 0 if not present.
/// </summary>
/// <param name="attribute">The attribute for which to retrieve resistance.</param>
/// <returns>The resistance value for the given attribute, or 0 if none is set.</returns>
public float GetResistance(BasicAttribute attribute) {
if (Resistances.TryGetValue(attribute, out float value)) {
return value;
Expand All@@ -318,6 +338,13 @@ public float GetResistance(BasicAttribute attribute) {
return 0;
}

/// <summary>
/// Calculates the total invoke value and rate for a specified invoke effect type, filtered by skill ID or skill group.
/// </summary>
/// <param name="invokeType">The type of invoke effect to aggregate.</param>
/// <param name="skillId">The skill ID to match against invoke effects.</param>
/// <param name="skillGroup">Optional skill group IDs to match against invoke effects.</param>
/// <returns>A tuple containing the total invoke value (as an integer) and the total invoke rate (as a float).</returns>
public (int, float) GetInvokeValues(InvokeEffectType invokeType, int skillId, params int[] skillGroup) {
if (!Invokes.TryGetValue(invokeType, out var nestedInvokeDic))
return (0, 0f);
Expand All@@ -335,6 +362,9 @@ public float GetResistance(BasicAttribute attribute) {
return ((int) value, rate);
}

/// <summary>
/// Sets the shield health for a buff based on its metadata, using either a fixed value or a percentage of the actor's maximum health.
/// </summary>
private void SetShield(Buff buff) {
if (buff.Metadata.Shield == null) {
return;
Expand DownExpand Up@@ -369,6 +399,9 @@ private void SetMount(Buff buff) {
}
}

/// <summary>
/// Applies update effects from the buff, including canceling specified buffs and resetting skill cooldowns for the actor.
/// </summary>
private void SetUpdates(Buff buff) {
if (buff.Metadata.Update.Cancel != null) {
CancelBuffs(buff, buff.Metadata.Update.Cancel);
Expand All@@ -381,6 +414,15 @@ private void SetUpdates(Buff buff) {
}
}
}
/// <summary>
/// Triggers an event for all enabled buffs, causing the owner to apply each buff's effects for the specified event type.
/// </summary>
/// <param name="caster">The actor who initiated the event.</param>
/// <param name="owner">The actor who owns the buffs.</param>
/// <param name="target">The target actor affected by the event.</param>
/// <param name="type">The event condition type that determines which effects to apply.</param>
/// <param name="skillId">Optional skill ID associated with the event.</param>
/// <param name="buffId">Optional buff ID associated with the event.</param>
public void TriggerEvent(IActor caster, IActor owner, IActor target, EventConditionType type, int skillId = 0, int buffId = 0) {
foreach (Buff buff in EnumerateBuffs()) {
if (!buff.Enabled) {
Expand DownExpand Up@@ -520,6 +562,12 @@ public void Remove(params (int id, int casterId)[] buffIds) {
}
}

/// <summary>
/// Removes all buffs with the specified ID and caster ID from the actor, updates resistances, handles related effects, and refreshes stats if necessary.
/// </summary>
/// <param name="id">The buff ID to remove.</param>
/// <param name="casterId">The object ID of the caster whose buffs should be removed.</param>
/// <returns>True if the removal process completes.</returns>
public bool Remove(int id, int casterId) {
//TODO: Check if buff is removable/should be removed
bool refreshStats = false;
Expand Down
5 changes: 4 additions & 1 deletion Maple2.Server.Game/Manager/Config/SkillManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Storage;
using Maple2.Database.Storage;
using Maple2.Model.Enum;
using Maple2.Model.Game;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -30,6 +30,9 @@ public void LoadSkillBook() {
session.Send(SkillBookPacket.Load(SkillBook));
}

/// <summary>
/// Applies all passive skill effects that target the player, updating active buffs based on the player's learned passive skills.
/// </summary>
public void UpdatePassiveBuffs(bool notifyField = true) {
// TODO: Only remove buffs that have been unlearned.
/*foreach (Buff buff in session.Player.Buffs.Buffs.Values) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
Expand DownExpand Up@@ -437,6 +437,10 @@ public void AddSkill(IActor caster, SkillEffectMetadata effect, Vector3[] points
}
}

/// <summary>
/// Adds splash skill effects from a skill record, calculating effect positions based on cube magic paths if applicable.
/// </summary>
/// <param name="record">The skill record containing caster, position, rotation, and attack metadata.</param>
public void AddSkill(SkillRecord record) {
SkillMetadataAttack attack = record.Attack;
if (!TableMetadata.MagicPathTable.Entries.TryGetValue(attack.CubeMagicPathId, out IReadOnlyList<MagicPath>? cubeMagicPaths)) {
Expand DownExpand Up@@ -474,6 +478,14 @@ public void AddSkill(SkillRecord record) {
}
}

/// <summary>
/// Returns a filtered collection of actors within the specified prisms, based on the target type and limit.
/// </summary>
/// <param name="prisms">The prisms used to filter actors by location or area.</param>
/// <param name="targetType">The type of targets to select (e.g., friendly players, hostile mobs, or hungry mobs).</param>
/// <param name="limit">The maximum number of actors to return.</param>
/// <param name="ignore">An optional collection of actors to exclude from the results.</param>
/// <returns>An enumerable of actors matching the criteria, or an empty collection if the target type is unhandled.</returns>
public IEnumerable<IActor> GetTargets(Prism[] prisms, ApplyTargetType targetType, int limit, ICollection<IActor>? ignore = null) {
switch (targetType) {
case ApplyTargetType.Friendly:
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Maple2.Database/Context/MetadataContext.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Extensions;
using Maple2.Database.Extensions;
using Maple2.Database.Model.Metadata;
using Maple2.Model.Game.Field;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -59,6 +59,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<FunctionCubeMetadata>(ConfigureFunctionCubeMetadata);
}

/// <summary>
/// Configures the Entity Framework Core mapping for the AdditionalEffectMetadata entity, including table name, composite primary key, and JSON conversion for complex properties.
/// </summary>
private static void ConfigureAdditionalEffectMetadata(EntityTypeBuilder<AdditionalEffectMetadata> builder) {
builder.ToTable("additional-effect");
builder.HasKey(effect => new { effect.Id, effect.Level });
Expand Down
16 changes: 15 additions & 1 deletion Maple2.File.Ingest/Mapper/AdditionalEffectMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.File.IO;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.AdditionalEffect;
using Maple2.Model.Enum;
Expand All@@ -16,6 +16,10 @@ public AdditionalEffectMapper(M2dReader xmlReader) {
parser = new AdditionalEffectParser(xmlReader);
}

/// <summary>
/// Maps parsed additional effect data into strongly typed <see cref="AdditionalEffectMetadata"/> objects.
/// </summary>
/// <returns>An enumerable of <see cref="AdditionalEffectMetadata"/> representing all parsed additional effects.</returns>
protected override IEnumerable<AdditionalEffectMetadata> Map() {
foreach ((int id, IList<AdditionalEffectData> datas) in parser.Parse()) {
foreach (AdditionalEffectData data in datas) {
Expand DownExpand Up@@ -231,6 +235,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
NotKill: dotDamage.notKill);
}

/// <summary>
/// Converts a <see cref="DotBuffProperty"/> to a <see cref="AdditionalEffectMetadataDot.DotBuff"/> if the buff ID is positive; otherwise returns null.
/// </summary>
/// <param name="dotBuff">The DOT buff property to convert.</param>
/// <returns>A <see cref="AdditionalEffectMetadataDot.DotBuff"/> instance if valid; otherwise, null.</returns>
private static AdditionalEffectMetadataDot.DotBuff? Convert(DotBuffProperty? dotBuff) {
if (dotBuff is not { buffID: > 0 }) {
return null;
Expand All@@ -239,6 +248,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
return new AdditionalEffectMetadataDot.DotBuff(Target: (SkillTargetType) dotBuff.target, Id: dotBuff.buffID, Level: dotBuff.buffLevel);
}

/// <summary>
/// Converts a <see cref="ShieldProperty"/> to an <see cref="AdditionalEffectMetadataShield"/> if shield values are positive; otherwise returns null.
/// </summary>
/// <param name="shield">The shield property to convert.</param>
/// <returns>An <see cref="AdditionalEffectMetadataShield"/> if applicable; otherwise, null.</returns>
private static AdditionalEffectMetadataShield? Convert(ShieldProperty shield) {
if (shield is { hpValue: <= 0, hpByTargetMaxHP: <= 0 }) {
return null;
Expand Down
11 changes: 10 additions & 1 deletion Maple2.File.Ingest/Mapper/SkillMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.Skill;
Expand All@@ -14,6 +14,10 @@ public SkillMapper(M2dReader xmlReader) {
parser = new SkillParser(xmlReader);
}

/// <summary>
/// Maps parsed skill XML data into structured <see cref="StoredSkillMetadata"/> objects, transforming raw skill definitions into strongly typed metadata for further processing.
/// </summary>
/// <returns>An enumerable sequence of <see cref="StoredSkillMetadata"/> representing all valid skills parsed from the source data.</returns>
protected override IEnumerable<StoredSkillMetadata> Map() {
foreach ((int id, string name, SkillData data) in parser.Parse()) {
if (data.basic == null) continue; // Old_JobChange_01
Expand DownExpand Up@@ -133,6 +137,11 @@ protected override IEnumerable<StoredSkillMetadata> Map() {
}
}

/// <summary>
/// Converts a <see cref="RegionSkill"/> object into a <see cref="SkillMetadataRange"/>, mapping region type strings to <see cref="SkillRegion"/> values and transferring relevant range properties.
/// </summary>
/// <param name="region">The region skill data to convert.</param>
/// <returns>A <see cref="SkillMetadataRange"/> representing the region's metadata.</returns>
private static SkillMetadataRange Convert(RegionSkill region) {
return new SkillMetadataRange(
Type: region.rangeType switch {
Expand Down
12 changes: 11 additions & 1 deletion Maple2.File.Ingest/MapperExtensions.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Maple2.File.Ingest.Utils;
Expand DownExpand Up@@ -227,6 +227,11 @@ public static byte OptionIndex(this SpecialAttribute attribute) {
};
}

/// <summary>
/// Converts a <see cref="TriggerSkill"/> instance into a <see cref="SkillEffectMetadata"/>, mapping its properties to either a splash or condition effect and assembling the associated skills.
/// </summary>
/// <param name="trigger">The trigger skill data to convert.</param>
/// <returns>A <see cref="SkillEffectMetadata"/> representing the trigger's effect, including splash or condition details and linked skills.</returns>
public static SkillEffectMetadata Convert(this TriggerSkill trigger) {
SkillEffectMetadataCondition? condition = null;
SkillEffectMetadataSplash? splash = null;
Expand DownExpand Up@@ -316,6 +321,11 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
UseMove: autoTargeting.autoTargetUseMove);
}

/// <summary>
/// Converts a parsed XML skill begin condition into a strongly typed <see cref="BeginCondition"/> metadata object for use in Maple2 game logic.
/// </summary>
/// <param name="beginCondition">The XML-parsed skill begin condition to convert.</param>
/// <returns>A <see cref="BeginCondition"/> instance containing mapped level, gender, mesos, stats, map and skill requirements, job codes, probability, cooldowns, durations, state flags, dungeon group types, weapon requirements, and subconditions for target, owner, and caster.</returns>
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
return new BeginCondition(
Level: beginCondition.level,
Expand Down
10 changes: 9 additions & 1 deletion Maple2.Server.Core/Packets/RequestPacket.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.PacketLib.Tools;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;

namespace Maple2.Server.Core.Packets;
Expand All@@ -8,10 +8,18 @@ public static ByteWriter Login() {
return Packet.Of(SendOp.RequestLogin);
}

/// <summary>
/// Creates a packet for requesting a session key from the server.
/// </summary>
/// <returns>A <see cref="ByteWriter"/> containing the key request packet.</returns>
public static ByteWriter Key() {
return Packet.Of(SendOp.RequestKey);
}

/// <summary>
/// Creates a heartbeat request packet containing the current system tick count.
/// </summary>
/// <returns>A ByteWriter representing the heartbeat request packet.</returns>
public static ByteWriter Heartbeat() {
var pWriter = Packet.Of(SendOp.RequestHeartbeat);
pWriter.WriteInt(Environment.TickCount);
Expand Down
13 changes: 12 additions & 1 deletion Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Maple2.Database.Storage;
Expand DownExpand Up@@ -40,6 +40,17 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

/// <summary>
/// Processes the "buff" command to add or remove a specified buff on one or more players in the current field.
/// </summary>
/// <param name="ctx">The command invocation context.</param>
/// <param name="buffId">The ID of the buff to add or remove.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="stack">The number of buff stacks to apply.</param>
/// <param name="duration">The duration of the buff in seconds, or -1 for default duration.</param>
/// <param name="all">If true, applies the operation to all players in the field.</param>
/// <param name="target">The name of the target player to affect, or empty to target the command issuer.</param>
/// <param name="remove">If true, removes the buff instead of adding it.</param>
private void Handle(InvocationContext ctx, int buffId, int level, int stack, int duration, bool all, string target, bool remove) {
try {
if (!skillStorage.TryGetEffect(buffId, (short) level, out AdditionalEffectMetadata? _)) {
Expand Down
8 changes: 7 additions & 1 deletion Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
Expand DownExpand Up@@ -141,6 +141,12 @@ private void Handle(InvocationContext ctx, string name) {
}
}

/// <summary>
/// Instantly kills the specified NPC by applying damage equal to its current health and broadcasts the resulting updates to the field.
/// </summary>
/// <param name="session">The game session performing the kill action.</param>
/// <param name="npc">The NPC to be killed.</param>
/// <param name="skill">The skill metadata used to generate the damage record.</param>
private static void Kill(GameSession session, FieldNpc npc, SkillMetadata skill) {
var damageRecord = new DamageRecord(skill, skill.Data.Motions[0].Attacks[0]) {
CasterId = session.Player.ObjectId,
Expand Down
14 changes: 11 additions & 3 deletions Maple2.Server.Game/Manager/AnimationManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Model.Enum;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Model;
Expand DownExpand Up@@ -199,7 +199,10 @@ public void CancelSequence() {
/// <summary>
/// Updates the animation state based on the current tick count.
/// </summary>
/// <param name="tickCount">The current server tick count</param>
/// <summary>
/// Updates the animation state for the actor based on the current server tick, processing keyframe events, handling looping, and resetting sequences as needed.
/// </summary>
/// <param name="tickCount">The current server tick count.</param>
public void Update(long tickCount) {
// Skip update if no animation metadata is available
if (RigMetadata is null) {
Expand DownExpand Up@@ -349,7 +352,12 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
/// </summary>
/// <param name="sequenceTime">The current sequence time</param>
/// <param name="key">The keyframe that was hit</param>
/// <param name="speed">The current animation speed</param>
/// <summary>
/// Handles a keyframe event during an animation sequence, triggering actor callbacks and updating loop or end timing based on the keyframe type.
/// </summary>
/// <param name="sequenceTime">The current normalized time within the animation sequence.</param>
/// <param name="key">The animation keyframe being processed.</param>
/// <param name="speed">The current animation speed.</param>
private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
isHandlingKeyframe = true;

Expand Down
50 changes: 49 additions & 1 deletion Maple2.Server.Game/Manager/Config/BuffManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Game;
Expand DownExpand Up@@ -61,6 +61,9 @@ public void Clear() {
}
}

/// <summary>
/// Applies entrance buffs and refreshes premium club buffs for the actor when entering a field.
/// </summary>
public void LoadFieldBuffs() {
// Lapenshards
// Game Events
Expand All@@ -71,6 +74,18 @@ public void LoadFieldBuffs() {
}
}

/// <summary>
/// Adds a buff to the specified owner, handling stacking, duration, cooldowns, group conflicts, and effect application.
/// </summary>
/// <param name="caster">The actor applying the buff.</param>
/// <param name="owner">The actor receiving the buff.</param>
/// <param name="id">The buff's skill or effect ID.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="startTick">The tick count when the buff starts.</param>
/// <param name="stacks">The number of stacks to apply (clamped to the buff's maximum).</param>
/// <param name="durationMs">The duration of the buff in milliseconds. If negative, uses the default duration.</param>
/// <param name="notifyField">Whether to broadcast the buff addition to the field.</param>
/// <param name="type">The event condition type triggering the buff application.</param>
public void AddBuff(IActor caster, IActor owner, int id, short level, long startTick, int stacks = 0, int durationMs = -1, bool notifyField = true, EventConditionType type = EventConditionType.Activate) {
if (!owner.Field.SkillMetadata.TryGetEffect(id, level, out AdditionalEffectMetadata? additionalEffect)) {
logger.Error("Invalid buff: {SkillId},{Level}", id, level);
Expand DownExpand Up@@ -310,6 +325,11 @@ public float TotalCompulsionRate(CompulsionEventType type, int skillId = 0) {
nestedCompulsionDic.Values.Where(compulsion => compulsion.SkillIds.Contains(skillId)).Sum(compulsion => compulsion.Rate);
}

/// <summary>
/// Returns the resistance value for the specified attribute, or 0 if not present.
/// </summary>
/// <param name="attribute">The attribute for which to retrieve resistance.</param>
/// <returns>The resistance value for the given attribute, or 0 if none is set.</returns>
public float GetResistance(BasicAttribute attribute) {
if (Resistances.TryGetValue(attribute, out float value)) {
return value;
Expand All@@ -318,6 +338,13 @@ public float GetResistance(BasicAttribute attribute) {
return 0;
}

/// <summary>
/// Calculates the total invoke value and rate for a specified invoke effect type, filtered by skill ID or skill group.
/// </summary>
/// <param name="invokeType">The type of invoke effect to aggregate.</param>
/// <param name="skillId">The skill ID to match against invoke effects.</param>
/// <param name="skillGroup">Optional skill group IDs to match against invoke effects.</param>
/// <returns>A tuple containing the total invoke value (as an integer) and the total invoke rate (as a float).</returns>
public (int, float) GetInvokeValues(InvokeEffectType invokeType, int skillId, params int[] skillGroup) {
if (!Invokes.TryGetValue(invokeType, out var nestedInvokeDic))
return (0, 0f);
Expand All@@ -335,6 +362,9 @@ public float GetResistance(BasicAttribute attribute) {
return ((int) value, rate);
}

/// <summary>
/// Sets the shield health for a buff based on its metadata, using either a fixed value or a percentage of the actor's maximum health.
/// </summary>
private void SetShield(Buff buff) {
if (buff.Metadata.Shield == null) {
return;
Expand DownExpand Up@@ -369,6 +399,9 @@ private void SetMount(Buff buff) {
}
}

/// <summary>
/// Applies update effects from the buff, including canceling specified buffs and resetting skill cooldowns for the actor.
/// </summary>
private void SetUpdates(Buff buff) {
if (buff.Metadata.Update.Cancel != null) {
CancelBuffs(buff, buff.Metadata.Update.Cancel);
Expand All@@ -381,6 +414,15 @@ private void SetUpdates(Buff buff) {
}
}
}
/// <summary>
/// Triggers an event for all enabled buffs, causing the owner to apply each buff's effects for the specified event type.
/// </summary>
/// <param name="caster">The actor who initiated the event.</param>
/// <param name="owner">The actor who owns the buffs.</param>
/// <param name="target">The target actor affected by the event.</param>
/// <param name="type">The event condition type that determines which effects to apply.</param>
/// <param name="skillId">Optional skill ID associated with the event.</param>
/// <param name="buffId">Optional buff ID associated with the event.</param>
public void TriggerEvent(IActor caster, IActor owner, IActor target, EventConditionType type, int skillId = 0, int buffId = 0) {
foreach (Buff buff in EnumerateBuffs()) {
if (!buff.Enabled) {
Expand DownExpand Up@@ -520,6 +562,12 @@ public void Remove(params (int id, int casterId)[] buffIds) {
}
}

/// <summary>
/// Removes all buffs with the specified ID and caster ID from the actor, updates resistances, handles related effects, and refreshes stats if necessary.
/// </summary>
/// <param name="id">The buff ID to remove.</param>
/// <param name="casterId">The object ID of the caster whose buffs should be removed.</param>
/// <returns>True if the removal process completes.</returns>
public bool Remove(int id, int casterId) {
//TODO: Check if buff is removable/should be removed
bool refreshStats = false;
Expand Down
5 changes: 4 additions & 1 deletion Maple2.Server.Game/Manager/Config/SkillManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Storage;
using Maple2.Database.Storage;
using Maple2.Model.Enum;
using Maple2.Model.Game;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -30,6 +30,9 @@ public void LoadSkillBook() {
session.Send(SkillBookPacket.Load(SkillBook));
}

/// <summary>
/// Applies all passive skill effects that target the player, updating active buffs based on the player's learned passive skills.
/// </summary>
public void UpdatePassiveBuffs(bool notifyField = true) {
// TODO: Only remove buffs that have been unlearned.
/*foreach (Buff buff in session.Player.Buffs.Buffs.Values) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
Expand DownExpand Up@@ -437,6 +437,10 @@ public void AddSkill(IActor caster, SkillEffectMetadata effect, Vector3[] points
}
}

/// <summary>
/// Adds splash skill effects from a skill record, calculating effect positions based on cube magic paths if applicable.
/// </summary>
/// <param name="record">The skill record containing caster, position, rotation, and attack metadata.</param>
public void AddSkill(SkillRecord record) {
SkillMetadataAttack attack = record.Attack;
if (!TableMetadata.MagicPathTable.Entries.TryGetValue(attack.CubeMagicPathId, out IReadOnlyList<MagicPath>? cubeMagicPaths)) {
Expand DownExpand Up@@ -474,6 +478,14 @@ public void AddSkill(SkillRecord record) {
}
}

/// <summary>
/// Returns a filtered collection of actors within the specified prisms, based on the target type and limit.
/// </summary>
/// <param name="prisms">The prisms used to filter actors by location or area.</param>
/// <param name="targetType">The type of targets to select (e.g., friendly players, hostile mobs, or hungry mobs).</param>
/// <param name="limit">The maximum number of actors to return.</param>
/// <param name="ignore">An optional collection of actors to exclude from the results.</param>
/// <returns>An enumerable of actors matching the criteria, or an empty collection if the target type is unhandled.</returns>
public IEnumerable<IActor> GetTargets(Prism[] prisms, ApplyTargetType targetType, int limit, ICollection<IActor>? ignore = null) {
switch (targetType) {
case ApplyTargetType.Friendly:
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Maple2.Database/Context/MetadataContext.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Extensions;
using Maple2.Database.Extensions;
using Maple2.Database.Model.Metadata;
using Maple2.Model.Game.Field;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -59,6 +59,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<FunctionCubeMetadata>(ConfigureFunctionCubeMetadata);
}

/// <summary>
/// Configures the Entity Framework Core mapping for the AdditionalEffectMetadata entity, including table name, composite primary key, and JSON conversion for complex properties.
/// </summary>
private static void ConfigureAdditionalEffectMetadata(EntityTypeBuilder<AdditionalEffectMetadata> builder) {
builder.ToTable("additional-effect");
builder.HasKey(effect => new { effect.Id, effect.Level });
Expand Down
16 changes: 15 additions & 1 deletion Maple2.File.Ingest/Mapper/AdditionalEffectMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.File.IO;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.AdditionalEffect;
using Maple2.Model.Enum;
Expand All@@ -16,6 +16,10 @@ public AdditionalEffectMapper(M2dReader xmlReader) {
parser = new AdditionalEffectParser(xmlReader);
}

/// <summary>
/// Maps parsed additional effect data into strongly typed <see cref="AdditionalEffectMetadata"/> objects.
/// </summary>
/// <returns>An enumerable of <see cref="AdditionalEffectMetadata"/> representing all parsed additional effects.</returns>
protected override IEnumerable<AdditionalEffectMetadata> Map() {
foreach ((int id, IList<AdditionalEffectData> datas) in parser.Parse()) {
foreach (AdditionalEffectData data in datas) {
Expand DownExpand Up@@ -231,6 +235,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
NotKill: dotDamage.notKill);
}

/// <summary>
/// Converts a <see cref="DotBuffProperty"/> to a <see cref="AdditionalEffectMetadataDot.DotBuff"/> if the buff ID is positive; otherwise returns null.
/// </summary>
/// <param name="dotBuff">The DOT buff property to convert.</param>
/// <returns>A <see cref="AdditionalEffectMetadataDot.DotBuff"/> instance if valid; otherwise, null.</returns>
private static AdditionalEffectMetadataDot.DotBuff? Convert(DotBuffProperty? dotBuff) {
if (dotBuff is not { buffID: > 0 }) {
return null;
Expand All@@ -239,6 +248,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
return new AdditionalEffectMetadataDot.DotBuff(Target: (SkillTargetType) dotBuff.target, Id: dotBuff.buffID, Level: dotBuff.buffLevel);
}

/// <summary>
/// Converts a <see cref="ShieldProperty"/> to an <see cref="AdditionalEffectMetadataShield"/> if shield values are positive; otherwise returns null.
/// </summary>
/// <param name="shield">The shield property to convert.</param>
/// <returns>An <see cref="AdditionalEffectMetadataShield"/> if applicable; otherwise, null.</returns>
private static AdditionalEffectMetadataShield? Convert(ShieldProperty shield) {
if (shield is { hpValue: <= 0, hpByTargetMaxHP: <= 0 }) {
return null;
Expand Down
11 changes: 10 additions & 1 deletion Maple2.File.Ingest/Mapper/SkillMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.Skill;
Expand All@@ -14,6 +14,10 @@ public SkillMapper(M2dReader xmlReader) {
parser = new SkillParser(xmlReader);
}

/// <summary>
/// Maps parsed skill XML data into structured <see cref="StoredSkillMetadata"/> objects, transforming raw skill definitions into strongly typed metadata for further processing.
/// </summary>
/// <returns>An enumerable sequence of <see cref="StoredSkillMetadata"/> representing all valid skills parsed from the source data.</returns>
protected override IEnumerable<StoredSkillMetadata> Map() {
foreach ((int id, string name, SkillData data) in parser.Parse()) {
if (data.basic == null) continue; // Old_JobChange_01
Expand DownExpand Up@@ -133,6 +137,11 @@ protected override IEnumerable<StoredSkillMetadata> Map() {
}
}

/// <summary>
/// Converts a <see cref="RegionSkill"/> object into a <see cref="SkillMetadataRange"/>, mapping region type strings to <see cref="SkillRegion"/> values and transferring relevant range properties.
/// </summary>
/// <param name="region">The region skill data to convert.</param>
/// <returns>A <see cref="SkillMetadataRange"/> representing the region's metadata.</returns>
private static SkillMetadataRange Convert(RegionSkill region) {
return new SkillMetadataRange(
Type: region.rangeType switch {
Expand Down
12 changes: 11 additions & 1 deletion Maple2.File.Ingest/MapperExtensions.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Maple2.File.Ingest.Utils;
Expand DownExpand Up@@ -227,6 +227,11 @@ public static byte OptionIndex(this SpecialAttribute attribute) {
};
}

/// <summary>
/// Converts a <see cref="TriggerSkill"/> instance into a <see cref="SkillEffectMetadata"/>, mapping its properties to either a splash or condition effect and assembling the associated skills.
/// </summary>
/// <param name="trigger">The trigger skill data to convert.</param>
/// <returns>A <see cref="SkillEffectMetadata"/> representing the trigger's effect, including splash or condition details and linked skills.</returns>
public static SkillEffectMetadata Convert(this TriggerSkill trigger) {
SkillEffectMetadataCondition? condition = null;
SkillEffectMetadataSplash? splash = null;
Expand DownExpand Up@@ -316,6 +321,11 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
UseMove: autoTargeting.autoTargetUseMove);
}

/// <summary>
/// Converts a parsed XML skill begin condition into a strongly typed <see cref="BeginCondition"/> metadata object for use in Maple2 game logic.
/// </summary>
/// <param name="beginCondition">The XML-parsed skill begin condition to convert.</param>
/// <returns>A <see cref="BeginCondition"/> instance containing mapped level, gender, mesos, stats, map and skill requirements, job codes, probability, cooldowns, durations, state flags, dungeon group types, weapon requirements, and subconditions for target, owner, and caster.</returns>
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
return new BeginCondition(
Level: beginCondition.level,
Expand Down
10 changes: 9 additions & 1 deletion Maple2.Server.Core/Packets/RequestPacket.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.PacketLib.Tools;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;

namespace Maple2.Server.Core.Packets;
Expand All@@ -8,10 +8,18 @@ public static ByteWriter Login() {
return Packet.Of(SendOp.RequestLogin);
}

/// <summary>
/// Creates a packet for requesting a session key from the server.
/// </summary>
/// <returns>A <see cref="ByteWriter"/> containing the key request packet.</returns>
public static ByteWriter Key() {
return Packet.Of(SendOp.RequestKey);
}

/// <summary>
/// Creates a heartbeat request packet containing the current system tick count.
/// </summary>
/// <returns>A ByteWriter representing the heartbeat request packet.</returns>
public static ByteWriter Heartbeat() {
var pWriter = Packet.Of(SendOp.RequestHeartbeat);
pWriter.WriteInt(Environment.TickCount);
Expand Down
13 changes: 12 additions & 1 deletion Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Maple2.Database.Storage;
Expand DownExpand Up@@ -40,6 +40,17 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

/// <summary>
/// Processes the "buff" command to add or remove a specified buff on one or more players in the current field.
/// </summary>
/// <param name="ctx">The command invocation context.</param>
/// <param name="buffId">The ID of the buff to add or remove.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="stack">The number of buff stacks to apply.</param>
/// <param name="duration">The duration of the buff in seconds, or -1 for default duration.</param>
/// <param name="all">If true, applies the operation to all players in the field.</param>
/// <param name="target">The name of the target player to affect, or empty to target the command issuer.</param>
/// <param name="remove">If true, removes the buff instead of adding it.</param>
private void Handle(InvocationContext ctx, int buffId, int level, int stack, int duration, bool all, string target, bool remove) {
try {
if (!skillStorage.TryGetEffect(buffId, (short) level, out AdditionalEffectMetadata? _)) {
Expand Down
8 changes: 7 additions & 1 deletion Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
Expand DownExpand Up@@ -141,6 +141,12 @@ private void Handle(InvocationContext ctx, string name) {
}
}

/// <summary>
/// Instantly kills the specified NPC by applying damage equal to its current health and broadcasts the resulting updates to the field.
/// </summary>
/// <param name="session">The game session performing the kill action.</param>
/// <param name="npc">The NPC to be killed.</param>
/// <param name="skill">The skill metadata used to generate the damage record.</param>
private static void Kill(GameSession session, FieldNpc npc, SkillMetadata skill) {
var damageRecord = new DamageRecord(skill, skill.Data.Motions[0].Attacks[0]) {
CasterId = session.Player.ObjectId,
Expand Down
14 changes: 11 additions & 3 deletions Maple2.Server.Game/Manager/AnimationManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Model.Enum;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Model;
Expand DownExpand Up@@ -199,7 +199,10 @@ public void CancelSequence() {
/// <summary>
/// Updates the animation state based on the current tick count.
/// </summary>
/// <param name="tickCount">The current server tick count</param>
/// <summary>
/// Updates the animation state for the actor based on the current server tick, processing keyframe events, handling looping, and resetting sequences as needed.
/// </summary>
/// <param name="tickCount">The current server tick count.</param>
public void Update(long tickCount) {
// Skip update if no animation metadata is available
if (RigMetadata is null) {
Expand DownExpand Up@@ -349,7 +352,12 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
/// </summary>
/// <param name="sequenceTime">The current sequence time</param>
/// <param name="key">The keyframe that was hit</param>
/// <param name="speed">The current animation speed</param>
/// <summary>
/// Handles a keyframe event during an animation sequence, triggering actor callbacks and updating loop or end timing based on the keyframe type.
/// </summary>
/// <param name="sequenceTime">The current normalized time within the animation sequence.</param>
/// <param name="key">The animation keyframe being processed.</param>
/// <param name="speed">The current animation speed.</param>
private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
isHandlingKeyframe = true;

Expand Down
50 changes: 49 additions & 1 deletion Maple2.Server.Game/Manager/Config/BuffManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Game;
Expand DownExpand Up@@ -61,6 +61,9 @@ public void Clear() {
}
}

/// <summary>
/// Applies entrance buffs and refreshes premium club buffs for the actor when entering a field.
/// </summary>
public void LoadFieldBuffs() {
// Lapenshards
// Game Events
Expand All@@ -71,6 +74,18 @@ public void LoadFieldBuffs() {
}
}

/// <summary>
/// Adds a buff to the specified owner, handling stacking, duration, cooldowns, group conflicts, and effect application.
/// </summary>
/// <param name="caster">The actor applying the buff.</param>
/// <param name="owner">The actor receiving the buff.</param>
/// <param name="id">The buff's skill or effect ID.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="startTick">The tick count when the buff starts.</param>
/// <param name="stacks">The number of stacks to apply (clamped to the buff's maximum).</param>
/// <param name="durationMs">The duration of the buff in milliseconds. If negative, uses the default duration.</param>
/// <param name="notifyField">Whether to broadcast the buff addition to the field.</param>
/// <param name="type">The event condition type triggering the buff application.</param>
public void AddBuff(IActor caster, IActor owner, int id, short level, long startTick, int stacks = 0, int durationMs = -1, bool notifyField = true, EventConditionType type = EventConditionType.Activate) {
if (!owner.Field.SkillMetadata.TryGetEffect(id, level, out AdditionalEffectMetadata? additionalEffect)) {
logger.Error("Invalid buff: {SkillId},{Level}", id, level);
Expand DownExpand Up@@ -310,6 +325,11 @@ public float TotalCompulsionRate(CompulsionEventType type, int skillId = 0) {
nestedCompulsionDic.Values.Where(compulsion => compulsion.SkillIds.Contains(skillId)).Sum(compulsion => compulsion.Rate);
}

/// <summary>
/// Returns the resistance value for the specified attribute, or 0 if not present.
/// </summary>
/// <param name="attribute">The attribute for which to retrieve resistance.</param>
/// <returns>The resistance value for the given attribute, or 0 if none is set.</returns>
public float GetResistance(BasicAttribute attribute) {
if (Resistances.TryGetValue(attribute, out float value)) {
return value;
Expand All@@ -318,6 +338,13 @@ public float GetResistance(BasicAttribute attribute) {
return 0;
}

/// <summary>
/// Calculates the total invoke value and rate for a specified invoke effect type, filtered by skill ID or skill group.
/// </summary>
/// <param name="invokeType">The type of invoke effect to aggregate.</param>
/// <param name="skillId">The skill ID to match against invoke effects.</param>
/// <param name="skillGroup">Optional skill group IDs to match against invoke effects.</param>
/// <returns>A tuple containing the total invoke value (as an integer) and the total invoke rate (as a float).</returns>
public (int, float) GetInvokeValues(InvokeEffectType invokeType, int skillId, params int[] skillGroup) {
if (!Invokes.TryGetValue(invokeType, out var nestedInvokeDic))
return (0, 0f);
Expand All@@ -335,6 +362,9 @@ public float GetResistance(BasicAttribute attribute) {
return ((int) value, rate);
}

/// <summary>
/// Sets the shield health for a buff based on its metadata, using either a fixed value or a percentage of the actor's maximum health.
/// </summary>
private void SetShield(Buff buff) {
if (buff.Metadata.Shield == null) {
return;
Expand DownExpand Up@@ -369,6 +399,9 @@ private void SetMount(Buff buff) {
}
}

/// <summary>
/// Applies update effects from the buff, including canceling specified buffs and resetting skill cooldowns for the actor.
/// </summary>
private void SetUpdates(Buff buff) {
if (buff.Metadata.Update.Cancel != null) {
CancelBuffs(buff, buff.Metadata.Update.Cancel);
Expand All@@ -381,6 +414,15 @@ private void SetUpdates(Buff buff) {
}
}
}
/// <summary>
/// Triggers an event for all enabled buffs, causing the owner to apply each buff's effects for the specified event type.
/// </summary>
/// <param name="caster">The actor who initiated the event.</param>
/// <param name="owner">The actor who owns the buffs.</param>
/// <param name="target">The target actor affected by the event.</param>
/// <param name="type">The event condition type that determines which effects to apply.</param>
/// <param name="skillId">Optional skill ID associated with the event.</param>
/// <param name="buffId">Optional buff ID associated with the event.</param>
public void TriggerEvent(IActor caster, IActor owner, IActor target, EventConditionType type, int skillId = 0, int buffId = 0) {
foreach (Buff buff in EnumerateBuffs()) {
if (!buff.Enabled) {
Expand DownExpand Up@@ -520,6 +562,12 @@ public void Remove(params (int id, int casterId)[] buffIds) {
}
}

/// <summary>
/// Removes all buffs with the specified ID and caster ID from the actor, updates resistances, handles related effects, and refreshes stats if necessary.
/// </summary>
/// <param name="id">The buff ID to remove.</param>
/// <param name="casterId">The object ID of the caster whose buffs should be removed.</param>
/// <returns>True if the removal process completes.</returns>
public bool Remove(int id, int casterId) {
//TODO: Check if buff is removable/should be removed
bool refreshStats = false;
Expand Down
5 changes: 4 additions & 1 deletion Maple2.Server.Game/Manager/Config/SkillManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Storage;
using Maple2.Database.Storage;
using Maple2.Model.Enum;
using Maple2.Model.Game;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -30,6 +30,9 @@ public void LoadSkillBook() {
session.Send(SkillBookPacket.Load(SkillBook));
}

/// <summary>
/// Applies all passive skill effects that target the player, updating active buffs based on the player's learned passive skills.
/// </summary>
public void UpdatePassiveBuffs(bool notifyField = true) {
// TODO: Only remove buffs that have been unlearned.
/*foreach (Buff buff in session.Player.Buffs.Buffs.Values) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
Expand DownExpand Up@@ -437,6 +437,10 @@ public void AddSkill(IActor caster, SkillEffectMetadata effect, Vector3[] points
}
}

/// <summary>
/// Adds splash skill effects from a skill record, calculating effect positions based on cube magic paths if applicable.
/// </summary>
/// <param name="record">The skill record containing caster, position, rotation, and attack metadata.</param>
public void AddSkill(SkillRecord record) {
SkillMetadataAttack attack = record.Attack;
if (!TableMetadata.MagicPathTable.Entries.TryGetValue(attack.CubeMagicPathId, out IReadOnlyList<MagicPath>? cubeMagicPaths)) {
Expand DownExpand Up@@ -474,6 +478,14 @@ public void AddSkill(SkillRecord record) {
}
}

/// <summary>
/// Returns a filtered collection of actors within the specified prisms, based on the target type and limit.
/// </summary>
/// <param name="prisms">The prisms used to filter actors by location or area.</param>
/// <param name="targetType">The type of targets to select (e.g., friendly players, hostile mobs, or hungry mobs).</param>
/// <param name="limit">The maximum number of actors to return.</param>
/// <param name="ignore">An optional collection of actors to exclude from the results.</param>
/// <returns>An enumerable of actors matching the criteria, or an empty collection if the target type is unhandled.</returns>
public IEnumerable<IActor> GetTargets(Prism[] prisms, ApplyTargetType targetType, int limit, ICollection<IActor>? ignore = null) {
switch (targetType) {
case ApplyTargetType.Friendly:
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Maple2.Database/Context/MetadataContext.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Extensions;
using Maple2.Database.Extensions;
using Maple2.Database.Model.Metadata;
using Maple2.Model.Game.Field;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -59,6 +59,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<FunctionCubeMetadata>(ConfigureFunctionCubeMetadata);
}

/// <summary>
/// Configures the Entity Framework Core mapping for the AdditionalEffectMetadata entity, including table name, composite primary key, and JSON conversion for complex properties.
/// </summary>
private static void ConfigureAdditionalEffectMetadata(EntityTypeBuilder<AdditionalEffectMetadata> builder) {
builder.ToTable("additional-effect");
builder.HasKey(effect => new { effect.Id, effect.Level });
Expand Down
16 changes: 15 additions & 1 deletion Maple2.File.Ingest/Mapper/AdditionalEffectMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.File.IO;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.AdditionalEffect;
using Maple2.Model.Enum;
Expand All@@ -16,6 +16,10 @@ public AdditionalEffectMapper(M2dReader xmlReader) {
parser = new AdditionalEffectParser(xmlReader);
}

/// <summary>
/// Maps parsed additional effect data into strongly typed <see cref="AdditionalEffectMetadata"/> objects.
/// </summary>
/// <returns>An enumerable of <see cref="AdditionalEffectMetadata"/> representing all parsed additional effects.</returns>
protected override IEnumerable<AdditionalEffectMetadata> Map() {
foreach ((int id, IList<AdditionalEffectData> datas) in parser.Parse()) {
foreach (AdditionalEffectData data in datas) {
Expand DownExpand Up@@ -231,6 +235,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
NotKill: dotDamage.notKill);
}

/// <summary>
/// Converts a <see cref="DotBuffProperty"/> to a <see cref="AdditionalEffectMetadataDot.DotBuff"/> if the buff ID is positive; otherwise returns null.
/// </summary>
/// <param name="dotBuff">The DOT buff property to convert.</param>
/// <returns>A <see cref="AdditionalEffectMetadataDot.DotBuff"/> instance if valid; otherwise, null.</returns>
private static AdditionalEffectMetadataDot.DotBuff? Convert(DotBuffProperty? dotBuff) {
if (dotBuff is not { buffID: > 0 }) {
return null;
Expand All@@ -239,6 +248,11 @@ private static AdditionalEffectMetadataStatus Convert(StatusProperty status, Off
return new AdditionalEffectMetadataDot.DotBuff(Target: (SkillTargetType) dotBuff.target, Id: dotBuff.buffID, Level: dotBuff.buffLevel);
}

/// <summary>
/// Converts a <see cref="ShieldProperty"/> to an <see cref="AdditionalEffectMetadataShield"/> if shield values are positive; otherwise returns null.
/// </summary>
/// <param name="shield">The shield property to convert.</param>
/// <returns>An <see cref="AdditionalEffectMetadataShield"/> if applicable; otherwise, null.</returns>
private static AdditionalEffectMetadataShield? Convert(ShieldProperty shield) {
if (shield is { hpValue: <= 0, hpByTargetMaxHP: <= 0 }) {
return null;
Expand Down
11 changes: 10 additions & 1 deletion Maple2.File.Ingest/Mapper/SkillMapper.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Diagnostics;
using System.Diagnostics;
using Maple2.File.IO;
using Maple2.File.Parser;
using Maple2.File.Parser.Xml.Skill;
Expand All@@ -14,6 +14,10 @@ public SkillMapper(M2dReader xmlReader) {
parser = new SkillParser(xmlReader);
}

/// <summary>
/// Maps parsed skill XML data into structured <see cref="StoredSkillMetadata"/> objects, transforming raw skill definitions into strongly typed metadata for further processing.
/// </summary>
/// <returns>An enumerable sequence of <see cref="StoredSkillMetadata"/> representing all valid skills parsed from the source data.</returns>
protected override IEnumerable<StoredSkillMetadata> Map() {
foreach ((int id, string name, SkillData data) in parser.Parse()) {
if (data.basic == null) continue; // Old_JobChange_01
Expand DownExpand Up@@ -133,6 +137,11 @@ protected override IEnumerable<StoredSkillMetadata> Map() {
}
}

/// <summary>
/// Converts a <see cref="RegionSkill"/> object into a <see cref="SkillMetadataRange"/>, mapping region type strings to <see cref="SkillRegion"/> values and transferring relevant range properties.
/// </summary>
/// <param name="region">The region skill data to convert.</param>
/// <returns>A <see cref="SkillMetadataRange"/> representing the region's metadata.</returns>
private static SkillMetadataRange Convert(RegionSkill region) {
return new SkillMetadataRange(
Type: region.rangeType switch {
Expand Down
12 changes: 11 additions & 1 deletion Maple2.File.Ingest/MapperExtensions.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using Maple2.File.Ingest.Utils;
Expand DownExpand Up@@ -227,6 +227,11 @@ public static byte OptionIndex(this SpecialAttribute attribute) {
};
}

/// <summary>
/// Converts a <see cref="TriggerSkill"/> instance into a <see cref="SkillEffectMetadata"/>, mapping its properties to either a splash or condition effect and assembling the associated skills.
/// </summary>
/// <param name="trigger">The trigger skill data to convert.</param>
/// <returns>A <see cref="SkillEffectMetadata"/> representing the trigger's effect, including splash or condition details and linked skills.</returns>
public static SkillEffectMetadata Convert(this TriggerSkill trigger) {
SkillEffectMetadataCondition? condition = null;
SkillEffectMetadataSplash? splash = null;
Expand DownExpand Up@@ -316,6 +321,11 @@ public static SkillMetadataAutoTargeting Convert(this AutoTargeting autoTargetin
UseMove: autoTargeting.autoTargetUseMove);
}

/// <summary>
/// Converts a parsed XML skill begin condition into a strongly typed <see cref="BeginCondition"/> metadata object for use in Maple2 game logic.
/// </summary>
/// <param name="beginCondition">The XML-parsed skill begin condition to convert.</param>
/// <returns>A <see cref="BeginCondition"/> instance containing mapped level, gender, mesos, stats, map and skill requirements, job codes, probability, cooldowns, durations, state flags, dungeon group types, weapon requirements, and subconditions for target, owner, and caster.</returns>
public static BeginCondition Convert(this Maple2.File.Parser.Xml.Skill.BeginCondition beginCondition) {
return new BeginCondition(
Level: beginCondition.level,
Expand Down
10 changes: 9 additions & 1 deletion Maple2.Server.Core/Packets/RequestPacket.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.PacketLib.Tools;
using Maple2.PacketLib.Tools;
using Maple2.Server.Core.Constants;

namespace Maple2.Server.Core.Packets;
Expand All@@ -8,10 +8,18 @@ public static ByteWriter Login() {
return Packet.Of(SendOp.RequestLogin);
}

/// <summary>
/// Creates a packet for requesting a session key from the server.
/// </summary>
/// <returns>A <see cref="ByteWriter"/> containing the key request packet.</returns>
public static ByteWriter Key() {
return Packet.Of(SendOp.RequestKey);
}

/// <summary>
/// Creates a heartbeat request packet containing the current system tick count.
/// </summary>
/// <returns>A ByteWriter representing the heartbeat request packet.</returns>
public static ByteWriter Heartbeat() {
var pWriter = Packet.Of(SendOp.RequestHeartbeat);
pWriter.WriteInt(Environment.TickCount);
Expand Down
13 changes: 12 additions & 1 deletion Maple2.Server.Game/Commands/BuffCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using Maple2.Database.Storage;
Expand DownExpand Up@@ -40,6 +40,17 @@ public BuffCommand(GameSession session, SkillMetadataStorage skillStorage) : bas
this.SetHandler<InvocationContext, int, int, int, int, bool, string, bool>(Handle, id, level, stack, duration, all, target, remove);
}

/// <summary>
/// Processes the "buff" command to add or remove a specified buff on one or more players in the current field.
/// </summary>
/// <param name="ctx">The command invocation context.</param>
/// <param name="buffId">The ID of the buff to add or remove.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="stack">The number of buff stacks to apply.</param>
/// <param name="duration">The duration of the buff in seconds, or -1 for default duration.</param>
/// <param name="all">If true, applies the operation to all players in the field.</param>
/// <param name="target">The name of the target player to affect, or empty to target the command issuer.</param>
/// <param name="remove">If true, removes the buff instead of adding it.</param>
private void Handle(InvocationContext ctx, int buffId, int level, int stack, int duration, bool all, string target, bool remove) {
try {
if (!skillStorage.TryGetEffect(buffId, (short) level, out AdditionalEffectMetadata? _)) {
Expand Down
8 changes: 7 additions & 1 deletion Maple2.Server.Game/Commands/KillCommand.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.CommandLine;
using System.CommandLine;
using System.CommandLine.Invocation;
using System.CommandLine.IO;
using System.Numerics;
Expand DownExpand Up@@ -141,6 +141,12 @@ private void Handle(InvocationContext ctx, string name) {
}
}

/// <summary>
/// Instantly kills the specified NPC by applying damage equal to its current health and broadcasts the resulting updates to the field.
/// </summary>
/// <param name="session">The game session performing the kill action.</param>
/// <param name="npc">The NPC to be killed.</param>
/// <param name="skill">The skill metadata used to generate the damage record.</param>
private static void Kill(GameSession session, FieldNpc npc, SkillMetadata skill) {
var damageRecord = new DamageRecord(skill, skill.Data.Motions[0].Attacks[0]) {
CasterId = session.Player.ObjectId,
Expand Down
14 changes: 11 additions & 3 deletions Maple2.Server.Game/Manager/AnimationManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Model.Enum;
using Maple2.Model.Enum;
using Maple2.Model.Metadata;
using Maple2.Server.Core.Packets;
using Maple2.Server.Game.Model;
Expand DownExpand Up@@ -199,7 +199,10 @@ public void CancelSequence() {
/// <summary>
/// Updates the animation state based on the current tick count.
/// </summary>
/// <param name="tickCount">The current server tick count</param>
/// <summary>
/// Updates the animation state for the actor based on the current server tick, processing keyframe events, handling looping, and resetting sequences as needed.
/// </summary>
/// <param name="tickCount">The current server tick count.</param>
public void Update(long tickCount) {
// Skip update if no animation metadata is available
if (RigMetadata is null) {
Expand DownExpand Up@@ -349,7 +352,12 @@ public float GetSequenceSegmentTime(string keyframe1, string keyframe2) {
/// </summary>
/// <param name="sequenceTime">The current sequence time</param>
/// <param name="key">The keyframe that was hit</param>
/// <param name="speed">The current animation speed</param>
/// <summary>
/// Handles a keyframe event during an animation sequence, triggering actor callbacks and updating loop or end timing based on the keyframe type.
/// </summary>
/// <param name="sequenceTime">The current normalized time within the animation sequence.</param>
/// <param name="key">The animation keyframe being processed.</param>
/// <param name="speed">The current animation speed.</param>
private void HitKeyframe(float sequenceTime, AnimationKey key, float speed) {
isHandlingKeyframe = true;

Expand Down
50 changes: 49 additions & 1 deletion Maple2.Server.Game/Manager/Config/BuffManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Numerics;
using Maple2.Model.Enum;
using Maple2.Model.Game;
Expand DownExpand Up@@ -61,6 +61,9 @@ public void Clear() {
}
}

/// <summary>
/// Applies entrance buffs and refreshes premium club buffs for the actor when entering a field.
/// </summary>
public void LoadFieldBuffs() {
// Lapenshards
// Game Events
Expand All@@ -71,6 +74,18 @@ public void LoadFieldBuffs() {
}
}

/// <summary>
/// Adds a buff to the specified owner, handling stacking, duration, cooldowns, group conflicts, and effect application.
/// </summary>
/// <param name="caster">The actor applying the buff.</param>
/// <param name="owner">The actor receiving the buff.</param>
/// <param name="id">The buff's skill or effect ID.</param>
/// <param name="level">The level of the buff.</param>
/// <param name="startTick">The tick count when the buff starts.</param>
/// <param name="stacks">The number of stacks to apply (clamped to the buff's maximum).</param>
/// <param name="durationMs">The duration of the buff in milliseconds. If negative, uses the default duration.</param>
/// <param name="notifyField">Whether to broadcast the buff addition to the field.</param>
/// <param name="type">The event condition type triggering the buff application.</param>
public void AddBuff(IActor caster, IActor owner, int id, short level, long startTick, int stacks = 0, int durationMs = -1, bool notifyField = true, EventConditionType type = EventConditionType.Activate) {
if (!owner.Field.SkillMetadata.TryGetEffect(id, level, out AdditionalEffectMetadata? additionalEffect)) {
logger.Error("Invalid buff: {SkillId},{Level}", id, level);
Expand DownExpand Up@@ -310,6 +325,11 @@ public float TotalCompulsionRate(CompulsionEventType type, int skillId = 0) {
nestedCompulsionDic.Values.Where(compulsion => compulsion.SkillIds.Contains(skillId)).Sum(compulsion => compulsion.Rate);
}

/// <summary>
/// Returns the resistance value for the specified attribute, or 0 if not present.
/// </summary>
/// <param name="attribute">The attribute for which to retrieve resistance.</param>
/// <returns>The resistance value for the given attribute, or 0 if none is set.</returns>
public float GetResistance(BasicAttribute attribute) {
if (Resistances.TryGetValue(attribute, out float value)) {
return value;
Expand All@@ -318,6 +338,13 @@ public float GetResistance(BasicAttribute attribute) {
return 0;
}

/// <summary>
/// Calculates the total invoke value and rate for a specified invoke effect type, filtered by skill ID or skill group.
/// </summary>
/// <param name="invokeType">The type of invoke effect to aggregate.</param>
/// <param name="skillId">The skill ID to match against invoke effects.</param>
/// <param name="skillGroup">Optional skill group IDs to match against invoke effects.</param>
/// <returns>A tuple containing the total invoke value (as an integer) and the total invoke rate (as a float).</returns>
public (int, float) GetInvokeValues(InvokeEffectType invokeType, int skillId, params int[] skillGroup) {
if (!Invokes.TryGetValue(invokeType, out var nestedInvokeDic))
return (0, 0f);
Expand All@@ -335,6 +362,9 @@ public float GetResistance(BasicAttribute attribute) {
return ((int) value, rate);
}

/// <summary>
/// Sets the shield health for a buff based on its metadata, using either a fixed value or a percentage of the actor's maximum health.
/// </summary>
private void SetShield(Buff buff) {
if (buff.Metadata.Shield == null) {
return;
Expand DownExpand Up@@ -369,6 +399,9 @@ private void SetMount(Buff buff) {
}
}

/// <summary>
/// Applies update effects from the buff, including canceling specified buffs and resetting skill cooldowns for the actor.
/// </summary>
private void SetUpdates(Buff buff) {
if (buff.Metadata.Update.Cancel != null) {
CancelBuffs(buff, buff.Metadata.Update.Cancel);
Expand All@@ -381,6 +414,15 @@ private void SetUpdates(Buff buff) {
}
}
}
/// <summary>
/// Triggers an event for all enabled buffs, causing the owner to apply each buff's effects for the specified event type.
/// </summary>
/// <param name="caster">The actor who initiated the event.</param>
/// <param name="owner">The actor who owns the buffs.</param>
/// <param name="target">The target actor affected by the event.</param>
/// <param name="type">The event condition type that determines which effects to apply.</param>
/// <param name="skillId">Optional skill ID associated with the event.</param>
/// <param name="buffId">Optional buff ID associated with the event.</param>
public void TriggerEvent(IActor caster, IActor owner, IActor target, EventConditionType type, int skillId = 0, int buffId = 0) {
foreach (Buff buff in EnumerateBuffs()) {
if (!buff.Enabled) {
Expand DownExpand Up@@ -520,6 +562,12 @@ public void Remove(params (int id, int casterId)[] buffIds) {
}
}

/// <summary>
/// Removes all buffs with the specified ID and caster ID from the actor, updates resistances, handles related effects, and refreshes stats if necessary.
/// </summary>
/// <param name="id">The buff ID to remove.</param>
/// <param name="casterId">The object ID of the caster whose buffs should be removed.</param>
/// <returns>True if the removal process completes.</returns>
public bool Remove(int id, int casterId) {
//TODO: Check if buff is removable/should be removed
bool refreshStats = false;
Expand Down
5 changes: 4 additions & 1 deletion Maple2.Server.Game/Manager/Config/SkillManager.cs
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using Maple2.Database.Storage;
using Maple2.Database.Storage;
using Maple2.Model.Enum;
using Maple2.Model.Game;
using Maple2.Model.Metadata;
Expand DownExpand Up@@ -30,6 +30,9 @@ public void LoadSkillBook() {
session.Send(SkillBookPacket.Load(SkillBook));
}

/// <summary>
/// Applies all passive skill effects that target the player, updating active buffs based on the player's learned passive skills.
/// </summary>
public void UpdatePassiveBuffs(bool notifyField = true) {
// TODO: Only remove buffs that have been unlearned.
/*foreach (Buff buff in session.Player.Buffs.Buffs.Values) {
Expand Down
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
Expand DownExpand Up@@ -437,6 +437,10 @@ public void AddSkill(IActor caster, SkillEffectMetadata effect, Vector3[] points
}
}

/// <summary>
/// Adds splash skill effects from a skill record, calculating effect positions based on cube magic paths if applicable.
/// </summary>
/// <param name="record">The skill record containing caster, position, rotation, and attack metadata.</param>
public void AddSkill(SkillRecord record) {
SkillMetadataAttack attack = record.Attack;
if (!TableMetadata.MagicPathTable.Entries.TryGetValue(attack.CubeMagicPathId, out IReadOnlyList<MagicPath>? cubeMagicPaths)) {
Expand DownExpand Up@@ -474,6 +478,14 @@ public void AddSkill(SkillRecord record) {
}
}

/// <summary>
/// Returns a filtered collection of actors within the specified prisms, based on the target type and limit.
/// </summary>
/// <param name="prisms">The prisms used to filter actors by location or area.</param>
/// <param name="targetType">The type of targets to select (e.g., friendly players, hostile mobs, or hungry mobs).</param>
/// <param name="limit">The maximum number of actors to return.</param>
/// <param name="ignore">An optional collection of actors to exclude from the results.</param>
/// <returns>An enumerable of actors matching the criteria, or an empty collection if the target type is unhandled.</returns>
public IEnumerable<IActor> GetTargets(Prism[] prisms, ApplyTargetType targetType, int limit, ICollection<IActor>? ignore = null) {
switch (targetType) {
case ApplyTargetType.Friendly:
Expand Down
Loading